From 341cffd4ce9872659116ce87458559a5d18c4694 Mon Sep 17 00:00:00 2001 From: yourtion Date: Wed, 1 Jul 2026 09:13:38 +0800 Subject: [PATCH] =?UTF-8?q?fix(axios-sdk):=20=E8=B7=AF=E5=BE=84=E5=8F=82?= =?UTF-8?q?=E6=95=B0=E5=85=9C=E5=BA=95=E6=8F=90=E5=8F=96=EF=BC=8C=E9=81=BF?= =?UTF-8?q?=E5=85=8D=E7=94=9F=E6=88=90=E5=BC=95=E7=94=A8=E6=9C=AA=E5=AE=9A?= =?UTF-8?q?=E4=B9=89=E5=8F=98=E9=87=8F=E7=9A=84=E5=9D=8F=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=20(issue=20#27)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit getPathParams 此前只读 paramsSchema,与 getReqSendPath(看路径串)脱节。 当路由为 /:id 但未声明 params schema 时,生成路径引用了 id 变量, 函数签名却没有 id 参数 → 坏 SDK。 现在 getPathParams 在 paramsSchema 为空时从 realPath 的 :xxx 兜底提取, 保证签名与路径一致。已声明 paramsSchema 的场景行为不变。 - 新增 3 个回归测试(声明 params / 未声明 fallback / 无路径参数) - bump 版本 3.2.1 → 3.2.3,登记 MIGRATION --- MIGRATION.md | 6 ++++ package.json | 2 +- packages/erest-express/package.json | 2 +- packages/erest-gen/package.json | 2 +- packages/erest-koa/package.json | 2 +- packages/erest-leizmweb/package.json | 2 +- src/lib/plugin/generate_axios/index.ts | 9 +++-- src/test/test-docs-plugins.ts | 49 ++++++++++++++++++++++++++ 8 files changed, 67 insertions(+), 7 deletions(-) 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'"); + }); +});