Skip to content

Commit acaf836

Browse files
Merge pull request #443 from openapi-ui/main
chore: merge main to release
2 parents b564015 + e56121c commit acaf836

File tree

47 files changed

+10741
-28
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

47 files changed

+10741
-28
lines changed

.changeset/tall-turkeys-rule.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'openapi-ts-request': minor
3+
---
4+
5+
feat: support generate api responses type

openapi-ts-request.config.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
export default [
22
{
33
schemaPath: 'http://petstore.swagger.io/v2/swagger.json',
4-
serversPath: './src/apis/app',
4+
serversPath: './apis/app',
55
},
66
];

pnpm-lock.yaml

Lines changed: 13 additions & 13 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/generator/serviceGenarator.ts

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
entries,
77
filter,
88
find,
9+
findIndex,
910
forEach,
1011
isArray,
1112
isEmpty,
@@ -18,6 +19,7 @@ import {
1819
} from 'lodash';
1920
import { minimatch } from 'minimatch';
2021
import nunjucks from 'nunjucks';
22+
import type { OpenAPIV3 } from 'openapi-types';
2123
import { join } from 'path';
2224
import { rimrafSync } from 'rimraf';
2325

@@ -690,6 +692,21 @@ export default class ServiceGenerator {
690692
response.type = `${this.config.namespace}.${responseName}`;
691693
}
692694

695+
const responsesType = this.getResponsesType(
696+
newApi.responses,
697+
functionName
698+
);
699+
700+
// 如果有多个响应类型,生成对应的类型定义
701+
if (responsesType) {
702+
this.interfaceTPConfigs.push({
703+
typeName: upperFirst(`${functionName}Responses`),
704+
type: responsesType,
705+
isEnum: false,
706+
props: [],
707+
});
708+
}
709+
693710
let formattedPath = newApi.path.replace(
694711
/:([^/]*)|{([^}]*)}/gi,
695712
(_, str, str2) => `$\{${str || str2}}`
@@ -1091,6 +1108,143 @@ export default class ServiceGenerator {
10911108
return responseSchema;
10921109
}
10931110

