Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions MIGRATION.md
Original file line number Diff line number Diff line change
Expand Up @@ -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` 的场景行为不变。
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/erest-express/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/erest-gen/package.json
Original file line number Diff line number Diff line change
@@ -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": {
Expand Down
2 changes: 1 addition & 1 deletion packages/erest-koa/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
2 changes: 1 addition & 1 deletion packages/erest-leizmweb/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
9 changes: 7 additions & 2 deletions src/lib/plugin/generate_axios/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import type { IDocOptions } from "../../index.js";
import * as utils from "../../utils.js";

export default function generateAxios(data: IDocData, dir: string, options: IDocOptions, writter: IDocWritter) {

Check warning on line 8 in src/lib/plugin/generate_axios/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

unicorn(consistent-function-scoping)

Function `rmPathParam` does not capture any variables from its parent scope

Check warning on line 8 in src/lib/plugin/generate_axios/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

unicorn(consistent-function-scoping)

Function `slashToCamel` does not capture any variables from its parent scope
debug("generateAxios: %s - %o", dir, options);

function slashToCamel(name: string) {
Expand All @@ -14,7 +14,7 @@
});
}

function rmPathParam(path: string) {

Check warning on line 17 in src/lib/plugin/generate_axios/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

eslint(no-shadow)

'path' is already declared in the upper scope.
return path.replace(/:([a-z]+)/gi, "$1");
}

Expand All @@ -37,19 +37,24 @@
return `{ ${dataKeys.join(", ")} }`;
}

function hasUrlParam(path: string) {

Check warning on line 40 in src/lib/plugin/generate_axios/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

eslint(no-shadow)

'path' is already declared in the upper scope.
return /:[a-z]+/i.test(path);
}

function getReqSendPath(path: string) {

Check warning on line 44 in src/lib/plugin/generate_axios/index.ts

View workflow job for this annotation

GitHub Actions / test (22.x)

eslint(no-shadow)

'path' is already declared in the upper scope.
if (hasUrlParam(path)) {
return `\`${path.replace(/:([a-z]+)/gi, "\\$\\{$1\\}")}\``;
}
return `'${path}'`;
}

function getPathParams(_req: APIOption<unknown>) {
const dataKeys = schemaFieldNames(_req.paramsSchema);
function getPathParams(req: APIOption<unknown>) {
// 优先用 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("");
}
Expand Down
49 changes: 49 additions & 0 deletions src/test/test-docs-plugins.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,3 +144,52 @@ describe("postman response 示例(issue #6)", () => {
expect(item.response).toBeUndefined();
});
});

describe("axios SDK 路径参数生成(issue #27)", () => {
function captureAxios(api: Record<string, unknown>) {
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'");
});
});
Loading