Skip to content

Commit 44a9a80

Browse files
committed
feat: add global response and global parameter support
1 parent 7eff691 commit 44a9a80

File tree

5 files changed

+607
-2
lines changed

5 files changed

+607
-2
lines changed

ROADMAP.md

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
- [ ] 模仿 nestjs 添加 addxxxAuth 方法
2-
- [ ] 模仿 nestjs 添加 addGlobalResponse/addGlobalParameter 方法
31
- [ ] 更严格的校验(对 JSON Schema 进行校验)
42
- [ ] 支持通过 ClassValidator 校验请求参数并生成 schema
53
- [ ] 适配其他 Node.js 框架

src/builders/OpenAPIBuilder.test.ts

Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,4 +1215,221 @@ describe("OpenAPIBuilder", () => {
12151215
expect(returnValue).toStrictEqual(builder);
12161216
});
12171217
});
1218+
1219+
describe("addGlobalResponse", () => {
1220+
it("应该添加全局响应对象", () => {
1221+
const builder = new OpenAPIBuilder();
1222+
const response: ResponseObject = {
1223+
description: "内部服务器错误",
1224+
content: {
1225+
"application/json": {
1226+
schema: {
1227+
type: "object",
1228+
properties: {
1229+
error: { type: "string" },
1230+
message: { type: "string" },
1231+
},
1232+
},
1233+
},
1234+
},
1235+
};
1236+
builder.addGlobalResponse("500", response);
1237+
const globalResponses = builder.getGlobalResponses();
1238+
1239+
expect(globalResponses["500"]).toStrictEqual(response);
1240+
});
1241+
1242+
it("应该添加全局响应引用对象", () => {
1243+
const builder = new OpenAPIBuilder();
1244+
const responseRef: ReferenceObject = {
1245+
$ref: "#/components/responses/InternalServerError",
1246+
};
1247+
builder.addGlobalResponse("500", responseRef);
1248+
const globalResponses = builder.getGlobalResponses();
1249+
1250+
expect(globalResponses["500"]).toStrictEqual(responseRef);
1251+
});
1252+
1253+
it("应该支持添加多个不同状态码的全局响应", () => {
1254+
const builder = new OpenAPIBuilder();
1255+
const response401: ResponseObject = { description: "未授权" };
1256+
const response500: ResponseObject = { description: "内部服务器错误" };
1257+
1258+
builder.addGlobalResponse("401", response401).addGlobalResponse("500", response500);
1259+
1260+
const globalResponses = builder.getGlobalResponses();
1261+
1262+
expect(globalResponses["401"]).toStrictEqual(response401);
1263+
expect(globalResponses["500"]).toStrictEqual(response500);
1264+
});
1265+
1266+
it("应该覆盖相同状态码的全局响应", () => {
1267+
const builder = new OpenAPIBuilder();
1268+
const firstResponse: ResponseObject = { description: "第一个响应" };
1269+
const secondResponse: ResponseObject = { description: "第二个响应" };
1270+
1271+
builder.addGlobalResponse("500", firstResponse).addGlobalResponse("500", secondResponse);
1272+
1273+
const globalResponses = builder.getGlobalResponses();
1274+
1275+
expect(globalResponses["500"]).toStrictEqual(secondResponse);
1276+
});
1277+
1278+
it("应该支持链式调用", () => {
1279+
const builder = new OpenAPIBuilder();
1280+
const response: ResponseObject = { description: "测试响应" };
1281+
const returnValue = builder.addGlobalResponse("500", response);
1282+
1283+
expect(returnValue).toStrictEqual(builder);
1284+
});
1285+
});
1286+
1287+
describe("getGlobalResponses", () => {
1288+
it("应该返回空对象当没有全局响应时", () => {
1289+
const builder = new OpenAPIBuilder();
1290+
const globalResponses = builder.getGlobalResponses();
1291+
1292+
expect(globalResponses).toStrictEqual({});
1293+
});
1294+
1295+
it("应该返回所有全局响应的深拷贝", () => {
1296+
const builder = new OpenAPIBuilder();
1297+
const response: ResponseObject = { description: "测试响应" };
1298+
1299+
builder.addGlobalResponse("500", response);
1300+
const globalResponses1 = builder.getGlobalResponses();
1301+
const globalResponses2 = builder.getGlobalResponses();
1302+
1303+
expect(globalResponses1).toStrictEqual(globalResponses2);
1304+
expect(globalResponses1).not.toBe(globalResponses2);
1305+
expect(globalResponses1["500"]).toStrictEqual(response);
1306+
expect(globalResponses1["500"]).not.toBe(response);
1307+
});
1308+
});
1309+
1310+
describe("addGlobalParameter", () => {
1311+
it("应该添加全局参数对象", () => {
1312+
const builder = new OpenAPIBuilder();
1313+
const parameter: ParameterObject = {
1314+
name: "Authorization",
1315+
in: "header",
1316+
description: "认证令牌",
1317+
required: true,
1318+
schema: {
1319+
type: "string",
1320+
example: "Bearer token123",
1321+
},
1322+
};
1323+
builder.addGlobalParameter(parameter);
1324+
const globalParameters = builder.getGlobalParameters();
1325+
1326+
expect(globalParameters).toHaveLength(1);
1327+
expect(globalParameters[0]).toStrictEqual(parameter);
1328+
});
1329+
1330+
it("应该添加全局参数引用对象", () => {
1331+
const builder = new OpenAPIBuilder();
1332+
const parameterRef: ReferenceObject = {
1333+
$ref: "#/components/parameters/Authorization",
1334+
};
1335+
builder.addGlobalParameter(parameterRef);
1336+
const globalParameters = builder.getGlobalParameters();
1337+
1338+
expect(globalParameters).toHaveLength(1);
1339+
expect(globalParameters[0]).toStrictEqual(parameterRef);
1340+
});
1341+
1342+
it("应该支持添加多个不同的全局参数", () => {
1343+
const builder = new OpenAPIBuilder();
1344+
const authParam: ParameterObject = {
1345+
name: "Authorization",
1346+
in: "header",
1347+
description: "认证令牌",
1348+
required: true,
1349+
schema: { type: "string" },
1350+
};
1351+
const versionParam: ParameterObject = {
1352+
name: "version",
1353+
in: "query",
1354+
description: "API版本",
1355+
required: false,
1356+
schema: { type: "string", example: "v1" },
1357+
};
1358+
1359+
builder.addGlobalParameter(authParam).addGlobalParameter(versionParam);
1360+
1361+
const globalParameters = builder.getGlobalParameters();
1362+
1363+
expect(globalParameters).toHaveLength(2);
1364+
expect(globalParameters[0]).toStrictEqual(authParam);
1365+
expect(globalParameters[1]).toStrictEqual(versionParam);
1366+
});
1367+
1368+
it("应该支持添加相同名称但不同位置的全局参数", () => {
1369+
const builder = new OpenAPIBuilder();
1370+
const headerParam: ParameterObject = {
1371+
name: "id",
1372+
in: "header",
1373+
description: "头部中的ID",
1374+
required: true,
1375+
schema: { type: "string" },
1376+
};
1377+
const queryParam: ParameterObject = {
1378+
name: "id",
1379+
in: "query",
1380+
description: "查询参数中的ID",
1381+
required: false,
1382+
schema: { type: "string" },
1383+
};
1384+
1385+
builder.addGlobalParameter(headerParam).addGlobalParameter(queryParam);
1386+
1387+
const globalParameters = builder.getGlobalParameters();
1388+
1389+
expect(globalParameters).toHaveLength(2);
1390+
expect(globalParameters[0]).toStrictEqual(headerParam);
1391+
expect(globalParameters[1]).toStrictEqual(queryParam);
1392+
});
1393+
1394+
it("应该支持链式调用", () => {
1395+
const builder = new OpenAPIBuilder();
1396+
const parameter: ParameterObject = {
1397+
name: "test",
1398+
in: "query",
1399+
description: "测试参数",
1400+
schema: { type: "string" },
1401+
};
1402+
const returnValue = builder.addGlobalParameter(parameter);
1403+
1404+
expect(returnValue).toStrictEqual(builder);
1405+
});
1406+
});
1407+
1408+
describe("getGlobalParameters", () => {
1409+
it("应该返回空数组当没有全局参数时", () => {
1410+
const builder = new OpenAPIBuilder();
1411+
const globalParameters = builder.getGlobalParameters();
1412+
1413+
expect(globalParameters).toStrictEqual([]);
1414+
});
1415+
1416+
it("应该返回所有全局参数的深拷贝", () => {
1417+
const builder = new OpenAPIBuilder();
1418+
const parameter: ParameterObject = {
1419+
name: "test",
1420+
in: "query",
1421+
description: "测试参数",
1422+
schema: { type: "string" },
1423+
};
1424+
1425+
builder.addGlobalParameter(parameter);
1426+
const globalParameters1 = builder.getGlobalParameters();
1427+
const globalParameters2 = builder.getGlobalParameters();
1428+
1429+
expect(globalParameters1).toStrictEqual(globalParameters2);
1430+
expect(globalParameters1).not.toBe(globalParameters2);
1431+
expect(globalParameters1[0]).toStrictEqual(parameter);
1432+
expect(globalParameters1[0]).not.toBe(parameter);
1433+
});
1434+
});
12181435
});