1111+
/**
1112+
* 生成多状态码响应类型定义
1113+
* 将 OpenAPI 的 responses 对象转换为 TypeScript 类型定义
1114+
* 例如:{ 200: ResponseType, 400: unknown, 404: unknown }
1115+
*
1116+
* @param responses OpenAPI 响应对象
1117+
* @param functionName 函数名称,用于生成主响应类型名称
1118+
* @returns 多状态码响应类型定义字符串,如果没有响应则返回 null
1119+
*/
1120+
private getResponsesType(
1121+
responses: ResponsesObject = {},
1122+
functionName: string
1123+
) {
1124+
if (
1125+
isEmpty(responses) ||
1126+
~findIndex(
1127+
this.interfaceTPConfigs,
1128+
(item) => item.typeName === upperFirst(`${functionName}Responses`)
1129+
)
1130+
) {
1131+
return null;
1132+
}
1133+
1134+
const { components } = this.openAPIData;
1135+
// 生成主响应类型名称
1136+
const mainResponseTypeName = upperFirst(`${functionName}Response`);
1137+
const responseEntries = this.parseResponseEntries(responses, components);
1138+
1139+
const responseTypes = responseEntries.map(
1140+
({ statusCode, type, description = '' }) => {
1141+
// 检查是否已存在对应的主响应类型,如果存在则复用,避免重复定义
1142+
const existType = this.interfaceTPConfigs.find(
1143+
(item) => item.typeName === mainResponseTypeName
1144+
);
1145+
const lastType = existType ? mainResponseTypeName : type;
1146+
1147+
// 格式化描述文本,让描述支持换行
1148+
const formattedDescription = lineBreakReg.test(description)
1149+
? description.split('\n')?.join('\n * ')
1150+
: description;
1151+
1152+
// 生成带注释的类型定义
1153+
return formattedDescription
1154+
? ` /**\n * ${formattedDescription}\n */\n ${statusCode}: ${lastType};`
1155+
: ` ${statusCode}: ${lastType};`;
1156+
}
1157+
);
1158+
1159+
// 返回完整的对象类型定义
1160+
return `{\n${responseTypes.join('\n')}\n}`;
1161+
}
1162+
1163+
/**
1164+
* 解析响应条目,提取每个状态码对应的类型和描述信息
1165+
*
1166+
* @param responses OpenAPI 响应对象
1167+
* @param components OpenAPI 组件对象,用于解析引用类型
1168+
* @returns 响应条目数组,包含状态码、类型和描述
1169+
*/
1170+
private parseResponseEntries(
1171+
responses: ResponsesObject,
1172+
components: OpenAPIV3.ComponentsObject
1173+
) {
1174+
return keys(responses).map((statusCode) => {
1175+
const response = this.resolveRefObject(
1176+
responses[statusCode] as ResponseObject
1177+
);
1178+
1179+
if (!response) {
1180+
return { statusCode, type: 'unknown', description: '' };
1181+
}
1182+
1183+
const responseType = this.getResponseTypeFromContent(
1184+
response,
1185+
components
1186+
);
1187+
const description = response.description || '';
1188+
1189+
return { statusCode, type: responseType, description };
1190+
});
1191+
}
1192+
1193+
/**
1194+
* 从响应内容中提取 TypeScript 类型
1195+
* 处理不同的媒体类型和 schema 类型
1196+
*
1197+
* @param response 响应对象
1198+
* @param components OpenAPI 组件对象
1199+
* @returns TypeScript 类型字符串
1200+
*/
1201+
private getResponseTypeFromContent(
1202+
response: ResponseObject,
1203+
components: OpenAPIV3.ComponentsObject
1204+
): string {
1205+
if (!response.content) {
1206+
return 'unknown';
1207+
}
1208+
1209+
const resContent: ContentObject = response.content;
1210+
const resContentMediaTypes = keys(resContent);
1211+
const mediaType = resContentMediaTypes.includes('application/json')
1212+
? 'application/json'
1213+
: resContentMediaTypes[0];
1214+
1215+
if (!isObject(resContent) || !mediaType) {
1216+
return 'unknown';
1217+
}
1218+
1219+
let schema = (resContent[mediaType].schema ||
1220+
DEFAULT_SCHEMA) as SchemaObject;
1221+
1222+
if (isReferenceObject(schema)) {
1223+
const refName = getLastRefName(schema.$ref);
1224+
const childrenSchema = components.schemas[refName];
1225+
1226+
// 如果配置了 dataFields,尝试从指定字段提取类型
1227+
if (isNonArraySchemaObject(childrenSchema) && this.config.dataFields) {
1228+
schema = (this.config.dataFields
1229+
.map((field) => childrenSchema.properties[field])
1230+
.filter(Boolean)?.[0] ||
1231+
resContent[mediaType].schema ||
1232+
DEFAULT_SCHEMA) as SchemaObject;
1233+
}
1234+
1235+
return this.getType(schema);
1236+
} else if (isSchemaObject(schema)) {
1237+
// 设置属性的 required 状态
1238+
keys(schema.properties).map((fieldName) => {
1239+
schema.properties[fieldName]['required'] =
1240+
schema.required?.includes(fieldName) ?? false;
1241+
});
1242+
return this.getType(schema);
1243+
} else {
1244+
return this.getType(schema);
1245+
}
1246+
}
1247+
10941248
private getParamsTP(
10951249
parameters: (ParameterObject | ReferenceObject)[] = [],
10961250
path: string = null

test/__snapshots__/both/should both excludeTags and excludePaths works while excludePaths has wildcard.snap

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,20 @@ export * from './types';
55
export * from './userZ';
66
/* eslint-disable */
77
// @ts-ignore
8-
export {};
8+
9+
export type UserZz1UsingGetResponses = {
10+
/**
11+
* OK
12+
*/
13+
200: string;
14+
};
15+
16+
export type UserZz1Zz1UsingGetResponses = {
17+
/**
18+
* OK
19+
*/
20+
200: string;
21+
};
922
/* eslint-disable */
1023
// @ts-ignore
1124
import request from '../request';

test/__snapshots__/both/should both excludeTags and excludePaths works with includeTags.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ export * from './types';
55
export * from './userZ';
66
/* eslint-disable */
77
// @ts-ignore
8-
export {};
8+
9+
export type UserZz1UsingGetResponses = {
10+
/**
11+
* OK
12+
*/
13+
200: string;
14+
};
915
/* eslint-disable */
1016
// @ts-ignore
1117
import request from '../request';

test/__snapshots__/both/should exclude items from includePaths and includeTags.snap

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,10 @@ export async function sysAa1UsingGet({
2222
}
2323
/* eslint-disable */
2424
// @ts-ignore
25-
export {};
25+
26+
export type SysAa1UsingGetResponses = {
27+
/**
28+
* OK
29+
*/
30+
200: string;
31+
};

0 commit comments

Comments
 (0)