diff --git a/.changeset/sunny-moons-appear.md b/.changeset/sunny-moons-appear.md new file mode 100644 index 0000000..049865a --- /dev/null +++ b/.changeset/sunny-moons-appear.md @@ -0,0 +1,5 @@ +--- +'openapi-ts-request': patch +--- + +perf: 添加 OpenAPI 3.1 type 数组支持,支持 type 字段为数组格式(如:["string", "null"]) diff --git a/src/generator/serviceGeneratorHelper.ts b/src/generator/serviceGeneratorHelper.ts index 386cc2f..b0161ce 100644 --- a/src/generator/serviceGeneratorHelper.ts +++ b/src/generator/serviceGeneratorHelper.ts @@ -112,7 +112,18 @@ export function resolveEnumObject(params: { let enumStr = ''; let enumLabelTypeStr = ''; - if (numberEnum.includes(schemaObject.type) || isAllNumber(enumArray)) { + // 获取实际的类型(处理 OpenAPI 3.1 的 type 数组情况) + const getActualType = (type: typeof schemaObject.type): string => { + if (Array.isArray(type)) { + // 如果是数组,返回第一个非 null 类型 + return type.find((t) => t !== 'null') || 'string'; + } + return type; + }; + + const actualType = getActualType(schemaObject.type); + + if (numberEnum.includes(actualType) || isAllNumber(enumArray)) { if (config.isSupportParseEnumDesc && schemaObject.description) { const enumMap = parseDescriptionEnum(schemaObject.description); enumStr = `{${map(enumArray, (value) => { @@ -151,7 +162,7 @@ export function resolveEnumObject(params: { return `${value}:"${enumLabel}"`; }).join(',')}}`; } else { - if (numberEnum.includes(schemaObject.type) || isAllNumber(enumArray)) { + if (numberEnum.includes(actualType) || isAllNumber(enumArray)) { if ( (config.isSupportParseEnumDesc || config.supportParseEnumDescByReg) && schemaObject.description @@ -334,7 +345,13 @@ export function resolveRefObject(params: { resolvedType = (refResolved as { type?: string })?.type; } else { const schemaObj: SchemaObject = schema; - resolvedType = schemaObj.type; + // 处理 OpenAPI 3.1 的 type 数组情况 + if (Array.isArray(schemaObj.type)) { + // 如果是数组,使用第一个非 null 类型 + resolvedType = schemaObj.type.find((t) => t !== 'null') || 'string'; + } else { + resolvedType = schemaObj.type; + } } const finalSchema = schema as SchemaObject; diff --git a/src/generator/util.ts b/src/generator/util.ts index 9db87b2..f0bc9c5 100644 --- a/src/generator/util.ts +++ b/src/generator/util.ts @@ -144,6 +144,20 @@ export function getDefaultType( const dateEnum = ['Date', 'date', 'dateTime', 'date-time', 'datetime']; const stringEnum = ['string', 'email', 'password', 'url', 'byte', 'binary']; + // OpenAPI 3.1 支持 type 为数组,例如 ["string", "null"] + if (Array.isArray(type)) { + return type + .map((t) => { + // 为数组中的每个类型创建一个临时的 schemaObject + const tempSchema: ISchemaObject = { + ...schemaObject, + type: t, + }; + return getDefaultType(tempSchema, namespace, schemas); + }) + .join(' | '); + } + if (type === 'null') { return 'null'; } diff --git a/src/parser-mock/index.ts b/src/parser-mock/index.ts index 32ad4f7..f0be344 100644 --- a/src/parser-mock/index.ts +++ b/src/parser-mock/index.ts @@ -132,12 +132,18 @@ function primitive( ) { const schema = objectify(schemaParams); const { type, format } = schema; + + // 处理 OpenAPI 3.1 的 type 数组情况 + const actualType = Array.isArray(type) + ? type.find((t) => t !== 'null') || 'string' + : type; + const value = - primitives[`${type}_${format || getDateByName(propsName)}`] || - primitives[type]; + primitives[`${actualType}_${format || getDateByName(propsName)}`] || + primitives[actualType]; if (isUndefined(schema.example)) { - return value || `Unknown Type: ${schema.type}`; + return value || `Unknown Type: ${actualType}`; } return schema.example as string; diff --git a/src/type.ts b/src/type.ts index 0084561..d3d1a87 100644 --- a/src/type.ts +++ b/src/type.ts @@ -18,7 +18,8 @@ export type MutuallyExclusiveWithFallback = { type Modify = Omit & R; type ICustomBaseSchemaObject = { - type: ISchemaObjectType; + // OpenAPI 3.1 支持 type 为数组,例如 ["string", "null"] + type: ISchemaObjectType | ISchemaObjectType[]; format?: ISchemaObjectFormat; additionalProperties?: boolean | ISchemaObject; properties?: { diff --git "a/test/__snapshots__/common/\346\265\213\350\257\225 OpenAPI 3.1 type \346\225\260\347\273\204\346\240\274\345\274\217 (\345\246\202 [_string_, _null_]).snap" "b/test/__snapshots__/common/\346\265\213\350\257\225 OpenAPI 3.1 type \346\225\260\347\273\204\346\240\274\345\274\217 (\345\246\202 [_string_, _null_]).snap" new file mode 100644 index 0000000..86bb4f8 --- /dev/null +++ "b/test/__snapshots__/common/\346\265\213\350\257\225 OpenAPI 3.1 type \346\225\260\347\273\204\346\240\274\345\274\217 (\345\246\202 [_string_, _null_]).snap" @@ -0,0 +1,58 @@ +/* eslint-disable */ +// @ts-ignore +import * as API from './types'; + +export function displayUser(field: keyof API.User) { + return { + idCard: '身份证号', + name: '姓名', + age: '年龄', + isActive: '是否激活', + tags: '标签', + }[field]; +} +/* eslint-disable */ +// @ts-ignore +import request from 'axios'; + +import * as API from './types'; + +/** Get user GET /user */ +export function userUsingGet({ + options, +}: { + options?: { [key: string]: unknown }; +}) { + return request('/user', { + method: 'GET', + ...(options || {}), + }); +} +/* eslint-disable */ +// @ts-ignore +export * from './types'; +export * from './displayTypeLabel'; + +export * from './getUser'; +/* eslint-disable */ +// @ts-ignore + +export type User = { + /** 身份证号 */ + idCard?: string | null; + /** 姓名 */ + name?: string | null; + /** 年龄 */ + age?: number | null; + /** 是否激活 */ + isActive?: boolean | null; + /** 标签 */ + tags?: string[] | null; +}; + +export type UserUsingGetResponses = { + /** + * Success + */ + 200: User; +}; diff --git a/test/common.spec.ts b/test/common.spec.ts index 0c93dec..93822fb 100644 --- a/test/common.spec.ts +++ b/test/common.spec.ts @@ -540,4 +540,19 @@ export async function ${api.functionName}(${api.body ? `data: ${api.body.type}` readGeneratedFiles('./apis/split-types-by-module') ).resolves.toMatchFileSnapshot(getSnapshotDir(ctx)); }); + + it('测试 OpenAPI 3.1 type 数组格式 (如 ["string", "null"])', async (ctx) => { + await openAPI.generateService({ + schemaPath: join( + import.meta.dirname, + './example-files/openapi-3.1-type-array.json' + ), + serversPath: './apis/openapi-3.1-type-array', + isDisplayTypeLabel: true, + }); + + await expect( + readGeneratedFiles('./apis/openapi-3.1-type-array') + ).resolves.toMatchFileSnapshot(getSnapshotDir(ctx)); + }); }); diff --git a/test/example-files/openapi-3.1-type-array.json b/test/example-files/openapi-3.1-type-array.json new file mode 100644 index 0000000..8a1f834 --- /dev/null +++ b/test/example-files/openapi-3.1-type-array.json @@ -0,0 +1,47 @@ +{ + "openapi": "3.1.0", + "info": { "title": "OpenAPI 3.1 Type Array Test", "version": "1.0.0" }, + "paths": { + "/user": { + "get": { + "summary": "Get user", + "operationId": "getUser", + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { "$ref": "#/components/schemas/User" } + } + } + } + } + } + } + }, + "components": { + "schemas": { + "User": { + "type": "object", + "properties": { + "idCard": { + "type": ["string", "null"], + "description": "身份证号", + "example": "110101199001011234" + }, + "name": { "type": ["string", "null"], "description": "姓名" }, + "age": { "type": ["integer", "null"], "description": "年龄" }, + "isActive": { + "type": ["boolean", "null"], + "description": "是否激活" + }, + "tags": { + "type": ["array", "null"], + "items": { "type": "string" }, + "description": "标签" + } + } + } + } + } +}