Skip to content
Merged
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
5 changes: 5 additions & 0 deletions .changeset/sunny-moons-appear.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'openapi-ts-request': patch
---

perf: 添加 OpenAPI 3.1 type 数组支持,支持 type 字段为数组格式(如:["string", "null"])
23 changes: 20 additions & 3 deletions src/generator/serviceGeneratorHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -334,7 +345,13 @@ export function resolveRefObject<T>(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;
Expand Down
14 changes: 14 additions & 0 deletions src/generator/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
}
Expand Down
12 changes: 9 additions & 3 deletions src/parser-mock/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
3 changes: 2 additions & 1 deletion src/type.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ export type MutuallyExclusiveWithFallback<T, N> = {
type Modify<T, R> = Omit<T, keyof R> & R;

type ICustomBaseSchemaObject = {
type: ISchemaObjectType;
// OpenAPI 3.1 支持 type 为数组,例如 ["string", "null"]
type: ISchemaObjectType | ISchemaObjectType[];
format?: ISchemaObjectFormat;
additionalProperties?: boolean | ISchemaObject;
properties?: {
Expand Down
Original file line number Diff line number Diff line change
@@ -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<API.User>('/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;
};
15 changes: 15 additions & 0 deletions test/common.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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));
});
});
47 changes: 47 additions & 0 deletions test/example-files/openapi-3.1-type-array.json
Original file line number Diff line number Diff line change
@@ -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": "标签"
}
}
}
}
}
}