swagger2api-v3 1.1.9 → 1.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -3
- package/dist/.swagger2api.schema.json +6 -2
- package/dist/cli/index.js +22 -21
- package/dist/config/validator.js +94 -9
- package/dist/core/generator.d.ts +83 -11
- package/dist/core/generator.js +295 -94
- package/dist/core/parser.d.ts +20 -16
- package/dist/core/parser.js +72 -103
- package/dist/core/query-parameter-generator.d.ts +26 -0
- package/dist/core/query-parameter-generator.js +114 -0
- package/dist/core/type-generator.d.ts +73 -0
- package/dist/core/type-generator.js +215 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +26 -21
- package/dist/types/index.d.ts +31 -6
- package/dist/utils/comment.d.ts +4 -1
- package/dist/utils/comment.js +6 -2
- package/dist/utils/file.d.ts +10 -3
- package/dist/utils/file.js +49 -19
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/logger.d.ts +115 -0
- package/dist/utils/logger.js +261 -0
- package/dist/utils/naming.js +3 -2
- package/dist/utils/schema.d.ts +79 -0
- package/dist/utils/schema.js +536 -0
- package/dist/utils/type.d.ts +7 -25
- package/dist/utils/type.js +29 -241
- package/package.json +21 -19
package/dist/core/parser.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.SwaggerParser = void 0;
|
|
4
4
|
const utils_1 = require("../utils");
|
|
5
|
+
const type_generator_1 = require("./type-generator");
|
|
5
6
|
const HTTP_METHODS = [
|
|
6
7
|
'get',
|
|
7
8
|
'post',
|
|
@@ -9,12 +10,19 @@ const HTTP_METHODS = [
|
|
|
9
10
|
'delete',
|
|
10
11
|
'patch',
|
|
11
12
|
'head',
|
|
12
|
-
'options'
|
|
13
|
+
'options',
|
|
14
|
+
'trace'
|
|
13
15
|
];
|
|
16
|
+
const MAX_REF_RESOLVE_DEPTH = 20;
|
|
14
17
|
/**
|
|
15
|
-
*
|
|
18
|
+
* OpenAPI 3.0 文档解析器
|
|
16
19
|
*/
|
|
17
20
|
class SwaggerParser {
|
|
21
|
+
/**
|
|
22
|
+
* 创建 OpenAPI 文档解析器
|
|
23
|
+
* @param document OpenAPI 文档
|
|
24
|
+
* @param config 生成配置
|
|
25
|
+
*/
|
|
18
26
|
constructor(document, config) {
|
|
19
27
|
this.document = document;
|
|
20
28
|
this.config = config;
|
|
@@ -33,7 +41,8 @@ class SwaggerParser {
|
|
|
33
41
|
parseApis() {
|
|
34
42
|
const apis = [];
|
|
35
43
|
const paths = this.document.paths;
|
|
36
|
-
for (const [path,
|
|
44
|
+
for (const [path, rawPathItem] of Object.entries(paths)) {
|
|
45
|
+
const pathItem = this.resolveReference(rawPathItem);
|
|
37
46
|
for (const method of HTTP_METHODS) {
|
|
38
47
|
const operation = pathItem[method];
|
|
39
48
|
if (!operation)
|
|
@@ -54,10 +63,7 @@ class SwaggerParser {
|
|
|
54
63
|
*/
|
|
55
64
|
parseOperation(method, path, operation, globalParameters) {
|
|
56
65
|
// 合并全局参数和操作参数
|
|
57
|
-
const allParameters = [
|
|
58
|
-
...(globalParameters || []),
|
|
59
|
-
...(operation.parameters || [])
|
|
60
|
-
].map((parameter) => this.resolveReference(parameter));
|
|
66
|
+
const allParameters = this.mergeParameters([...(globalParameters || []), ...(operation.parameters || [])].map((parameter) => this.resolveReference(parameter)));
|
|
61
67
|
// 处理 OpenAPI 3.0 requestBody
|
|
62
68
|
if (operation.requestBody) {
|
|
63
69
|
const requestBody = this.resolveReference(operation.requestBody);
|
|
@@ -94,15 +100,16 @@ class SwaggerParser {
|
|
|
94
100
|
// 应用前缀忽略规则
|
|
95
101
|
functionName = (0, utils_1.stripMethodNamePrefixes)(functionName, this.config.methodNameIgnorePrefix);
|
|
96
102
|
// 获取响应类型
|
|
97
|
-
const
|
|
103
|
+
const responses = this.resolveResponses(operation.responses);
|
|
104
|
+
const responseType = (0, utils_1.getResponseType)(responses, this.getAllSchemas());
|
|
98
105
|
// 获取请求体类型
|
|
99
106
|
const bodyParam = allParameters.find((p) => p.in === 'body');
|
|
100
107
|
const requestBodyType = bodyParam?.schema
|
|
101
|
-
? (0, utils_1.swaggerTypeToTsType)(bodyParam.schema)
|
|
108
|
+
? (0, utils_1.swaggerTypeToTsType)(bodyParam.schema, this.getAllSchemas(), 'request')
|
|
102
109
|
: undefined;
|
|
103
110
|
// 解析参数信息
|
|
104
111
|
const parameters = allParameters.map((param) => {
|
|
105
|
-
const type = (0, utils_1.swaggerTypeToTsType)((0, utils_1.swaggerParameterToSchema)(param), this.getAllSchemas());
|
|
112
|
+
const type = (0, utils_1.swaggerTypeToTsType)((0, utils_1.swaggerParameterToSchema)(param), this.getAllSchemas(), 'request');
|
|
106
113
|
return {
|
|
107
114
|
name: param.name,
|
|
108
115
|
type,
|
|
@@ -118,6 +125,7 @@ class SwaggerParser {
|
|
|
118
125
|
path,
|
|
119
126
|
description: operation.summary || operation.description,
|
|
120
127
|
tags: operation.tags || [],
|
|
128
|
+
deprecated: operation.deprecated,
|
|
121
129
|
parameters,
|
|
122
130
|
responseType,
|
|
123
131
|
requestBodyType
|
|
@@ -128,116 +136,74 @@ class SwaggerParser {
|
|
|
128
136
|
* @returns 类型信息数组
|
|
129
137
|
*/
|
|
130
138
|
parseTypes() {
|
|
131
|
-
const types = [];
|
|
132
139
|
// Debug log
|
|
133
|
-
|
|
134
|
-
|
|
140
|
+
utils_1.logger.debug('解析类型定义...');
|
|
141
|
+
utils_1.logger.debugKV('components', !!this.document.components);
|
|
135
142
|
if (this.document.components) {
|
|
136
|
-
|
|
143
|
+
utils_1.logger.debugKV('schemas', !!this.document.components.schemas);
|
|
137
144
|
if (this.document.components.schemas) {
|
|
138
|
-
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
// 解析 OpenAPI 3.x components.schemas
|
|
142
|
-
if (this.document.components?.schemas) {
|
|
143
|
-
for (const [name, schema] of Object.entries(this.document.components.schemas)) {
|
|
144
|
-
const sanitizedName = (0, utils_1.sanitizeTypeName)(name); // Use it
|
|
145
|
-
const typeInfo = this.parseTypeDefinition(sanitizedName, schema);
|
|
146
|
-
types.push(typeInfo);
|
|
145
|
+
utils_1.logger.debugKV('schema 列表', Object.keys(this.document.components.schemas).join(', '));
|
|
147
146
|
}
|
|
148
147
|
}
|
|
149
|
-
return
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* 解析单个类型定义
|
|
153
|
-
* @param name 类型名称
|
|
154
|
-
* @param schema 模式对象
|
|
155
|
-
* @returns 类型信息
|
|
156
|
-
*/
|
|
157
|
-
parseTypeDefinition(name, schema) {
|
|
158
|
-
const typeName = (0, utils_1.toPascalCase)(name);
|
|
159
|
-
let definition;
|
|
160
|
-
// 获取所有 schemas 用于类型解析
|
|
161
|
-
const allSchemas = this.getAllSchemas();
|
|
162
|
-
if (schema.type === 'object' && schema.properties) {
|
|
163
|
-
// 对象类型
|
|
164
|
-
const properties = Object.entries(schema.properties)
|
|
165
|
-
.map(([key, value]) => {
|
|
166
|
-
const optional = schema.required?.includes(key) ? '' : '?';
|
|
167
|
-
let type = (0, utils_1.swaggerTypeToTsType)(value, allSchemas);
|
|
168
|
-
if (optional === '?')
|
|
169
|
-
type = (0, utils_1.stripNullFromUnion)(type);
|
|
170
|
-
const comment = value.description
|
|
171
|
-
? ` /** ${value.description} */`
|
|
172
|
-
: '';
|
|
173
|
-
return `${comment}\n ${key}${optional}: ${type};`;
|
|
174
|
-
})
|
|
175
|
-
.join('\n');
|
|
176
|
-
definition = `export interface ${typeName} {\n${properties}\n}`;
|
|
177
|
-
}
|
|
178
|
-
else if (schema.type === 'array') {
|
|
179
|
-
// 数组类型 - 生成指向 items 类型的别名(不带 [])
|
|
180
|
-
// 在引用时会自动添加 []
|
|
181
|
-
const itemType = (0, utils_1.swaggerTypeToTsType)(schema.items || {}, allSchemas);
|
|
182
|
-
definition = `export type ${typeName} = ${itemType};`;
|
|
183
|
-
}
|
|
184
|
-
else if (schema.enum) {
|
|
185
|
-
// 枚举类型
|
|
186
|
-
const enumValues = schema.enum
|
|
187
|
-
.map((value, index) => {
|
|
188
|
-
const key = this.getEnumMemberName(schema, value, index);
|
|
189
|
-
return ` ${key} = '${value}'`;
|
|
190
|
-
})
|
|
191
|
-
.join(',\n');
|
|
192
|
-
definition = `export enum ${typeName} {\n${enumValues}\n}`;
|
|
193
|
-
}
|
|
194
|
-
else {
|
|
195
|
-
// 其他类型
|
|
196
|
-
const type = (0, utils_1.swaggerTypeToTsType)(schema);
|
|
197
|
-
definition = `export type ${typeName} = ${type};`;
|
|
198
|
-
}
|
|
199
|
-
return {
|
|
200
|
-
name: typeName,
|
|
201
|
-
definition,
|
|
202
|
-
description: schema.description
|
|
203
|
-
};
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* 获取枚举成员名称
|
|
207
|
-
* @param schema 枚举 schema
|
|
208
|
-
* @param value 枚举值
|
|
209
|
-
* @param index 枚举值索引
|
|
210
|
-
* @returns 枚举成员名称
|
|
211
|
-
*/
|
|
212
|
-
getEnumMemberName(schema, value, index) {
|
|
213
|
-
const extensionName = schema['x-enum-varnames']?.[index] || schema['x-enumNames']?.[index];
|
|
214
|
-
if (extensionName) {
|
|
215
|
-
return extensionName;
|
|
216
|
-
}
|
|
217
|
-
const strValue = String(value);
|
|
218
|
-
if (/^\d+$/.test(strValue)) {
|
|
219
|
-
return `VALUE_${strValue}`;
|
|
220
|
-
}
|
|
221
|
-
return strValue.toUpperCase();
|
|
148
|
+
return new type_generator_1.OpenAPITypeGenerator(this.getAllSchemas()).generate();
|
|
222
149
|
}
|
|
223
150
|
/**
|
|
224
151
|
* 解析本地 $ref 引用对象
|
|
225
152
|
* @param value 可能带有 $ref 的对象
|
|
153
|
+
* @param seenRefs 已访问的引用
|
|
154
|
+
* @param depth 当前解析深度
|
|
226
155
|
* @returns 引用解析后的对象
|
|
227
156
|
*/
|
|
228
|
-
resolveReference(value) {
|
|
157
|
+
resolveReference(value, seenRefs = new Set(), depth = 0) {
|
|
229
158
|
if (!value || !value.$ref) {
|
|
230
159
|
return value;
|
|
231
160
|
}
|
|
232
|
-
const
|
|
161
|
+
const ref = value.$ref;
|
|
162
|
+
if (!ref.startsWith('#/')) {
|
|
163
|
+
throw new Error(`外部 $ref 需要先通过 loadSwaggerDocument 打包: ${ref}`);
|
|
164
|
+
}
|
|
165
|
+
if (depth >= MAX_REF_RESOLVE_DEPTH) {
|
|
166
|
+
throw new Error(`$ref 解析超过最大深度: ${ref}`);
|
|
167
|
+
}
|
|
168
|
+
if (seenRefs.has(ref)) {
|
|
169
|
+
throw new Error(`检测到循环 $ref 引用: ${ref}`);
|
|
170
|
+
}
|
|
171
|
+
seenRefs.add(ref);
|
|
172
|
+
const refPath = ref
|
|
173
|
+
.replace(/^#\//, '')
|
|
174
|
+
.split('/')
|
|
175
|
+
.map((segment) => decodeURIComponent(segment).replace(/~1/g, '/').replace(/~0/g, '~'));
|
|
233
176
|
let current = this.document;
|
|
234
177
|
for (const segment of refPath) {
|
|
235
178
|
current = current?.[segment];
|
|
236
|
-
if (
|
|
237
|
-
|
|
179
|
+
if (current === undefined) {
|
|
180
|
+
throw new Error(`无法解析 $ref 引用: ${ref}`);
|
|
238
181
|
}
|
|
239
182
|
}
|
|
240
|
-
return current;
|
|
183
|
+
return this.resolveReference(current, seenRefs, depth + 1);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 解析响应对象中的本地引用
|
|
187
|
+
* @param responses OpenAPI 响应集合
|
|
188
|
+
* @returns 已解析的响应集合
|
|
189
|
+
*/
|
|
190
|
+
resolveResponses(responses) {
|
|
191
|
+
return Object.fromEntries(Object.entries(responses).map(([status, response]) => [
|
|
192
|
+
status,
|
|
193
|
+
this.resolveReference(response)
|
|
194
|
+
]));
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* 合并同名同位置参数,后出现的 operation 级参数覆盖 path 级参数
|
|
198
|
+
* @param parameters 参数数组
|
|
199
|
+
* @returns 合并后的参数数组
|
|
200
|
+
*/
|
|
201
|
+
mergeParameters(parameters) {
|
|
202
|
+
const merged = new Map();
|
|
203
|
+
for (const parameter of parameters) {
|
|
204
|
+
merged.set(`${parameter.in}:${parameter.name}`, parameter);
|
|
205
|
+
}
|
|
206
|
+
return Array.from(merged.values());
|
|
241
207
|
}
|
|
242
208
|
/**
|
|
243
209
|
* 按标签分组API
|
|
@@ -248,7 +214,9 @@ class SwaggerParser {
|
|
|
248
214
|
const groupedApis = new Map();
|
|
249
215
|
for (const api of apis) {
|
|
250
216
|
const tags = api.tags.length > 0 ? api.tags : ['default'];
|
|
251
|
-
|
|
217
|
+
const strategy = this.config.multiTagStrategy ?? 'first';
|
|
218
|
+
const groupTags = strategy === 'all' ? [tags.join('-')] : [tags[0]];
|
|
219
|
+
for (const tag of groupTags) {
|
|
252
220
|
if (!groupedApis.has(tag)) {
|
|
253
221
|
groupedApis.set(tag, []);
|
|
254
222
|
}
|
|
@@ -268,7 +236,8 @@ class SwaggerParser {
|
|
|
268
236
|
this.document.tags.forEach((tag) => tags.add(tag.name));
|
|
269
237
|
}
|
|
270
238
|
// 从路径操作中获取
|
|
271
|
-
for (const
|
|
239
|
+
for (const rawPathItem of Object.values(this.document.paths)) {
|
|
240
|
+
const pathItem = this.resolveReference(rawPathItem);
|
|
272
241
|
for (const method of HTTP_METHODS) {
|
|
273
242
|
const operation = pathItem[method];
|
|
274
243
|
if (operation?.tags) {
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { ApiInfo, TypeInfo } from '../types';
|
|
2
|
+
/** API 查询参数类型及其所属接口 */
|
|
3
|
+
export interface QueryParameterTypeInfo extends TypeInfo {
|
|
4
|
+
/** 所属 API */
|
|
5
|
+
api: ApiInfo;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* 获取 API 查询参数类型名称
|
|
9
|
+
* @param api API 接口信息
|
|
10
|
+
* @returns 查询参数类型名称
|
|
11
|
+
*/
|
|
12
|
+
export declare function getQueryParameterTypeName(api: ApiInfo): string;
|
|
13
|
+
/**
|
|
14
|
+
* 创建 API 查询参数类型
|
|
15
|
+
* @param apis API 接口数组
|
|
16
|
+
* @param reservedTypeNames 已占用的类型名称
|
|
17
|
+
* @returns 查询参数类型定义
|
|
18
|
+
*/
|
|
19
|
+
export declare function generateQueryParameterTypes(apis: ApiInfo[], reservedTypeNames?: Iterable<string>): QueryParameterTypeInfo[];
|
|
20
|
+
/**
|
|
21
|
+
* 收集查询参数类型依赖的组件模型
|
|
22
|
+
* @param apis API 接口数组
|
|
23
|
+
* @param modelTypes 组件模型类型
|
|
24
|
+
* @returns 组件模型名称
|
|
25
|
+
*/
|
|
26
|
+
export declare function collectQueryParameterModelTypes(apis: ApiInfo[], modelTypes: TypeInfo[]): string[];
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getQueryParameterTypeName = getQueryParameterTypeName;
|
|
4
|
+
exports.generateQueryParameterTypes = generateQueryParameterTypes;
|
|
5
|
+
exports.collectQueryParameterModelTypes = collectQueryParameterModelTypes;
|
|
6
|
+
const utils_1 = require("../utils");
|
|
7
|
+
const PRIMITIVE_TYPES = new Set([
|
|
8
|
+
'string',
|
|
9
|
+
'number',
|
|
10
|
+
'boolean',
|
|
11
|
+
'object',
|
|
12
|
+
'array',
|
|
13
|
+
'any',
|
|
14
|
+
'blob',
|
|
15
|
+
'void',
|
|
16
|
+
'null',
|
|
17
|
+
'undefined'
|
|
18
|
+
]);
|
|
19
|
+
/**
|
|
20
|
+
* 获取 API 查询参数类型名称
|
|
21
|
+
* @param api API 接口信息
|
|
22
|
+
* @returns 查询参数类型名称
|
|
23
|
+
*/
|
|
24
|
+
function getQueryParameterTypeName(api) {
|
|
25
|
+
return `${(0, utils_1.sanitizeTypeName)(api.name)}Params`;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* 创建 API 查询参数类型
|
|
29
|
+
* @param apis API 接口数组
|
|
30
|
+
* @param reservedTypeNames 已占用的类型名称
|
|
31
|
+
* @returns 查询参数类型定义
|
|
32
|
+
*/
|
|
33
|
+
function generateQueryParameterTypes(apis, reservedTypeNames = []) {
|
|
34
|
+
const parameterTypes = [];
|
|
35
|
+
const usedTypeNames = new Set(reservedTypeNames);
|
|
36
|
+
for (const api of apis) {
|
|
37
|
+
const queryParameters = api.parameters.filter((parameter) => parameter.in === 'query');
|
|
38
|
+
if (queryParameters.length === 0)
|
|
39
|
+
continue;
|
|
40
|
+
const typeName = createAvailableTypeName(getQueryParameterTypeName(api), usedTypeNames);
|
|
41
|
+
usedTypeNames.add(typeName);
|
|
42
|
+
const members = renderQueryParameterMembers(queryParameters);
|
|
43
|
+
parameterTypes.push({
|
|
44
|
+
api,
|
|
45
|
+
name: typeName,
|
|
46
|
+
definition: `export interface ${typeName} {\n${members}\n}`
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return parameterTypes;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* 创建未被占用的类型名称
|
|
53
|
+
* @param baseName 基础类型名称
|
|
54
|
+
* @param usedTypeNames 已占用的类型名称
|
|
55
|
+
* @returns 可用类型名称
|
|
56
|
+
*/
|
|
57
|
+
function createAvailableTypeName(baseName, usedTypeNames) {
|
|
58
|
+
if (!usedTypeNames.has(baseName))
|
|
59
|
+
return baseName;
|
|
60
|
+
let suffix = 1;
|
|
61
|
+
while (usedTypeNames.has(`${baseName}${suffix}`))
|
|
62
|
+
suffix++;
|
|
63
|
+
return `${baseName}${suffix}`;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* 收集查询参数类型依赖的组件模型
|
|
67
|
+
* @param apis API 接口数组
|
|
68
|
+
* @param modelTypes 组件模型类型
|
|
69
|
+
* @returns 组件模型名称
|
|
70
|
+
*/
|
|
71
|
+
function collectQueryParameterModelTypes(apis, modelTypes) {
|
|
72
|
+
const modelTypeNames = new Set(modelTypes.map((type) => type.name));
|
|
73
|
+
const usedTypeNames = new Set();
|
|
74
|
+
for (const api of apis) {
|
|
75
|
+
for (const parameter of api.parameters) {
|
|
76
|
+
if (parameter.in !== 'query')
|
|
77
|
+
continue;
|
|
78
|
+
extractTypeNames(parameter.type).forEach((name) => {
|
|
79
|
+
if (modelTypeNames.has(name))
|
|
80
|
+
usedTypeNames.add(name);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return Array.from(usedTypeNames).sort();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 渲染查询参数类型成员
|
|
88
|
+
* @param parameters 查询参数数组
|
|
89
|
+
* @returns 查询参数类型成员
|
|
90
|
+
*/
|
|
91
|
+
function renderQueryParameterMembers(parameters) {
|
|
92
|
+
return parameters
|
|
93
|
+
.map((parameter) => {
|
|
94
|
+
const optional = parameter.required ? '' : '?';
|
|
95
|
+
const type = optional
|
|
96
|
+
? (0, utils_1.stripNullFromUnion)(parameter.type)
|
|
97
|
+
: parameter.type;
|
|
98
|
+
const comment = parameter.description
|
|
99
|
+
? ` /** ${(0, utils_1.escapeJSDocText)(parameter.description)} */\n`
|
|
100
|
+
: '';
|
|
101
|
+
const member = `${(0, utils_1.formatTsPropertyName)(parameter.name)}${optional}: ${type}`;
|
|
102
|
+
return `${comment} ${member};`;
|
|
103
|
+
})
|
|
104
|
+
.join('\n');
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* 从类型字符串中提取非基础类型名称
|
|
108
|
+
* @param type 类型字符串
|
|
109
|
+
* @returns 类型名称
|
|
110
|
+
*/
|
|
111
|
+
function extractTypeNames(type) {
|
|
112
|
+
const matches = type.match(/[A-Za-z_][A-Za-z0-9_]*/g) || [];
|
|
113
|
+
return Array.from(new Set(matches.filter((name) => !PRIMITIVE_TYPES.has(name.toLowerCase()))));
|
|
114
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { SwaggerSchema, TypeInfo } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* OpenAPI 组件类型生成器
|
|
4
|
+
*/
|
|
5
|
+
export declare class OpenAPITypeGenerator {
|
|
6
|
+
private readonly schemas;
|
|
7
|
+
/**
|
|
8
|
+
* 创建类型生成器
|
|
9
|
+
* @param schemas OpenAPI components.schemas
|
|
10
|
+
*/
|
|
11
|
+
constructor(schemas: Record<string, SwaggerSchema>);
|
|
12
|
+
/**
|
|
13
|
+
* 生成全部组件类型
|
|
14
|
+
* @returns 类型信息数组
|
|
15
|
+
*/
|
|
16
|
+
generate(): TypeInfo[];
|
|
17
|
+
/**
|
|
18
|
+
* 创建单个类型信息
|
|
19
|
+
* @param typeName 输出类型名称
|
|
20
|
+
* @param schema OpenAPI Schema
|
|
21
|
+
* @param usage Schema 使用场景
|
|
22
|
+
* @returns 类型信息
|
|
23
|
+
*/
|
|
24
|
+
private createTypeInfo;
|
|
25
|
+
/**
|
|
26
|
+
* 创建类型定义
|
|
27
|
+
* @param typeName 输出类型名称
|
|
28
|
+
* @param schema OpenAPI Schema
|
|
29
|
+
* @param usage Schema 使用场景
|
|
30
|
+
* @returns TypeScript 类型定义
|
|
31
|
+
*/
|
|
32
|
+
private createDefinition;
|
|
33
|
+
/**
|
|
34
|
+
* 判断 Schema 是否可以生成 TypeScript enum
|
|
35
|
+
* @param schema OpenAPI Schema
|
|
36
|
+
* @returns 是否可以生成 enum
|
|
37
|
+
*/
|
|
38
|
+
private canGenerateEnum;
|
|
39
|
+
/**
|
|
40
|
+
* 创建枚举定义
|
|
41
|
+
* @param typeName 枚举名称
|
|
42
|
+
* @param schema 枚举 Schema
|
|
43
|
+
* @returns TypeScript 枚举定义
|
|
44
|
+
*/
|
|
45
|
+
private createEnumDefinition;
|
|
46
|
+
/**
|
|
47
|
+
* 创建合法且唯一的枚举成员名称
|
|
48
|
+
* @param schema 枚举 Schema
|
|
49
|
+
* @param value 枚举值
|
|
50
|
+
* @param index 枚举索引
|
|
51
|
+
* @param usedNames 已使用名称
|
|
52
|
+
* @returns 枚举成员名称
|
|
53
|
+
*/
|
|
54
|
+
private createEnumMemberName;
|
|
55
|
+
/**
|
|
56
|
+
* 从枚举值创建默认成员名称
|
|
57
|
+
* @param value 枚举值
|
|
58
|
+
* @param index 枚举索引
|
|
59
|
+
* @returns 默认成员名称
|
|
60
|
+
*/
|
|
61
|
+
private createEnumValueName;
|
|
62
|
+
/**
|
|
63
|
+
* 校验基础类型和请求/响应变体不会重名
|
|
64
|
+
*/
|
|
65
|
+
private validateGeneratedTypeNames;
|
|
66
|
+
/**
|
|
67
|
+
* 注册生成类型名称并检测冲突
|
|
68
|
+
* @param owners 类型名称归属映射
|
|
69
|
+
* @param typeName 生成类型名称
|
|
70
|
+
* @param owner 原始 Schema 描述
|
|
71
|
+
*/
|
|
72
|
+
private registerTypeName;
|
|
73
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OpenAPITypeGenerator = void 0;
|
|
4
|
+
const schema_1 = require("../utils/schema");
|
|
5
|
+
const naming_1 = require("../utils/naming");
|
|
6
|
+
const RESERVED_IDENTIFIERS = new Set([
|
|
7
|
+
'break',
|
|
8
|
+
'case',
|
|
9
|
+
'catch',
|
|
10
|
+
'class',
|
|
11
|
+
'const',
|
|
12
|
+
'continue',
|
|
13
|
+
'debugger',
|
|
14
|
+
'default',
|
|
15
|
+
'delete',
|
|
16
|
+
'do',
|
|
17
|
+
'else',
|
|
18
|
+
'enum',
|
|
19
|
+
'export',
|
|
20
|
+
'extends',
|
|
21
|
+
'false',
|
|
22
|
+
'finally',
|
|
23
|
+
'for',
|
|
24
|
+
'function',
|
|
25
|
+
'if',
|
|
26
|
+
'import',
|
|
27
|
+
'in',
|
|
28
|
+
'instanceof',
|
|
29
|
+
'new',
|
|
30
|
+
'null',
|
|
31
|
+
'return',
|
|
32
|
+
'super',
|
|
33
|
+
'switch',
|
|
34
|
+
'this',
|
|
35
|
+
'throw',
|
|
36
|
+
'true',
|
|
37
|
+
'try',
|
|
38
|
+
'typeof',
|
|
39
|
+
'var',
|
|
40
|
+
'void',
|
|
41
|
+
'while',
|
|
42
|
+
'with',
|
|
43
|
+
'yield'
|
|
44
|
+
]);
|
|
45
|
+
/**
|
|
46
|
+
* OpenAPI 组件类型生成器
|
|
47
|
+
*/
|
|
48
|
+
class OpenAPITypeGenerator {
|
|
49
|
+
/**
|
|
50
|
+
* 创建类型生成器
|
|
51
|
+
* @param schemas OpenAPI components.schemas
|
|
52
|
+
*/
|
|
53
|
+
constructor(schemas) {
|
|
54
|
+
this.schemas = schemas;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 生成全部组件类型
|
|
58
|
+
* @returns 类型信息数组
|
|
59
|
+
*/
|
|
60
|
+
generate() {
|
|
61
|
+
this.validateGeneratedTypeNames();
|
|
62
|
+
const types = [];
|
|
63
|
+
for (const [schemaName, schema] of Object.entries(this.schemas)) {
|
|
64
|
+
const typeName = (0, naming_1.sanitizeTypeName)(schemaName);
|
|
65
|
+
types.push(this.createTypeInfo(typeName, schema, 'neutral'));
|
|
66
|
+
if ((0, schema_1.schemaNeedsUsageVariant)(schema, this.schemas, 'request')) {
|
|
67
|
+
types.push(this.createTypeInfo(`${typeName}Input`, schema, 'request'));
|
|
68
|
+
}
|
|
69
|
+
if ((0, schema_1.schemaNeedsUsageVariant)(schema, this.schemas, 'response')) {
|
|
70
|
+
types.push(this.createTypeInfo(`${typeName}Output`, schema, 'response'));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return types;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* 创建单个类型信息
|
|
77
|
+
* @param typeName 输出类型名称
|
|
78
|
+
* @param schema OpenAPI Schema
|
|
79
|
+
* @param usage Schema 使用场景
|
|
80
|
+
* @returns 类型信息
|
|
81
|
+
*/
|
|
82
|
+
createTypeInfo(typeName, schema, usage) {
|
|
83
|
+
return {
|
|
84
|
+
name: typeName,
|
|
85
|
+
definition: this.createDefinition(typeName, schema, usage),
|
|
86
|
+
description: schema.description
|
|
87
|
+
? (0, schema_1.escapeJSDocText)(schema.description)
|
|
88
|
+
: undefined
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* 创建类型定义
|
|
93
|
+
* @param typeName 输出类型名称
|
|
94
|
+
* @param schema OpenAPI Schema
|
|
95
|
+
* @param usage Schema 使用场景
|
|
96
|
+
* @returns TypeScript 类型定义
|
|
97
|
+
*/
|
|
98
|
+
createDefinition(typeName, schema, usage) {
|
|
99
|
+
if (schema.enum && this.canGenerateEnum(schema)) {
|
|
100
|
+
return this.createEnumDefinition(typeName, schema);
|
|
101
|
+
}
|
|
102
|
+
if ((0, schema_1.isObjectSchema)(schema) &&
|
|
103
|
+
!schema.allOf &&
|
|
104
|
+
!schema.oneOf &&
|
|
105
|
+
!schema.anyOf &&
|
|
106
|
+
!schema.$ref &&
|
|
107
|
+
schema.nullable !== true &&
|
|
108
|
+
(0, schema_1.getVisibleSchemaProperties)(schema, usage).length > 0) {
|
|
109
|
+
const isGeneric = (0, schema_1.isGenericResponseSchema)(schema);
|
|
110
|
+
const genericDeclaration = isGeneric ? '<T = Record<string, any>>' : '';
|
|
111
|
+
const members = (0, schema_1.renderObjectMembers)(schema, this.schemas, usage, isGeneric ? { data: 'T' } : {});
|
|
112
|
+
return `export interface ${typeName}${genericDeclaration} {\n${members}\n}`;
|
|
113
|
+
}
|
|
114
|
+
const type = (0, schema_1.swaggerTypeToTsType)(schema, this.schemas, usage);
|
|
115
|
+
return `export type ${typeName} = ${type};`;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 判断 Schema 是否可以生成 TypeScript enum
|
|
119
|
+
* @param schema OpenAPI Schema
|
|
120
|
+
* @returns 是否可以生成 enum
|
|
121
|
+
*/
|
|
122
|
+
canGenerateEnum(schema) {
|
|
123
|
+
return (schema.nullable !== true &&
|
|
124
|
+
!!schema.enum?.length &&
|
|
125
|
+
schema.enum.every((value) => typeof value === 'string' || typeof value === 'number'));
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* 创建枚举定义
|
|
129
|
+
* @param typeName 枚举名称
|
|
130
|
+
* @param schema 枚举 Schema
|
|
131
|
+
* @returns TypeScript 枚举定义
|
|
132
|
+
*/
|
|
133
|
+
createEnumDefinition(typeName, schema) {
|
|
134
|
+
const usedNames = new Set();
|
|
135
|
+
const members = (schema.enum || []).map((value, index) => {
|
|
136
|
+
const memberName = this.createEnumMemberName(schema, value, index, usedNames);
|
|
137
|
+
return ` ${memberName} = ${JSON.stringify(value)}`;
|
|
138
|
+
});
|
|
139
|
+
return `export enum ${typeName} {\n${members.join(',\n')}\n}`;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* 创建合法且唯一的枚举成员名称
|
|
143
|
+
* @param schema 枚举 Schema
|
|
144
|
+
* @param value 枚举值
|
|
145
|
+
* @param index 枚举索引
|
|
146
|
+
* @param usedNames 已使用名称
|
|
147
|
+
* @returns 枚举成员名称
|
|
148
|
+
*/
|
|
149
|
+
createEnumMemberName(schema, value, index, usedNames) {
|
|
150
|
+
const extensionName = schema['x-enum-varnames']?.[index] || schema['x-enumNames']?.[index];
|
|
151
|
+
const sourceName = extensionName || this.createEnumValueName(value, index);
|
|
152
|
+
let memberName = sourceName.replace(/[^A-Za-z0-9_$]+/g, '_');
|
|
153
|
+
if (!memberName)
|
|
154
|
+
memberName = `VALUE_${index}`;
|
|
155
|
+
if (/^\d/.test(memberName))
|
|
156
|
+
memberName = `VALUE_${memberName}`;
|
|
157
|
+
if (RESERVED_IDENTIFIERS.has(memberName))
|
|
158
|
+
memberName = `_${memberName}`;
|
|
159
|
+
if (!(0, schema_1.isValidIdentifier)(memberName))
|
|
160
|
+
memberName = `VALUE_${index}`;
|
|
161
|
+
const baseName = memberName;
|
|
162
|
+
let suffix = 2;
|
|
163
|
+
while (usedNames.has(memberName)) {
|
|
164
|
+
memberName = `${baseName}_${suffix}`;
|
|
165
|
+
suffix++;
|
|
166
|
+
}
|
|
167
|
+
usedNames.add(memberName);
|
|
168
|
+
return memberName;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* 从枚举值创建默认成员名称
|
|
172
|
+
* @param value 枚举值
|
|
173
|
+
* @param index 枚举索引
|
|
174
|
+
* @returns 默认成员名称
|
|
175
|
+
*/
|
|
176
|
+
createEnumValueName(value, index) {
|
|
177
|
+
if (typeof value === 'number') {
|
|
178
|
+
return `VALUE_${String(value)
|
|
179
|
+
.replace(/^-/, 'NEGATIVE_')
|
|
180
|
+
.replace(/\./g, '_')}`;
|
|
181
|
+
}
|
|
182
|
+
const text = String(value).trim();
|
|
183
|
+
return text ? text.toUpperCase() : `VALUE_${index}`;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* 校验基础类型和请求/响应变体不会重名
|
|
187
|
+
*/
|
|
188
|
+
validateGeneratedTypeNames() {
|
|
189
|
+
const owners = new Map();
|
|
190
|
+
for (const [schemaName, schema] of Object.entries(this.schemas)) {
|
|
191
|
+
const typeName = (0, naming_1.sanitizeTypeName)(schemaName);
|
|
192
|
+
this.registerTypeName(owners, typeName, schemaName);
|
|
193
|
+
if ((0, schema_1.schemaNeedsUsageVariant)(schema, this.schemas, 'request')) {
|
|
194
|
+
this.registerTypeName(owners, `${typeName}Input`, `${schemaName} 的请求类型`);
|
|
195
|
+
}
|
|
196
|
+
if ((0, schema_1.schemaNeedsUsageVariant)(schema, this.schemas, 'response')) {
|
|
197
|
+
this.registerTypeName(owners, `${typeName}Output`, `${schemaName} 的响应类型`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* 注册生成类型名称并检测冲突
|
|
203
|
+
* @param owners 类型名称归属映射
|
|
204
|
+
* @param typeName 生成类型名称
|
|
205
|
+
* @param owner 原始 Schema 描述
|
|
206
|
+
*/
|
|
207
|
+
registerTypeName(owners, typeName, owner) {
|
|
208
|
+
const existingOwner = owners.get(typeName);
|
|
209
|
+
if (existingOwner && existingOwner !== owner) {
|
|
210
|
+
throw new Error(`Schema 类型名称冲突: ${existingOwner} 和 ${owner} 都会生成 ${typeName}`);
|
|
211
|
+
}
|
|
212
|
+
owners.set(typeName, owner);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
exports.OpenAPITypeGenerator = OpenAPITypeGenerator;
|