src/builders/OpenAPIBuilder.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ import { isZodSchema } from "@/utils/typeGuards";
3333
*/
3434
export class OpenAPIBuilder implements Builder<OpenAPIObject> {
3535
private document: OpenAPIObject;
36+
private globalResponses: Record<string, ResponseObject | ReferenceObject> = {};
37+
private globalParameters: (ParameterObject | ReferenceObject)[] = [];
3638

3739
constructor(document?: Partial<Omit<OpenAPIObject, "info"> & { info: Partial<InfoObject> }>) {
3840
const defaultDocument: OpenAPIObject = {
@@ -47,6 +49,43 @@ export class OpenAPIBuilder implements Builder<OpenAPIObject> {
4749
return cloneDeep(this.document);
4850
}
4951

52+
/**
53+
* 获取全局响应配置。
54+
* @returns 全局响应对象。
55+
*/
56+
getGlobalResponses() {
57+
return cloneDeep(this.globalResponses);
58+
}
59+
60+
/**
61+
* 添加全局响应,该响应将应用于所有操作。
62+
* @param statusCode HTTP 状态码或 "default"。
63+
* @param response 响应对象或引用对象。
64+
* @returns 文档构建器。
65+
*/
66+
addGlobalResponse(statusCode: string | "default", response: ResponseObject | ReferenceObject) {
67+
this.globalResponses[statusCode] = response;
68+
return this;
69+
}
70+
71+
/**
72+
* 获取全局参数配置。
73+
* @returns 全局参数数组。
74+
*/
75+
getGlobalParameters() {
76+
return cloneDeep(this.globalParameters);
77+
}
78+
79+
/**
80+
* 添加全局参数,该参数将应用于所有操作。
81+
* @param parameter 参数对象或引用对象。
82+
* @returns 文档构建器。
83+
*/
84+
addGlobalParameter(parameter: ParameterObject | ReferenceObject) {
85+
this.globalParameters.push(parameter);
86+
return this;
87+
}
88+
5089
/**
5190
* 设置 OpenAPI 版本。
5291
* @param version OpenAPI 版本。

0 commit comments

Comments
 (0)