fastpaper-cli 0.2.0

CLI tool for searching, downloading and reading academic papers
Documentation
# 新增数据源指南

> English: [adding-a-source.md]adding-a-source.md

先阅读[数据源模块契约](architecture.zh-CN.md#数据源模块契约)。全文以 `xueshu`(百度学术)为贯穿实例。

## Step 0 — 调研

写代码之前,先产出一份调研笔记,回答:

- **端点**:URL、HTTP 方法、必需请求头。
- **参数**:查询串编码、分页方案、每页条数。
- **响应结构**:论文列表在哪个字段下、字段名与类型、缺失值如何表示(`null`、空串、字段缺席——常常三者都有)。
- **认证与反爬行为**:API key、限频、风控特征。
- **分页**:offset/cursor、最后一页如何判断。

体例参考 [docs/sources/](sources/README.md) 里的现有笔记,写完放进去。

*xueshu 实例:*新版网页前端调用未公开的 JSON 端点 `GET /search/api/search`;请求需要 `Acs-Token` 头,前端为它准备了一个静态回退值;业务码 `7350001` 表示"需要交互式验证码";curl 默认 User-Agent 会触发该验证码,而诚实的工具 UA 不会。

## Step 1 — 抓取 fixture

发**一次**真实请求,把响应存为 `tests/fixtures/<source>_search.json`(或 `.xml`/`.html`)。先脱敏:

- 去掉带签名的 CDN URL、会话 token、任何含 `authorization=` 的查询串;
- 匿名化请求/日志 ID;
- 保留结构和真实的字段差异(空 DOI、缺失年份、标题里的 HTML 标签)——这些差异正是测试需要的。

## Step 2 — 先写解析器

创建 `src/sources/<source>.rs`。在写任何 HTTP 代码之前,对着 fixture 写解析器:

```rust
pub fn parse_search_response(json: &str) -> Result<Vec<Paper>, String>
```

测试先行:每个行为先写失败测试,再写最小实现。至少覆盖:

- 正常路径:论文数量符合预期、id/title 非空、`source` 字段正确;
- 字段映射:作者、年份、引用数、venue 回退、空串 → `None`;
- 文本清理:标题****摘要都要去掉搜索高亮标签(如 `<em>`);
- 容错:`null`、空串、字段缺席都不能导致解析失败;
- API 层错误:业务错误码必须返回清晰的 `Err`,而不是空列表。

*xueshu 实例:*22 个解析器测试,其中一个用内联 JSON(`"code": 7350001`)断言错误消息里提到验证码。

## Step 3 — HTTP 层

加上标准入口:

```rust
pub fn search(base_url: &str, query: &str, max_results: u32) -> Result<Vec<Paper>, String>
```

规则:

- `base_url` 由调用方传入——请求代码里绝不硬编码。
- 查询串用 `super::encode_query` 编码。
- 发送项目 User-Agent(`fastpaper-cli/<version> (+仓库 URL)`);有些风控引擎会拦截 HTTP 库的默认 UA。
- 串行分页;拿够 `max_results`、遇到空页或出错即停;返回前截断。
- 把 HTTP 状态映射为可区分的可读错误(403 被拦截 / 429 限频 / 其他)。

用 mockito(同步)测试:请求路径与参数、必需请求头、第二页分页、满足 `max_results` 后提前停止、每种错误状态。*xueshu 实例:*10 个 mockito 测试。

## Step 4 — 接线 CLI

四个接线点:

1. `src/cli.rs``enum Source` 加变体,`name()` 加分支。
2. `src/cli.rs` — 若该源不能提供 PDF,在 `download_hint()` 加一条有用的提示。
3. `src/main.rs``Search` match 里加分发分支:读 `FASTPAPER_<SOURCE>_URL`(默认真实 base URL),调用 `sources::<source>::search`4. `src/main.rs``sources` 命令的表格加一行。

在 `tests/cli.rs` 加集成测试(mockito server + `FASTPAPER_<SOURCE>_URL` 环境变量):搜索成功并输出已知标题、API 错误时非零退出并带消息、`sources` 列表包含新源名。

## Step 5 — 文档同步清单

- [ ] `README.md` — 源数量(两处:"N academic sources" 和 "N of M sources work with zero configuration")+ 表格行
- [ ] `README.zh-CN.md` — 同样三处
- [ ] `skills/fastpaper/SKILL.md` — frontmatter description 里的数量****正文里的数量,外加领域列表里的一条
- [ ] `docs/sources/` — 放入你的调研笔记,并在其 README 索引加一行
- [ ] Shell completions 由 enum 自动生成,无需改文件,但用户升级后需重新执行 `fastpaper completions <shell>`

## Step 6 — 真实 API 冒烟测试

在 `tests/cli.rs` 加一个 `#[ignore]` 测试,通过二进制访问真实 API:

```bash
cargo test --test cli <test_name> -- --ignored
```

只手动执行,绝不进 CI。保持礼貌:验证一次即可,不要轰炸。失败时先判断是不是对方风控(换个关键词、等一等),再下结论是集成坏了。绝不自动化验证码破解或逆向风控 token——对方要求交互式验证时,数据源应返回清晰错误并停止。