Skip to content

Commit 76f4ecb

Browse files
feat: 添加 OpenAPI 3.1 type 数组支持 (#618)
* feat: 添加 OpenAPI 3.1 type 数组支持 - 支持 OpenAPI 3.1 规范中的 type 数组格式(如 ["string", "null"]) - 更新类型定义以支持 type 为数组的情况 - 在 getDefaultType 函数中添加 type 数组处理逻辑 - 在 resolveEnumObject 函数中添加 type 数组处理逻辑 - 在 primitive 函数中添加 type 数组处理逻辑 - 添加测试用例验证 OpenAPI 3.1 type 数组格式 修复 issue: 当使用 OpenAPI 3.1 规范时,type 字段为数组格式会导致生成的类型为 unknown * chore: 添加 changeset 文件 * chore: 修复 changeset 格式并优化测试用例 - 将测试用例从独立文件合并到 common.spec.ts - 修复 changeset 文件格式(.json → .md) - 使用 patch 版本级别(1.12.3) - 更新 CHANGELOG * docs: changelog * chore: clean code * chore: clean code --------- Co-authored-by: luochao <1055120207@qq.com>
1 parent 28bf705 commit 76f4ecb

8 files changed

Lines changed: 170 additions & 7 deletions

File tree

.changeset/sunny-moons-appear.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openapi-ts-request': patch
3+
---
4+
5+
perf: 添加 OpenAPI 3.1 type 数组支持,支持 type 字段为数组格式(如:["string", "null"]

src/generator/serviceGeneratorHelper.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,18 @@ export function resolveEnumObject(params: {
112112
let enumStr = '';
113113
let enumLabelTypeStr = '';
114114

115-
if (numberEnum.includes(schemaObject.type) || isAllNumber(enumArray)) {
115+
// 获取实际的类型(处理 OpenAPI 3.1 的 type 数组情况)
116+
const getActualType = (type: typeof schemaObject.type): string => {
117+
if (Array.isArray(type)) {
118+
// 如果是数组,返回第一个非 null 类型
119+
return type.find((t) => t !== 'null') || 'string';
120+
}
121+
return type;
122+
};
123+
124+
const actualType = getActualType(schemaObject.type);
125+
126+
if (numberEnum.includes(actualType) || isAllNumber(enumArray)) {
116127
if (config.isSupportParseEnumDesc && schemaObject.description) {
117128
const enumMap = parseDescriptionEnum(schemaObject.description);
118129
enumStr = `{${map(enumArray, (value) => {
@@ -151,7 +162,7 @@ export function resolveEnumObject(params: {
151162
return `${value}:"${enumLabel}"`;
152163
}).join(',')}}`;
153164
} else {
154-
if (numberEnum.includes(schemaObject.type) || isAllNumber(enumArray)) {
165+
if (numberEnum.includes(actualType) || isAllNumber(enumArray)) {
155166
if (
156167
(config.isSupportParseEnumDesc || config.supportParseEnumDescByReg) &&
157168
schemaObject.description
@@ -334,7 +345,13 @@ export function resolveRefObject<T>(params: {
334345
resolvedType = (refResolved as { type?: string })?.type;
335346
} else {
336347
const schemaObj: SchemaObject = schema;
337-
resolvedType = schemaObj.type;
348+
// 处理 OpenAPI 3.1 的 type 数组情况
349+
if (Array.isArray(schemaObj.type)) {
350+
// 如果是数组,使用第一个非 null 类型
351+
resolvedType = schemaObj.type.find((t) => t !== 'null') || 'string';
352+
} else {
353+
resolvedType = schemaObj.type;
354+
}
338355
}
339356

340357
const finalSchema = schema as SchemaObject;

src/generator/util.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,20 @@ export function getDefaultType(
144144
const dateEnum = ['Date', 'date', 'dateTime', 'date-time', 'datetime'];
145145
const stringEnum = ['string', 'email', 'password', 'url', 'byte', 'binary'];
146146

147+
// OpenAPI 3.1 支持 type 为数组,例如 ["string", "null"]
148+
if (Array.isArray(type)) {
149+
return type
150+
.map((t) => {
151+
// 为数组中的每个类型创建一个临时的 schemaObject
152+
const tempSchema: ISchemaObject = {
153+
...schemaObject,
154+
type: t,
155+
};
156+
return getDefaultType(tempSchema, namespace, schemas);
157+
})
158+
.join(' | ');
159+
}
160+
147161
if (type === 'null') {
148162
return 'null';
149163
}

src/parser-mock/index.ts

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -132,12 +132,18 @@ function primitive(
132132
) {
133133
const schema = objectify(schemaParams);
134134
const { type, format } = schema;
135+
136+
// 处理 OpenAPI 3.1 的 type 数组情况
137+
const actualType = Array.isArray(type)
138+
? type.find((t) => t !== 'null') || 'string'
139+
: type;
140+
135141
const value =
136-
primitives[`${type}_${format || getDateByName(propsName)}`] ||
137-
primitives[type];
142+
primitives[`${actualType}_${format || getDateByName(propsName)}`] ||
143+
primitives[actualType];
138144

139145
if (isUndefined(schema.example)) {
140-
return value || `Unknown Type: ${schema.type}`;
146+
return value || `Unknown Type: ${actualType}`;
141147
}
142148

143149
return schema.example as string;

src/type.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ export type MutuallyExclusiveWithFallback<T, N> = {
1818
type Modify<T, R> = Omit<T, keyof R> & R;
1919

2020
type ICustomBaseSchemaObject = {
21-
type: ISchemaObjectType;
21+
// OpenAPI 3.1 支持 type 为数组,例如 ["string", "null"]
22+
type: ISchemaObjectType | ISchemaObjectType[];
2223
format?: ISchemaObjectFormat;
2324
additionalProperties?: boolean | ISchemaObject;
2425
properties?: {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/* eslint-disable */
2+
// @ts-ignore
3+
import * as API from './types';
4+
5+
export function displayUser(field: keyof API.User) {
6+
return {
7+
idCard: '身份证号',
8+
name: '姓名',
9+
age: '年龄',
10+
isActive: '是否激活',
11+
tags: '标签',
12+
}[field];
13+
}
14+
/* eslint-disable */
15+
// @ts-ignore
16+
import request from 'axios';
17+
18+
import * as API from './types';
19+
20+
/** Get user GET /user */
21+
export function userUsingGet({
22+
options,
23+
}: {
24+
options?: { [key: string]: unknown };
25+
}) {
26+
return request<API.User>('/user', {
27+
method: 'GET',
28+
...(options || {}),
29+
});
30+
}
31+
/* eslint-disable */
32+
// @ts-ignore
33+
export * from './types';
34+
export * from './displayTypeLabel';
35+
36+
export * from './getUser';
37+
/* eslint-disable */
38+
// @ts-ignore
39+
40+
export type User = {
41+
/** 身份证号 */
42+
idCard?: string | null;
43+
/** 姓名 */
44+
name?: string | null;
45+
/** 年龄 */
46+
age?: number | null;
47+
/** 是否激活 */
48+
isActive?: boolean | null;
49+
/** 标签 */
50+
tags?: string[] | null;
51+
};
52+
53+
export type UserUsingGetResponses = {
54+
/**
55+
* Success
56+
*/
57+
200: User;
58+
};

test/common.spec.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -540,4 +540,19 @@ export async function ${api.functionName}(${api.body ? `data: ${api.body.type}`
540540
readGeneratedFiles('./apis/split-types-by-module')
541541
).resolves.toMatchFileSnapshot(getSnapshotDir(ctx));
542542
});
543+
544+
it('测试 OpenAPI 3.1 type 数组格式 (如 ["string", "null"])', async (ctx) => {
545+
await openAPI.generateService({
546+
schemaPath: join(
547+
import.meta.dirname,
548+
'./example-files/openapi-3.1-type-array.json'
549+
),
550+
serversPath: './apis/openapi-3.1-type-array',
551+
isDisplayTypeLabel: true,
552+
});
553+
554+
await expect(
555+
readGeneratedFiles('./apis/openapi-3.1-type-array')
556+
).resolves.toMatchFileSnapshot(getSnapshotDir(ctx));
557+
});
543558
});
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"openapi": "3.1.0",
3+
"info": { "title": "OpenAPI 3.1 Type Array Test", "version": "1.0.0" },
4+
"paths": {
5+
"/user": {
6+
"get": {
7+
"summary": "Get user",
8+
"operationId": "getUser",
9+
"responses": {
10+
"200": {
11+
"description": "Success",
12+
"content": {
13+
"application/json": {
14+
"schema": { "$ref": "#/components/schemas/User" }
15+
}
16+
}
17+
}
18+
}
19+
}
20+
}
21+
},
22+
"components": {
23+
"schemas": {
24+
"User": {
25+
"type": "object",
26+
"properties": {
27+
"idCard": {
28+
"type": ["string", "null"],
29+
"description": "身份证号",
30+
"example": "110101199001011234"
31+
},
32+
"name": { "type": ["string", "null"], "description": "姓名" },
33+
"age": { "type": ["integer", "null"], "description": "年龄" },
34+
"isActive": {
35+
"type": ["boolean", "null"],
36+
"description": "是否激活"
37+
},
38+
"tags": {
39+
"type": ["array", "null"],
40+
"items": { "type": "string" },
41+
"description": "标签"
42+
}
43+
}
44+
}
45+
}
46+
}
47+
}

0 commit comments

Comments
 (0)