diff --git a/MIGRATION.md b/MIGRATION.md index 0206afe..7cfebb7 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -350,3 +350,9 @@ instead」)。`@koa/router` 是官方维护的继任包,API 与 koa-router * ### 影响范围 仅文档生成器(`generate_swagger` / `generate_postman`),不涉及运行时校验、路由绑定或类型推导。已声明 response schema 的项目重新生成文档即可看到变化;未声明的项目输出与之前完全一致。 + +## v3.2.3 — Axios SDK 路径参数兜底(issue #27) + +### 修复 + +- **`generate_axios` 在未声明 `paramsSchema` 时从路径兜底提取路径参数**:此前 `getPathParams` 只读 `paramsSchema`,而 `getReqSendPath` 只看路径串,两者脱节。当路由为 `/:id` 形式但用户未调用 `.params()` / `registerTyped({ params })` 时,生成的请求路径会引用变量 `id`,但函数签名里没有 `id` 参数 → 生成引用未定义变量的坏 SDK。现在 `getPathParams` 在 `paramsSchema` 为空时从 `realPath` 的 `:xxx` 提取参数名作为兜底,保证签名与路径一致。已声明 `paramsSchema` 的场景行为不变。 diff --git a/package.json b/package.json index 0582519..6894e41 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "erest", - "version": "3.2.2", + "version": "3.2.3", "description": "Framework-agnostic REST API framework with Zod validation, auto docs & test scaffolding. Express/Koa/@leizm/web adapters as separate packages.", "type": "module", "main": "dist/lib/index.js", diff --git a/packages/erest-express/package.json b/packages/erest-express/package.json index 0a48c3f..c9511dc 100644 --- a/packages/erest-express/package.json +++ b/packages/erest-express/package.json @@ -1,6 +1,6 @@ { "name": "@erest/express", - "version": "3.2.2", + "version": "3.2.3", "description": "Express adapter for erest", "type": "module", "main": "./dist/index.js", diff --git a/packages/erest-gen/package.json b/packages/erest-gen/package.json index a4593cf..50e011a 100644 --- a/packages/erest-gen/package.json +++ b/packages/erest-gen/package.json @@ -1,6 +1,6 @@ { "name": "@erest/gen", - "version": "3.2.2", + "version": "3.2.3", "description": "erest codegen CLI:从 Zod schema 生成 handler 骨架等(实验性,独立于 erest 主版本演进)", "type": "module", "bin": { diff --git a/packages/erest-koa/package.json b/packages/erest-koa/package.json index d633e95..4f5086d 100644 --- a/packages/erest-koa/package.json +++ b/packages/erest-koa/package.json @@ -1,6 +1,6 @@ { "name": "@erest/koa", - "version": "3.2.2", + "version": "3.2.3", "description": "Koa adapter for erest", "type": "module", "main": "./dist/index.js", diff --git a/packages/erest-leizmweb/package.json b/packages/erest-leizmweb/package.json index ec8256e..9f8e33b 100644 --- a/packages/erest-leizmweb/package.json +++ b/packages/erest-leizmweb/package.json @@ -1,6 +1,6 @@ { "name": "@erest/leizmweb", - "version": "3.2.2", + "version": "3.2.3", "description": "@leizm/web adapter for erest", "type": "module", "main": "./dist/index.js", diff --git a/src/lib/plugin/generate_axios/index.ts b/src/lib/plugin/generate_axios/index.ts index 285c9e3..66235f3 100644 --- a/src/lib/plugin/generate_axios/index.ts +++ b/src/lib/plugin/generate_axios/index.ts @@ -48,8 +48,13 @@ export default function generateAxios(data: IDocData, dir: string, options: IDoc return `'${path}'`; } - function getPathParams(_req: APIOption) { - const dataKeys = schemaFieldNames(_req.paramsSchema); + function getPathParams(req: APIOption) { + // 优先用 paramsSchema 声明的字段名;缺失时从路径 :xxx 兜底, + // 避免生成的请求路径引用了未出现在签名里的变量(issue #27)。 + let dataKeys = schemaFieldNames(req.paramsSchema); + if (!dataKeys.length && hasUrlParam(req.realPath)) { + dataKeys = (req.realPath.match(/:([a-z]+)/gi) || []).map((s) => s.slice(1)); + } if (!dataKeys.length) return ""; return dataKeys.map((key) => `${key},`).join(""); } diff --git a/src/test/test-docs-plugins.ts b/src/test/test-docs-plugins.ts index 610ab5f..201ae35 100644 --- a/src/test/test-docs-plugins.ts +++ b/src/test/test-docs-plugins.ts @@ -144,3 +144,52 @@ describe("postman response 示例(issue #6)", () => { expect(item.response).toBeUndefined(); }); }); + +describe("axios SDK 路径参数生成(issue #27)", () => { + function captureAxios(api: Record) { + let captured = ""; + const writer = (_p: string, data: string) => (captured = data); + generateAxios( + { + info: { title: "t", description: "d", host: "http://x", basePath: "" }, + group: {}, + types: {}, + apis: { "get_/posts/:id": { method: "get", title: "t", ...api } }, + } as any, + "/out", + { axios: "sdk.js" } as any, + writer + ); + return captured; + } + + test("声明了 paramsSchema 时,路径参数出现在函数签名里", () => { + const sdk = captureAxios({ + realPath: "/posts/:id", + paramsSchema: z.object({ id: z.number() }), + }); + // 函数签名应包含 id 参数 + expect(sdk).toMatch(/getPostsId\(\s*id/); + // 请求路径应使用模板插值(生成器把 :id 转成 ${id},输出带转义反斜杠) + expect(sdk).toContain("/posts/"); + expect(sdk).toContain("id\\}"); + }); + + test("路径含 :id 但未声明 paramsSchema 时,签名仍应补全路径参数(避免生成引用未定义变量的坏代码)", () => { + const sdk = captureAxios({ + realPath: "/posts/:id", + // 没有 paramsSchema + }); + expect(sdk).toMatch(/getPostsId\(\s*id/); + expect(sdk).toContain("/posts/"); + expect(sdk).toContain("id\\}"); + }); + + test("无路径参数时,签名不含多余的路径参数", () => { + const sdk = captureAxios({ + realPath: "/posts", + }); + expect(sdk).toMatch(/getPosts\(\s*\)/); + expect(sdk).toContain("'/posts'"); + }); +});