@trapi/swagger 1.3.0 → 2.0.0-beta.1
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 +146 -6
- package/dist/index.d.mts +825 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +1308 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +20 -14
- package/dist/config/index.d.ts +0 -3
- package/dist/config/index.d.ts.map +0 -1
- package/dist/config/index.js +0 -25
- package/dist/config/index.js.map +0 -1
- package/dist/config/type.d.ts +0 -83
- package/dist/config/type.d.ts.map +0 -1
- package/dist/config/type.js +0 -9
- package/dist/config/type.js.map +0 -1
- package/dist/config/utils.d.ts +0 -3
- package/dist/config/utils.d.ts.map +0 -1
- package/dist/config/utils.js +0 -53
- package/dist/config/utils.js.map +0 -1
- package/dist/constants.d.ts +0 -15
- package/dist/constants.d.ts.map +0 -1
- package/dist/constants.js +0 -27
- package/dist/constants.js.map +0 -1
- package/dist/generator/abstract.d.ts +0 -35
- package/dist/generator/abstract.d.ts.map +0 -1
- package/dist/generator/abstract.js +0 -254
- package/dist/generator/abstract.js.map +0 -1
- package/dist/generator/index.d.ts +0 -5
- package/dist/generator/index.d.ts.map +0 -1
- package/dist/generator/index.js +0 -27
- package/dist/generator/index.js.map +0 -1
- package/dist/generator/module.d.ts +0 -14
- package/dist/generator/module.d.ts.map +0 -1
- package/dist/generator/module.js +0 -34
- package/dist/generator/module.js.map +0 -1
- package/dist/generator/v2/index.d.ts +0 -2
- package/dist/generator/v2/index.d.ts.map +0 -1
- package/dist/generator/v2/index.js +0 -24
- package/dist/generator/v2/index.js.map +0 -1
- package/dist/generator/v2/module.d.ts +0 -25
- package/dist/generator/v2/module.d.ts.map +0 -1
- package/dist/generator/v2/module.js +0 -517
- package/dist/generator/v2/module.js.map +0 -1
- package/dist/generator/v3/index.d.ts +0 -2
- package/dist/generator/v3/index.d.ts.map +0 -1
- package/dist/generator/v3/index.js +0 -24
- package/dist/generator/v3/index.js.map +0 -1
- package/dist/generator/v3/module.d.ts +0 -30
- package/dist/generator/v3/module.d.ts.map +0 -1
- package/dist/generator/v3/module.js +0 -500
- package/dist/generator/v3/module.js.map +0 -1
- package/dist/index.d.ts +0 -8
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -30
- package/dist/index.js.map +0 -1
- package/dist/metadata.d.ts +0 -4
- package/dist/metadata.d.ts.map +0 -1
- package/dist/metadata.js +0 -14
- package/dist/metadata.js.map +0 -1
- package/dist/schema/constants.d.ts +0 -28
- package/dist/schema/constants.d.ts.map +0 -1
- package/dist/schema/constants.js +0 -40
- package/dist/schema/constants.js.map +0 -1
- package/dist/schema/index.d.ts +0 -5
- package/dist/schema/index.d.ts.map +0 -1
- package/dist/schema/index.js +0 -27
- package/dist/schema/index.js.map +0 -1
- package/dist/schema/type.d.ts +0 -139
- package/dist/schema/type.d.ts.map +0 -1
- package/dist/schema/type.js +0 -9
- package/dist/schema/type.js.map +0 -1
- package/dist/schema/v2/constants.d.ts +0 -8
- package/dist/schema/v2/constants.d.ts.map +0 -1
- package/dist/schema/v2/constants.js +0 -18
- package/dist/schema/v2/constants.js.map +0 -1
- package/dist/schema/v2/index.d.ts +0 -3
- package/dist/schema/v2/index.d.ts.map +0 -1
- package/dist/schema/v2/index.js +0 -25
- package/dist/schema/v2/index.js.map +0 -1
- package/dist/schema/v2/type.d.ts +0 -116
- package/dist/schema/v2/type.d.ts.map +0 -1
- package/dist/schema/v2/type.js +0 -9
- package/dist/schema/v2/type.js.map +0 -1
- package/dist/schema/v3/constants.d.ts +0 -7
- package/dist/schema/v3/constants.d.ts.map +0 -1
- package/dist/schema/v3/constants.js +0 -17
- package/dist/schema/v3/constants.js.map +0 -1
- package/dist/schema/v3/index.d.ts +0 -3
- package/dist/schema/v3/index.d.ts.map +0 -1
- package/dist/schema/v3/index.js +0 -25
- package/dist/schema/v3/index.js.map +0 -1
- package/dist/schema/v3/type.d.ts +0 -159
- package/dist/schema/v3/type.d.ts.map +0 -1
- package/dist/schema/v3/type.js +0 -9
- package/dist/schema/v3/type.js.map +0 -1
- package/dist/type.d.ts +0 -49
- package/dist/type.d.ts.map +0 -1
- package/dist/type.js +0 -9
- package/dist/type.js.map +0 -1
- package/dist/utils/character.d.ts +0 -3
- package/dist/utils/character.d.ts.map +0 -1
- package/dist/utils/character.js +0 -20
- package/dist/utils/character.js.map +0 -1
- package/dist/utils/index.d.ts +0 -5
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -27
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/object.d.ts +0 -2
- package/dist/utils/object.d.ts.map +0 -1
- package/dist/utils/object.js +0 -14
- package/dist/utils/object.js.map +0 -1
- package/dist/utils/path.d.ts +0 -2
- package/dist/utils/path.d.ts.map +0 -1
- package/dist/utils/path.js +0 -20
- package/dist/utils/path.js.map +0 -1
- package/dist/utils/value.d.ts +0 -2
- package/dist/utils/value.d.ts.map +0 -1
- package/dist/utils/value.js +0 -25
- package/dist/utils/value.js.map +0 -1
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1308 @@
|
|
|
1
|
+
import { BaseError } from "@ebec/core";
|
|
2
|
+
import { ParameterSource, TypeName, generateMetadata, isAnyType, isArrayType, isBinaryType, isEnumType, isIntersectionType, isMetadata, isNestedObjectLiteralType, isNeverType, isPrimitiveType, isRefAliasType, isRefEnumType, isRefObjectType, isReferenceType, isTupleType, isUndefinedType, isUnionType, isVoidType } from "@trapi/metadata";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { URL } from "node:url";
|
|
5
|
+
import { isObject, merge } from "smob";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import process from "node:process";
|
|
8
|
+
import YAML from "yamljs";
|
|
9
|
+
//#region src/core/config/utils.ts
|
|
10
|
+
function buildSpecGeneratorOptions(input) {
|
|
11
|
+
const servers = [];
|
|
12
|
+
if (input.servers) if (Array.isArray(input.servers)) for (let i = 0; i < input.servers.length; i++) {
|
|
13
|
+
const server = input.servers[i];
|
|
14
|
+
if (typeof server === "string") servers.push({ url: server });
|
|
15
|
+
else servers.push(server);
|
|
16
|
+
}
|
|
17
|
+
else if (typeof input.servers === "string") servers.push({ url: input.servers });
|
|
18
|
+
else servers.push(input.servers);
|
|
19
|
+
return {
|
|
20
|
+
...input,
|
|
21
|
+
servers
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region src/core/constants.ts
|
|
26
|
+
const Version = {
|
|
27
|
+
V2: "v2",
|
|
28
|
+
V3: "v3",
|
|
29
|
+
V3_1: "v3.1",
|
|
30
|
+
V3_2: "v3.2"
|
|
31
|
+
};
|
|
32
|
+
const DocumentFormat = {
|
|
33
|
+
YAML: "yaml",
|
|
34
|
+
JSON: "json"
|
|
35
|
+
};
|
|
36
|
+
const SecurityType = {
|
|
37
|
+
API_KEY: "apiKey",
|
|
38
|
+
BASIC: "basic",
|
|
39
|
+
HTTP: "http",
|
|
40
|
+
OAUTH2: "oauth2"
|
|
41
|
+
};
|
|
42
|
+
//#endregion
|
|
43
|
+
//#region src/core/error/codes.ts
|
|
44
|
+
const SwaggerErrorCode = {
|
|
45
|
+
SPEC_NOT_BUILT: "SWAGGER_SPEC_NOT_BUILT",
|
|
46
|
+
ENUM_UNSUPPORTED_TYPE: "SWAGGER_ENUM_UNSUPPORTED_TYPE",
|
|
47
|
+
BODY_PARAMETER_DUPLICATE: "SWAGGER_BODY_PARAMETER_DUPLICATE",
|
|
48
|
+
BODY_FORM_CONFLICT: "SWAGGER_BODY_FORM_CONFLICT",
|
|
49
|
+
PARAMETER_SOURCE_UNSUPPORTED: "SWAGGER_PARAMETER_SOURCE_UNSUPPORTED"
|
|
50
|
+
};
|
|
51
|
+
//#endregion
|
|
52
|
+
//#region src/core/error/module.ts
|
|
53
|
+
var SwaggerError = class extends BaseError {};
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/core/schema/v2/constants.ts
|
|
56
|
+
const ParameterSourceV2 = {
|
|
57
|
+
BODY: "body",
|
|
58
|
+
FORM_DATA: "formData",
|
|
59
|
+
HEADER: "header",
|
|
60
|
+
PATH: "path",
|
|
61
|
+
QUERY: "query"
|
|
62
|
+
};
|
|
63
|
+
//#endregion
|
|
64
|
+
//#region src/core/schema/v3/constants.ts
|
|
65
|
+
const ParameterSourceV3 = {
|
|
66
|
+
COOKIE: "cookie",
|
|
67
|
+
HEADER: "header",
|
|
68
|
+
PATH: "path",
|
|
69
|
+
QUERY: "query"
|
|
70
|
+
};
|
|
71
|
+
//#endregion
|
|
72
|
+
//#region src/core/schema/constants.ts
|
|
73
|
+
const TransferProtocol = {
|
|
74
|
+
HTTP: "http",
|
|
75
|
+
HTTPS: "https",
|
|
76
|
+
WS: "ws",
|
|
77
|
+
WSS: "wss"
|
|
78
|
+
};
|
|
79
|
+
const DataFormatName = {
|
|
80
|
+
INT_32: "int32",
|
|
81
|
+
INT_64: "int64",
|
|
82
|
+
FLOAT: "float",
|
|
83
|
+
DOUBLE: "double",
|
|
84
|
+
BYTE: "byte",
|
|
85
|
+
BINARY: "binary",
|
|
86
|
+
DATE: "date",
|
|
87
|
+
DATE_TIME: "date-time",
|
|
88
|
+
PASSWORD: "password"
|
|
89
|
+
};
|
|
90
|
+
const DataTypeName = {
|
|
91
|
+
VOID: "void",
|
|
92
|
+
INTEGER: "integer",
|
|
93
|
+
NUMBER: "number",
|
|
94
|
+
BOOLEAN: "boolean",
|
|
95
|
+
STRING: "string",
|
|
96
|
+
ARRAY: "array",
|
|
97
|
+
OBJECT: "object",
|
|
98
|
+
FILE: "file"
|
|
99
|
+
};
|
|
100
|
+
//#endregion
|
|
101
|
+
//#region src/core/utils/character.ts
|
|
102
|
+
function removeDuplicateSlashes(str) {
|
|
103
|
+
return str.replace("/([^:]$)/+/g", "$1");
|
|
104
|
+
}
|
|
105
|
+
function removeFinalCharacter(str, character) {
|
|
106
|
+
while (str.charAt(str.length - 1) === character && str.length > 0) str = str.slice(0, -1);
|
|
107
|
+
return str;
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/core/utils/path.ts
|
|
111
|
+
function normalizePathParameters(str) {
|
|
112
|
+
str = str.replace(/<:([^/]+)>/g, "{$1}");
|
|
113
|
+
str = str.replace(/:([^/]+)/g, "{$1}");
|
|
114
|
+
str = str.replace(/<([^/]+)>/g, "{$1}");
|
|
115
|
+
return str;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
//#region src/core/utils/object.ts
|
|
119
|
+
function hasOwnProperty(obj, prop) {
|
|
120
|
+
return Object.prototype.hasOwnProperty.call(obj, prop);
|
|
121
|
+
}
|
|
122
|
+
//#endregion
|
|
123
|
+
//#region src/core/utils/value.ts
|
|
124
|
+
function transformValueTo(type, value) {
|
|
125
|
+
if (value === null) return null;
|
|
126
|
+
switch (type) {
|
|
127
|
+
case "integer":
|
|
128
|
+
case "number": return Number(value);
|
|
129
|
+
case "boolean": return !!value;
|
|
130
|
+
default: return String(value);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
//#endregion
|
|
134
|
+
//#region src/adapters/generator/abstract.ts
|
|
135
|
+
var AbstractSpecGenerator = class {
|
|
136
|
+
spec;
|
|
137
|
+
metadata;
|
|
138
|
+
config;
|
|
139
|
+
constructor(metadata, config) {
|
|
140
|
+
this.metadata = metadata;
|
|
141
|
+
this.config = buildSpecGeneratorOptions(config);
|
|
142
|
+
}
|
|
143
|
+
buildInfo() {
|
|
144
|
+
const info = {
|
|
145
|
+
title: this.config.name || "Documentation",
|
|
146
|
+
version: this.config.version || "1.0.0"
|
|
147
|
+
};
|
|
148
|
+
if (this.config.description) info.description = this.config.description;
|
|
149
|
+
if (this.config.license) info.license = { name: this.config.license };
|
|
150
|
+
return info;
|
|
151
|
+
}
|
|
152
|
+
buildTags() {
|
|
153
|
+
const tagMap = /* @__PURE__ */ new Map();
|
|
154
|
+
for (const controller of this.metadata.controllers) {
|
|
155
|
+
if (controller.hidden) continue;
|
|
156
|
+
const extensions = controller.extensions ?? [];
|
|
157
|
+
if (extensions.length === 0) continue;
|
|
158
|
+
const tagNames = controller.tags.length > 0 ? controller.tags : [controller.name];
|
|
159
|
+
const extensionFields = this.transformExtensions(extensions);
|
|
160
|
+
for (const tagName of tagNames) {
|
|
161
|
+
let entry = tagMap.get(tagName);
|
|
162
|
+
if (!entry) {
|
|
163
|
+
entry = { name: tagName };
|
|
164
|
+
tagMap.set(tagName, entry);
|
|
165
|
+
}
|
|
166
|
+
Object.assign(entry, extensionFields);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return Array.from(tagMap.values());
|
|
170
|
+
}
|
|
171
|
+
getSchemaForType(type) {
|
|
172
|
+
if (isVoidType(type) || isUndefinedType(type) || isNeverType(type)) return {};
|
|
173
|
+
if (isReferenceType(type)) return this.getSchemaForReferenceType(type);
|
|
174
|
+
if (isPrimitiveType(type)) return this.getSchemaForPrimitiveType(type);
|
|
175
|
+
if (isArrayType(type)) return this.getSchemaForArrayType(type);
|
|
176
|
+
if (isTupleType(type)) return this.getSchemaForTupleType(type);
|
|
177
|
+
if (isEnumType(type)) return this.getSchemaForEnumType(type);
|
|
178
|
+
if (isUnionType(type)) return this.getSchemaForUnionType(type);
|
|
179
|
+
if (isIntersectionType(type)) return this.getSchemaForIntersectionType(type);
|
|
180
|
+
if (isNestedObjectLiteralType(type)) return this.getSchemaForObjectLiteralType(type);
|
|
181
|
+
return {};
|
|
182
|
+
}
|
|
183
|
+
getSchemaForEnumType(enumType) {
|
|
184
|
+
const type = this.decideEnumType(enumType.members);
|
|
185
|
+
const nullable = !!enumType.members.includes(null);
|
|
186
|
+
const schema = {
|
|
187
|
+
type,
|
|
188
|
+
enum: enumType.members.map((member) => transformValueTo(type, member))
|
|
189
|
+
};
|
|
190
|
+
this.applyNullable(schema, nullable);
|
|
191
|
+
return schema;
|
|
192
|
+
}
|
|
193
|
+
getSchemaForPrimitiveType(type) {
|
|
194
|
+
return {
|
|
195
|
+
[TypeName.ANY]: { additionalProperties: true },
|
|
196
|
+
[TypeName.BINARY]: {
|
|
197
|
+
type: DataTypeName.STRING,
|
|
198
|
+
format: DataFormatName.BINARY
|
|
199
|
+
},
|
|
200
|
+
[TypeName.BOOLEAN]: { type: DataTypeName.BOOLEAN },
|
|
201
|
+
[TypeName.BUFFER]: {
|
|
202
|
+
type: DataTypeName.STRING,
|
|
203
|
+
format: DataFormatName.BYTE
|
|
204
|
+
},
|
|
205
|
+
[TypeName.BYTE]: {
|
|
206
|
+
type: DataTypeName.STRING,
|
|
207
|
+
format: DataFormatName.BYTE
|
|
208
|
+
},
|
|
209
|
+
[TypeName.DATE]: {
|
|
210
|
+
type: DataTypeName.STRING,
|
|
211
|
+
format: DataFormatName.DATE
|
|
212
|
+
},
|
|
213
|
+
[TypeName.DATETIME]: {
|
|
214
|
+
type: DataTypeName.STRING,
|
|
215
|
+
format: DataFormatName.DATE_TIME
|
|
216
|
+
},
|
|
217
|
+
[TypeName.DOUBLE]: {
|
|
218
|
+
type: DataTypeName.NUMBER,
|
|
219
|
+
format: DataFormatName.DOUBLE
|
|
220
|
+
},
|
|
221
|
+
[TypeName.FILE]: {
|
|
222
|
+
type: DataTypeName.STRING,
|
|
223
|
+
format: DataFormatName.BINARY
|
|
224
|
+
},
|
|
225
|
+
[TypeName.FLOAT]: {
|
|
226
|
+
type: DataTypeName.NUMBER,
|
|
227
|
+
format: DataFormatName.FLOAT
|
|
228
|
+
},
|
|
229
|
+
[TypeName.BIGINT]: { type: DataTypeName.INTEGER },
|
|
230
|
+
[TypeName.INTEGER]: {
|
|
231
|
+
type: DataTypeName.INTEGER,
|
|
232
|
+
format: DataFormatName.INT_32
|
|
233
|
+
},
|
|
234
|
+
[TypeName.LONG]: {
|
|
235
|
+
type: DataTypeName.INTEGER,
|
|
236
|
+
format: DataFormatName.INT_64
|
|
237
|
+
},
|
|
238
|
+
[TypeName.OBJECT]: {
|
|
239
|
+
type: DataTypeName.OBJECT,
|
|
240
|
+
additionalProperties: true
|
|
241
|
+
},
|
|
242
|
+
[TypeName.STRING]: { type: DataTypeName.STRING },
|
|
243
|
+
[TypeName.UNDEFINED]: {}
|
|
244
|
+
}[type.typeName] || { type: DataTypeName.OBJECT };
|
|
245
|
+
}
|
|
246
|
+
getSchemaForArrayType(arrayType) {
|
|
247
|
+
return {
|
|
248
|
+
type: DataTypeName.ARRAY,
|
|
249
|
+
items: this.getSchemaForType(arrayType.elementType)
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
getSchemaForTupleType(tupleType) {
|
|
253
|
+
if (tupleType.elements.length === 0) return {
|
|
254
|
+
type: DataTypeName.ARRAY,
|
|
255
|
+
items: {}
|
|
256
|
+
};
|
|
257
|
+
const elementSchemas = tupleType.elements.map((el) => this.getSchemaForType(el.type));
|
|
258
|
+
if (elementSchemas.length === 1) return {
|
|
259
|
+
type: DataTypeName.ARRAY,
|
|
260
|
+
items: elementSchemas[0]
|
|
261
|
+
};
|
|
262
|
+
return {
|
|
263
|
+
type: DataTypeName.ARRAY,
|
|
264
|
+
items: { anyOf: elementSchemas }
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
getSchemaForObjectLiteralType(objectLiteral) {
|
|
268
|
+
const properties = this.buildProperties(objectLiteral.properties);
|
|
269
|
+
const additionalProperties = objectLiteral.additionalProperties && this.getSchemaForType(objectLiteral.additionalProperties);
|
|
270
|
+
const required = objectLiteral.properties.filter((prop) => prop.required && !this.isUndefinedProperty(prop)).map((prop) => prop.name);
|
|
271
|
+
return {
|
|
272
|
+
properties,
|
|
273
|
+
...additionalProperties && { additionalProperties },
|
|
274
|
+
...required && required.length && { required },
|
|
275
|
+
type: DataTypeName.OBJECT
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
getSchemaForReferenceType(referenceType) {
|
|
279
|
+
return { $ref: `${this.getRefPrefix()}${referenceType.refName}` };
|
|
280
|
+
}
|
|
281
|
+
buildSchemaForRefAlias(referenceType) {
|
|
282
|
+
const swaggerType = this.getSchemaForType(referenceType.type);
|
|
283
|
+
const format = referenceType.format;
|
|
284
|
+
return {
|
|
285
|
+
...swaggerType,
|
|
286
|
+
default: referenceType.default ?? swaggerType.default,
|
|
287
|
+
example: referenceType.example ?? swaggerType.example,
|
|
288
|
+
format: format ?? swaggerType.format,
|
|
289
|
+
description: referenceType.description ?? swaggerType.description,
|
|
290
|
+
...this.transformValidators(referenceType.validators)
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
buildSchemaForRefEnum(referenceType) {
|
|
294
|
+
const output = {
|
|
295
|
+
...this.getSchemaForEnumType({
|
|
296
|
+
typeName: TypeName.ENUM,
|
|
297
|
+
members: referenceType.members
|
|
298
|
+
}),
|
|
299
|
+
description: referenceType.description
|
|
300
|
+
};
|
|
301
|
+
if (typeof referenceType.memberNames !== "undefined" && referenceType.members.length === referenceType.memberNames.length) output["x-enum-varnames"] = referenceType.memberNames;
|
|
302
|
+
return output;
|
|
303
|
+
}
|
|
304
|
+
buildSchemasForReferenceTypes(extendFn) {
|
|
305
|
+
const output = {};
|
|
306
|
+
const keys = Object.keys(this.metadata.referenceTypes);
|
|
307
|
+
for (const key of keys) {
|
|
308
|
+
const referenceType = this.metadata.referenceTypes[key];
|
|
309
|
+
switch (referenceType.typeName) {
|
|
310
|
+
case TypeName.REF_ALIAS:
|
|
311
|
+
output[referenceType.refName] = this.buildSchemaForRefAlias(referenceType);
|
|
312
|
+
break;
|
|
313
|
+
case TypeName.REF_ENUM:
|
|
314
|
+
output[referenceType.refName] = this.buildSchemaForRefEnum(referenceType);
|
|
315
|
+
break;
|
|
316
|
+
case TypeName.REF_OBJECT:
|
|
317
|
+
output[referenceType.refName] = this.buildSchemaForRefObject(referenceType);
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
if (typeof extendFn === "function") extendFn(output[referenceType.refName], referenceType);
|
|
321
|
+
}
|
|
322
|
+
return output;
|
|
323
|
+
}
|
|
324
|
+
isUndefinedProperty(input) {
|
|
325
|
+
return isUndefinedType(input.type) || isUnionType(input.type) && input.type.members.some((el) => isUndefinedType(el));
|
|
326
|
+
}
|
|
327
|
+
buildProperties(properties) {
|
|
328
|
+
const output = {};
|
|
329
|
+
properties.forEach((property) => {
|
|
330
|
+
const swaggerType = this.getSchemaForType(property.type);
|
|
331
|
+
if (swaggerType.$ref && this.shouldStripRefSiblings()) {
|
|
332
|
+
output[property.name] = { $ref: swaggerType.$ref };
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
swaggerType.description = property.description;
|
|
336
|
+
swaggerType.example = property.example;
|
|
337
|
+
swaggerType.format = property.format || swaggerType.format;
|
|
338
|
+
this.assignPropertyDefaults(swaggerType, property);
|
|
339
|
+
if (property.deprecated) this.markPropertyDeprecated(swaggerType);
|
|
340
|
+
const extensions = this.transformExtensions(property.extensions);
|
|
341
|
+
const validators = this.transformValidators(property.validators);
|
|
342
|
+
output[property.name] = {
|
|
343
|
+
...swaggerType,
|
|
344
|
+
...validators,
|
|
345
|
+
...extensions
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
return output;
|
|
349
|
+
}
|
|
350
|
+
shouldStripRefSiblings() {
|
|
351
|
+
return true;
|
|
352
|
+
}
|
|
353
|
+
assignPropertyDefaults(schema, property) {}
|
|
354
|
+
buildSchemaForRefObject(referenceType) {
|
|
355
|
+
const required = referenceType.properties.filter((p) => p.required && !this.isUndefinedProperty(p)).map((p) => p.name);
|
|
356
|
+
const output = {
|
|
357
|
+
description: referenceType.description,
|
|
358
|
+
properties: this.buildProperties(referenceType.properties),
|
|
359
|
+
required: required && required.length > 0 ? Array.from(new Set(required)) : void 0,
|
|
360
|
+
type: DataTypeName.OBJECT
|
|
361
|
+
};
|
|
362
|
+
if (referenceType.additionalProperties) output.additionalProperties = this.resolveAdditionalProperties(referenceType.additionalProperties);
|
|
363
|
+
if (referenceType.example !== void 0) output.example = referenceType.example;
|
|
364
|
+
return output;
|
|
365
|
+
}
|
|
366
|
+
determineTypesUsedInEnum(anEnum) {
|
|
367
|
+
const set = /* @__PURE__ */ new Set();
|
|
368
|
+
for (const element of anEnum) {
|
|
369
|
+
if (element === null) continue;
|
|
370
|
+
set.add(typeof element);
|
|
371
|
+
}
|
|
372
|
+
return Array.from(set);
|
|
373
|
+
}
|
|
374
|
+
decideEnumType(input) {
|
|
375
|
+
const types = this.determineTypesUsedInEnum(input);
|
|
376
|
+
if (types.length === 1) {
|
|
377
|
+
const value = types[0];
|
|
378
|
+
if (value === "string" || value === "number" || value === "boolean") return value;
|
|
379
|
+
throw new SwaggerError({
|
|
380
|
+
message: `Enum contains unsupported type '${types[0] || "unknown"}'. Only string, number, and boolean values are allowed.`,
|
|
381
|
+
code: SwaggerErrorCode.ENUM_UNSUPPORTED_TYPE
|
|
382
|
+
});
|
|
383
|
+
}
|
|
384
|
+
const unsupportedTypes = types.filter((type) => type !== "string" && type !== "number" && type !== "boolean");
|
|
385
|
+
if (unsupportedTypes.length > 0) throw new SwaggerError({
|
|
386
|
+
message: `Enum contains unsupported types: ${unsupportedTypes.join(", ")}. Only string, number, and boolean values are allowed.`,
|
|
387
|
+
code: SwaggerErrorCode.ENUM_UNSUPPORTED_TYPE
|
|
388
|
+
});
|
|
389
|
+
return "string";
|
|
390
|
+
}
|
|
391
|
+
getOperationId(name) {
|
|
392
|
+
return name.charAt(0).toUpperCase() + name.substring(1);
|
|
393
|
+
}
|
|
394
|
+
groupParameters(items) {
|
|
395
|
+
const output = {};
|
|
396
|
+
for (const item of items) {
|
|
397
|
+
if (typeof output[item.in] === "undefined") output[item.in] = [];
|
|
398
|
+
output[item.in].push(item);
|
|
399
|
+
}
|
|
400
|
+
return output;
|
|
401
|
+
}
|
|
402
|
+
transformExtensions(input) {
|
|
403
|
+
if (!input) return {};
|
|
404
|
+
const output = {};
|
|
405
|
+
for (const extension of input) {
|
|
406
|
+
const key = extension.key.startsWith("x-") ? extension.key : `x-${extension.key}`;
|
|
407
|
+
output[key] = extension.value;
|
|
408
|
+
}
|
|
409
|
+
return output;
|
|
410
|
+
}
|
|
411
|
+
transformValidators(input) {
|
|
412
|
+
if (!isObject(input)) return {};
|
|
413
|
+
const output = {};
|
|
414
|
+
for (const [name, validator] of Object.entries(input)) {
|
|
415
|
+
const mapping = validator.meta?.openApi ?? DEFAULT_VALIDATOR_OPENAPI_MAPPINGS[name];
|
|
416
|
+
if (!mapping || mapping.kind === "ignore") continue;
|
|
417
|
+
if (mapping.kind === "keyword") output[mapping.key] = validator.value;
|
|
418
|
+
else if (mapping.kind === "format") output.format = mapping.format;
|
|
419
|
+
}
|
|
420
|
+
return output;
|
|
421
|
+
}
|
|
422
|
+
};
|
|
423
|
+
const DEFAULT_VALIDATOR_OPENAPI_MAPPINGS = {
|
|
424
|
+
maxLength: {
|
|
425
|
+
kind: "keyword",
|
|
426
|
+
key: "maxLength"
|
|
427
|
+
},
|
|
428
|
+
minLength: {
|
|
429
|
+
kind: "keyword",
|
|
430
|
+
key: "minLength"
|
|
431
|
+
},
|
|
432
|
+
maximum: {
|
|
433
|
+
kind: "keyword",
|
|
434
|
+
key: "maximum"
|
|
435
|
+
},
|
|
436
|
+
minimum: {
|
|
437
|
+
kind: "keyword",
|
|
438
|
+
key: "minimum"
|
|
439
|
+
},
|
|
440
|
+
pattern: {
|
|
441
|
+
kind: "keyword",
|
|
442
|
+
key: "pattern"
|
|
443
|
+
},
|
|
444
|
+
maxItems: {
|
|
445
|
+
kind: "keyword",
|
|
446
|
+
key: "maxItems"
|
|
447
|
+
},
|
|
448
|
+
minItems: {
|
|
449
|
+
kind: "keyword",
|
|
450
|
+
key: "minItems"
|
|
451
|
+
},
|
|
452
|
+
uniqueItems: {
|
|
453
|
+
kind: "keyword",
|
|
454
|
+
key: "uniqueItems"
|
|
455
|
+
}
|
|
456
|
+
};
|
|
457
|
+
//#endregion
|
|
458
|
+
//#region src/adapters/generator/v2/module.ts
|
|
459
|
+
function uniqueOperationId$1(base, used) {
|
|
460
|
+
if (!used.has(base)) {
|
|
461
|
+
used.add(base);
|
|
462
|
+
return base;
|
|
463
|
+
}
|
|
464
|
+
let counter = 2;
|
|
465
|
+
while (used.has(`${base}_${counter}`)) counter += 1;
|
|
466
|
+
const candidate = `${base}_${counter}`;
|
|
467
|
+
used.add(candidate);
|
|
468
|
+
return candidate;
|
|
469
|
+
}
|
|
470
|
+
var V2Generator = class V2Generator extends AbstractSpecGenerator {
|
|
471
|
+
async build() {
|
|
472
|
+
if (typeof this.spec !== "undefined") return this.spec;
|
|
473
|
+
let spec = {
|
|
474
|
+
definitions: this.buildSchemasForReferenceTypes(),
|
|
475
|
+
info: this.buildInfo(),
|
|
476
|
+
paths: this.buildPaths(),
|
|
477
|
+
swagger: "2.0"
|
|
478
|
+
};
|
|
479
|
+
spec.securityDefinitions = this.config.securityDefinitions ? V2Generator.translateSecurityDefinitions(this.config.securityDefinitions) : {};
|
|
480
|
+
if (this.config.consumes) spec.consumes = this.config.consumes;
|
|
481
|
+
if (this.config.produces) spec.produces = this.config.produces;
|
|
482
|
+
if (this.config.servers && this.config.servers.length > 0) {
|
|
483
|
+
const url = new URL(this.config.servers[0].url, "http://localhost:3000/");
|
|
484
|
+
spec.host = url.host;
|
|
485
|
+
if (url.pathname) spec.basePath = url.pathname;
|
|
486
|
+
}
|
|
487
|
+
const tags = this.buildTags();
|
|
488
|
+
if (tags.length > 0) spec.tags = tags;
|
|
489
|
+
if (this.config.specificationExtra) spec = merge(spec, this.config.specificationExtra);
|
|
490
|
+
this.spec = spec;
|
|
491
|
+
return spec;
|
|
492
|
+
}
|
|
493
|
+
static translateSecurityDefinitions(securityDefinitions) {
|
|
494
|
+
const definitions = {};
|
|
495
|
+
const keys = Object.keys(securityDefinitions);
|
|
496
|
+
for (const key of keys) {
|
|
497
|
+
const securityDefinition = securityDefinitions[key];
|
|
498
|
+
switch (securityDefinition.type) {
|
|
499
|
+
case "http":
|
|
500
|
+
if (securityDefinition.scheme === "basic") definitions[key] = { type: "basic" };
|
|
501
|
+
break;
|
|
502
|
+
case "apiKey":
|
|
503
|
+
definitions[key] = securityDefinition;
|
|
504
|
+
break;
|
|
505
|
+
case "oauth2":
|
|
506
|
+
if (securityDefinition.flows.implicit) definitions[`${key}Implicit`] = {
|
|
507
|
+
type: "oauth2",
|
|
508
|
+
flow: "implicit",
|
|
509
|
+
authorizationUrl: securityDefinition.flows.implicit.authorizationUrl,
|
|
510
|
+
scopes: securityDefinition.flows.implicit.scopes
|
|
511
|
+
};
|
|
512
|
+
if (securityDefinition.flows.password) definitions[`${key}Password`] = {
|
|
513
|
+
type: "oauth2",
|
|
514
|
+
flow: "password",
|
|
515
|
+
tokenUrl: securityDefinition.flows.password.tokenUrl,
|
|
516
|
+
scopes: securityDefinition.flows.password.scopes
|
|
517
|
+
};
|
|
518
|
+
if (securityDefinition.flows.authorizationCode) definitions[`${key}AccessCode`] = {
|
|
519
|
+
type: "oauth2",
|
|
520
|
+
flow: "accessCode",
|
|
521
|
+
tokenUrl: securityDefinition.flows.authorizationCode.tokenUrl,
|
|
522
|
+
authorizationUrl: securityDefinition.flows.authorizationCode.authorizationUrl,
|
|
523
|
+
scopes: securityDefinition.flows.authorizationCode.scopes
|
|
524
|
+
};
|
|
525
|
+
if (securityDefinition.flows.clientCredentials) definitions[`${key}Application`] = {
|
|
526
|
+
type: "oauth2",
|
|
527
|
+
flow: "application",
|
|
528
|
+
tokenUrl: securityDefinition.flows.clientCredentials.tokenUrl,
|
|
529
|
+
scopes: securityDefinition.flows.clientCredentials.scopes
|
|
530
|
+
};
|
|
531
|
+
break;
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
return definitions;
|
|
535
|
+
}
|
|
536
|
+
resolveAdditionalProperties(type) {
|
|
537
|
+
return true;
|
|
538
|
+
}
|
|
539
|
+
markPropertyDeprecated(schema) {
|
|
540
|
+
schema["x-deprecated"] = true;
|
|
541
|
+
}
|
|
542
|
+
buildPaths() {
|
|
543
|
+
const output = {};
|
|
544
|
+
const usedOperationIds = /* @__PURE__ */ new Set();
|
|
545
|
+
const unique = (input) => [...new Set(input)];
|
|
546
|
+
this.metadata.controllers.forEach((controller) => {
|
|
547
|
+
if (controller.hidden) return;
|
|
548
|
+
const controllerPaths = controller.paths.length === 0 ? [""] : controller.paths;
|
|
549
|
+
controller.methods.forEach((method) => {
|
|
550
|
+
if (method.hidden) return;
|
|
551
|
+
method.consumes = unique([...controller.consumes, ...method.consumes]);
|
|
552
|
+
method.produces = unique([...controller.produces, ...method.produces]);
|
|
553
|
+
method.tags = unique([...controller.tags, ...method.tags]);
|
|
554
|
+
method.security = method.security || controller.security;
|
|
555
|
+
method.deprecated = method.deprecated || controller.deprecated;
|
|
556
|
+
method.responses = unique([...controller.responses, ...method.responses]);
|
|
557
|
+
for (const controllerPath of controllerPaths) {
|
|
558
|
+
let fullPath = path.posix.join("/", controllerPath, method.path);
|
|
559
|
+
fullPath = normalizePathParameters(fullPath);
|
|
560
|
+
output[fullPath] = output[fullPath] || {};
|
|
561
|
+
output[fullPath][method.method] = this.buildMethod(method, fullPath, usedOperationIds);
|
|
562
|
+
}
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
return output;
|
|
566
|
+
}
|
|
567
|
+
buildMethod(method, emittedPath, usedOperationIds) {
|
|
568
|
+
const output = this.buildOperation(method);
|
|
569
|
+
output.consumes = this.buildMethodConsumes(method);
|
|
570
|
+
output.operationId = uniqueOperationId$1(method.operationId || output.operationId, usedOperationIds);
|
|
571
|
+
output.description = method.description;
|
|
572
|
+
if (method.summary) output.summary = method.summary;
|
|
573
|
+
if (method.deprecated) output.deprecated = method.deprecated;
|
|
574
|
+
if (method.tags.length) output.tags = method.tags;
|
|
575
|
+
if (method.security) output.security = method.security;
|
|
576
|
+
const parameters = this.groupParameters(method.parameters);
|
|
577
|
+
output.parameters = [
|
|
578
|
+
...(parameters[ParameterSource.PATH] || []).filter((p) => emittedPath.includes(`{${p.name}}`)),
|
|
579
|
+
...parameters[ParameterSource.QUERY_PROP] || [],
|
|
580
|
+
...parameters[ParameterSource.HEADER] || [],
|
|
581
|
+
...parameters[ParameterSource.FORM_DATA] || []
|
|
582
|
+
].map((p) => this.buildParameter(p));
|
|
583
|
+
const bodyParameters = parameters[ParameterSource.BODY] || [];
|
|
584
|
+
if (bodyParameters.length > 1) throw new SwaggerError({
|
|
585
|
+
message: `Only one body parameter allowed per method, but ${bodyParameters.length} found in '${method.name}'.`,
|
|
586
|
+
code: SwaggerErrorCode.BODY_PARAMETER_DUPLICATE
|
|
587
|
+
});
|
|
588
|
+
const bodyParameter = bodyParameters.length > 0 ? this.buildParameter(bodyParameters[0]) : void 0;
|
|
589
|
+
const bodyPropParams = parameters[ParameterSource.BODY_PROP] || [];
|
|
590
|
+
if (bodyPropParams.length > 0) {
|
|
591
|
+
const schema = {
|
|
592
|
+
type: DataTypeName.OBJECT,
|
|
593
|
+
title: "Body",
|
|
594
|
+
properties: {}
|
|
595
|
+
};
|
|
596
|
+
const required = [];
|
|
597
|
+
for (const bodyPropParam of bodyPropParams) {
|
|
598
|
+
const bodyProp = this.getSchemaForType(bodyPropParam.type);
|
|
599
|
+
bodyProp.default = bodyPropParam.default;
|
|
600
|
+
bodyProp.description = bodyPropParam.description;
|
|
601
|
+
bodyProp.example = bodyPropParam.examples;
|
|
602
|
+
if (bodyProp.required) required.push(bodyPropParam.name);
|
|
603
|
+
schema.properties[bodyPropParam.name] = bodyProp;
|
|
604
|
+
}
|
|
605
|
+
if (bodyParameter && bodyParameter.in === ParameterSourceV2.BODY) {
|
|
606
|
+
if (bodyParameter.schema.type === DataTypeName.OBJECT) {
|
|
607
|
+
bodyParameter.schema.properties = {
|
|
608
|
+
...bodyParameter.schema.properties || {},
|
|
609
|
+
...schema.properties
|
|
610
|
+
};
|
|
611
|
+
bodyParameter.schema.required = [...bodyParameter.schema.required || [], ...required];
|
|
612
|
+
} else bodyParameter.schema = schema;
|
|
613
|
+
output.parameters.push(bodyParameter);
|
|
614
|
+
} else {
|
|
615
|
+
const parameter = {
|
|
616
|
+
in: ParameterSourceV2.BODY,
|
|
617
|
+
name: "body",
|
|
618
|
+
schema
|
|
619
|
+
};
|
|
620
|
+
if (required.length) parameter.schema.required = required;
|
|
621
|
+
output.parameters.push(parameter);
|
|
622
|
+
}
|
|
623
|
+
} else if (bodyParameter) output.parameters.push(bodyParameter);
|
|
624
|
+
Object.assign(output, this.transformExtensions(method.extensions));
|
|
625
|
+
return output;
|
|
626
|
+
}
|
|
627
|
+
transformParameterSource(source) {
|
|
628
|
+
if (source === ParameterSource.BODY) return ParameterSourceV2.BODY;
|
|
629
|
+
if (source === ParameterSource.FORM_DATA) return ParameterSourceV2.FORM_DATA;
|
|
630
|
+
if (source === ParameterSource.HEADER) return ParameterSourceV2.HEADER;
|
|
631
|
+
if (source === ParameterSource.PATH) return ParameterSourceV2.PATH;
|
|
632
|
+
if (source === ParameterSource.QUERY || source === ParameterSource.QUERY_PROP) return ParameterSourceV2.QUERY;
|
|
633
|
+
}
|
|
634
|
+
buildParameter(input) {
|
|
635
|
+
const sourceIn = this.transformParameterSource(input.in);
|
|
636
|
+
if (!sourceIn) throw new SwaggerError({
|
|
637
|
+
message: `The parameter source '${input.in}' for parameter '${input.name}' is not supported in OpenAPI 2.0.`,
|
|
638
|
+
code: SwaggerErrorCode.PARAMETER_SOURCE_UNSUPPORTED
|
|
639
|
+
});
|
|
640
|
+
const parameter = {
|
|
641
|
+
description: input.description,
|
|
642
|
+
in: sourceIn,
|
|
643
|
+
name: input.name,
|
|
644
|
+
required: input.required
|
|
645
|
+
};
|
|
646
|
+
Object.assign(parameter, this.transformExtensions(input.extensions));
|
|
647
|
+
if (input.in !== ParameterSource.BODY && isRefEnumType(input.type)) input.type = {
|
|
648
|
+
typeName: TypeName.ENUM,
|
|
649
|
+
members: input.type.members
|
|
650
|
+
};
|
|
651
|
+
if (parameter.in === ParameterSourceV2.FORM_DATA && input.type.typeName === TypeName.FILE) {
|
|
652
|
+
parameter.type = "file";
|
|
653
|
+
Object.assign(parameter, this.transformValidators(input.validators));
|
|
654
|
+
return parameter;
|
|
655
|
+
}
|
|
656
|
+
const parameterType = this.getSchemaForType(input.type);
|
|
657
|
+
if (parameter.in !== ParameterSourceV2.BODY && parameterType.format) parameter.format = parameterType.format;
|
|
658
|
+
if ((parameter.in === ParameterSourceV2.FORM_DATA || parameter.in === ParameterSourceV2.QUERY) && (input.type.typeName === TypeName.ARRAY || parameterType.type === DataTypeName.ARRAY)) parameter.collectionFormat = input.collectionFormat || this.config.collectionFormat || "multi";
|
|
659
|
+
if (parameter.in === ParameterSourceV2.BODY) {
|
|
660
|
+
if (input.type.typeName === TypeName.ARRAY || parameterType.type === DataTypeName.ARRAY) parameter.schema = {
|
|
661
|
+
items: parameterType.items,
|
|
662
|
+
type: DataTypeName.ARRAY
|
|
663
|
+
};
|
|
664
|
+
else if (input.type.typeName === TypeName.ANY) parameter.schema = { type: DataTypeName.OBJECT };
|
|
665
|
+
else parameter.schema = parameterType;
|
|
666
|
+
parameter.schema = {
|
|
667
|
+
...parameter.schema,
|
|
668
|
+
...this.transformValidators(input.validators)
|
|
669
|
+
};
|
|
670
|
+
return parameter;
|
|
671
|
+
}
|
|
672
|
+
Object.assign(parameter, this.transformValidators(input.validators));
|
|
673
|
+
if (input.type.typeName === TypeName.ANY) parameter.type = DataTypeName.STRING;
|
|
674
|
+
else if (parameterType.type && !Array.isArray(parameterType.type)) parameter.type = parameterType.type;
|
|
675
|
+
if (parameterType.items) parameter.items = parameterType.items;
|
|
676
|
+
if (parameterType.enum) parameter.enum = parameterType.enum;
|
|
677
|
+
if (typeof input.default !== "undefined") parameter.default = input.default;
|
|
678
|
+
return parameter;
|
|
679
|
+
}
|
|
680
|
+
buildMethodConsumes(method) {
|
|
681
|
+
if (method.consumes && method.consumes.length > 0) return method.consumes;
|
|
682
|
+
if (this.hasFileParams(method)) return ["multipart/form-data"];
|
|
683
|
+
if (this.hasFormParams(method)) return ["application/x-www-form-urlencoded"];
|
|
684
|
+
if (this.supportsBodyParameters(method.method)) return ["application/json"];
|
|
685
|
+
return [];
|
|
686
|
+
}
|
|
687
|
+
hasFileParams(method) {
|
|
688
|
+
return method.parameters.some((p) => p.in === ParameterSource.FORM_DATA && p.type.typeName === "file");
|
|
689
|
+
}
|
|
690
|
+
hasFormParams(method) {
|
|
691
|
+
return method.parameters.some((p) => p.in === ParameterSource.FORM_DATA);
|
|
692
|
+
}
|
|
693
|
+
supportsBodyParameters(method) {
|
|
694
|
+
return [
|
|
695
|
+
"post",
|
|
696
|
+
"put",
|
|
697
|
+
"patch"
|
|
698
|
+
].includes(method);
|
|
699
|
+
}
|
|
700
|
+
applyNullable(schema, nullable) {
|
|
701
|
+
schema["x-nullable"] = nullable;
|
|
702
|
+
}
|
|
703
|
+
getRefPrefix() {
|
|
704
|
+
return "#/definitions/";
|
|
705
|
+
}
|
|
706
|
+
getSchemaForIntersectionType(type) {
|
|
707
|
+
const properties = type.members.reduce((acc, type) => {
|
|
708
|
+
if (isRefObjectType(type)) {
|
|
709
|
+
const refType = this.metadata.referenceTypes[type.refName];
|
|
710
|
+
const props = refType && refType.properties && refType.properties.reduce((pAcc, prop) => ({
|
|
711
|
+
...pAcc,
|
|
712
|
+
[prop.name]: this.getSchemaForType(prop.type)
|
|
713
|
+
}), {});
|
|
714
|
+
return {
|
|
715
|
+
...acc,
|
|
716
|
+
...props
|
|
717
|
+
};
|
|
718
|
+
}
|
|
719
|
+
return { ...acc };
|
|
720
|
+
}, {});
|
|
721
|
+
return {
|
|
722
|
+
type: DataTypeName.OBJECT,
|
|
723
|
+
properties
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
getSchemaForUnionType(type) {
|
|
727
|
+
const members = [];
|
|
728
|
+
const enumTypeMember = {
|
|
729
|
+
typeName: TypeName.ENUM,
|
|
730
|
+
members: []
|
|
731
|
+
};
|
|
732
|
+
for (let i = 0; i < type.members.length; i++) {
|
|
733
|
+
const member = type.members[i];
|
|
734
|
+
if (isEnumType(member)) enumTypeMember.members.push(...member.members);
|
|
735
|
+
if (!isAnyType(member) && !isUndefinedType(member) && !isNeverType(member) && !isEnumType(member)) members.push(member);
|
|
736
|
+
}
|
|
737
|
+
if (members.length === 0 && enumTypeMember.members.length > 0) return this.getSchemaForEnumType(enumTypeMember);
|
|
738
|
+
const isNullEnum = enumTypeMember.members.every((member) => member === null);
|
|
739
|
+
if (members.length === 1) {
|
|
740
|
+
if (isNullEnum) {
|
|
741
|
+
const memberType = this.getSchemaForType(members[0]);
|
|
742
|
+
if (memberType.$ref) return memberType;
|
|
743
|
+
memberType["x-nullable"] = true;
|
|
744
|
+
return memberType;
|
|
745
|
+
}
|
|
746
|
+
if (enumTypeMember.members.length === 0) return this.getSchemaForType(members[0]);
|
|
747
|
+
}
|
|
748
|
+
return {
|
|
749
|
+
type: DataTypeName.OBJECT,
|
|
750
|
+
...isNullEnum ? { "x-nullable": true } : {}
|
|
751
|
+
};
|
|
752
|
+
}
|
|
753
|
+
buildOperation(method) {
|
|
754
|
+
const operation = {
|
|
755
|
+
operationId: this.getOperationId(method.name),
|
|
756
|
+
consumes: method.consumes || [],
|
|
757
|
+
produces: method.produces || [],
|
|
758
|
+
responses: {},
|
|
759
|
+
security: method.security || []
|
|
760
|
+
};
|
|
761
|
+
const produces = [];
|
|
762
|
+
method.responses.forEach((res) => {
|
|
763
|
+
operation.responses[res.status] = { description: res.description };
|
|
764
|
+
if (res.schema && !isVoidType(res.schema) && !isNeverType(res.schema)) {
|
|
765
|
+
if (res.produces) produces.push(...res.produces);
|
|
766
|
+
else if (isBinaryType(res.schema)) produces.push("application/octet-stream");
|
|
767
|
+
operation.responses[res.status].schema = this.getSchemaForType(res.schema);
|
|
768
|
+
}
|
|
769
|
+
if (res.examples && res.examples.length > 0) {
|
|
770
|
+
const example = res.examples[0];
|
|
771
|
+
if (example.value) operation.responses[res.status].examples = { "application/json": example.value };
|
|
772
|
+
}
|
|
773
|
+
});
|
|
774
|
+
if (operation.consumes.length === 0) {
|
|
775
|
+
if (method.parameters.some((parameter) => parameter.in === ParameterSource.BODY || parameter.in === ParameterSource.BODY_PROP)) operation.consumes.push("application/json");
|
|
776
|
+
if (method.parameters.some((parameter) => parameter.in === ParameterSource.FORM_DATA)) operation.consumes.push("multipart/form-data");
|
|
777
|
+
}
|
|
778
|
+
if (operation.produces.length === 0 && produces.length > 0) operation.produces = [...new Set(produces)];
|
|
779
|
+
if (operation.produces.length === 0) operation.produces = ["application/json"];
|
|
780
|
+
return operation;
|
|
781
|
+
}
|
|
782
|
+
};
|
|
783
|
+
//#endregion
|
|
784
|
+
//#region src/adapters/generator/v3/module.ts
|
|
785
|
+
const OPENAPI_VERSION_MAP = {
|
|
786
|
+
v3: "3.0.0",
|
|
787
|
+
"v3.1": "3.1.0",
|
|
788
|
+
"v3.2": "3.2.0"
|
|
789
|
+
};
|
|
790
|
+
function uniqueOperationId(base, used) {
|
|
791
|
+
if (!used.has(base)) {
|
|
792
|
+
used.add(base);
|
|
793
|
+
return base;
|
|
794
|
+
}
|
|
795
|
+
let counter = 2;
|
|
796
|
+
while (used.has(`${base}_${counter}`)) counter += 1;
|
|
797
|
+
const candidate = `${base}_${counter}`;
|
|
798
|
+
used.add(candidate);
|
|
799
|
+
return candidate;
|
|
800
|
+
}
|
|
801
|
+
var V3Generator = class V3Generator extends AbstractSpecGenerator {
|
|
802
|
+
openApiVersion;
|
|
803
|
+
constructor(metadata, config, version = "v3.2") {
|
|
804
|
+
super(metadata, config);
|
|
805
|
+
this.openApiVersion = OPENAPI_VERSION_MAP[version] || "3.2.0";
|
|
806
|
+
}
|
|
807
|
+
async build() {
|
|
808
|
+
if (typeof this.spec !== "undefined") return this.spec;
|
|
809
|
+
let spec = {
|
|
810
|
+
components: this.buildComponents(),
|
|
811
|
+
info: this.buildInfo(),
|
|
812
|
+
openapi: this.openApiVersion,
|
|
813
|
+
paths: this.buildPaths(),
|
|
814
|
+
servers: this.buildServers(),
|
|
815
|
+
tags: this.buildTags()
|
|
816
|
+
};
|
|
817
|
+
if (this.config.specificationExtra) spec = merge(spec, this.config.specificationExtra);
|
|
818
|
+
this.spec = spec;
|
|
819
|
+
return spec;
|
|
820
|
+
}
|
|
821
|
+
buildComponents() {
|
|
822
|
+
const components = {
|
|
823
|
+
examples: {},
|
|
824
|
+
headers: {},
|
|
825
|
+
parameters: {},
|
|
826
|
+
requestBodies: {},
|
|
827
|
+
responses: {},
|
|
828
|
+
schemas: this.buildSchemasForReferenceTypes((output, referenceType) => {
|
|
829
|
+
if (referenceType.deprecated) output.deprecated = true;
|
|
830
|
+
}),
|
|
831
|
+
securitySchemes: {}
|
|
832
|
+
};
|
|
833
|
+
if (this.config.securityDefinitions) components.securitySchemes = V3Generator.translateSecurityDefinitions(this.config.securityDefinitions);
|
|
834
|
+
return components;
|
|
835
|
+
}
|
|
836
|
+
static translateSecurityDefinitions(securityDefinitions) {
|
|
837
|
+
const output = {};
|
|
838
|
+
const keys = Object.keys(securityDefinitions);
|
|
839
|
+
for (const key of keys) {
|
|
840
|
+
const securityDefinition = securityDefinitions[key];
|
|
841
|
+
switch (securityDefinition.type) {
|
|
842
|
+
case "http":
|
|
843
|
+
output[key] = securityDefinition;
|
|
844
|
+
break;
|
|
845
|
+
case "oauth2":
|
|
846
|
+
output[key] = securityDefinition;
|
|
847
|
+
break;
|
|
848
|
+
case "apiKey":
|
|
849
|
+
output[key] = securityDefinition;
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return output;
|
|
854
|
+
}
|
|
855
|
+
buildPaths() {
|
|
856
|
+
const output = {};
|
|
857
|
+
const usedOperationIds = /* @__PURE__ */ new Set();
|
|
858
|
+
for (let i = 0; i < this.metadata.controllers.length; i++) {
|
|
859
|
+
const controller = this.metadata.controllers[i];
|
|
860
|
+
if (controller.hidden) continue;
|
|
861
|
+
const controllerPaths = controller.paths.length === 0 ? [""] : controller.paths;
|
|
862
|
+
for (let j = 0; j < controller.methods.length; j++) {
|
|
863
|
+
const method = controller.methods[j];
|
|
864
|
+
if (method.hidden) continue;
|
|
865
|
+
method.deprecated = method.deprecated || controller.deprecated;
|
|
866
|
+
for (const controllerPath of controllerPaths) {
|
|
867
|
+
let path = removeFinalCharacter(removeDuplicateSlashes(`/${controllerPath}/${method.path}`), "/");
|
|
868
|
+
path = normalizePathParameters(path);
|
|
869
|
+
output[path] = output[path] || {};
|
|
870
|
+
output[path][method.method] = this.buildMethod(controller.name, method, path, usedOperationIds);
|
|
871
|
+
}
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return output;
|
|
875
|
+
}
|
|
876
|
+
buildMethod(controllerName, method, emittedPath, usedOperationIds) {
|
|
877
|
+
const output = this.buildOperation(controllerName, method);
|
|
878
|
+
output.description = method.description;
|
|
879
|
+
output.summary = method.summary;
|
|
880
|
+
output.tags = method.tags;
|
|
881
|
+
output.operationId = uniqueOperationId(method.operationId || output.operationId, usedOperationIds);
|
|
882
|
+
if (method.deprecated) output.deprecated = method.deprecated;
|
|
883
|
+
if (method.security) output.security = method.security;
|
|
884
|
+
const parameters = this.groupParameters(method.parameters);
|
|
885
|
+
const pathParams = (parameters[ParameterSource.PATH] || []).filter((p) => emittedPath.includes(`{${p.name}}`));
|
|
886
|
+
output.parameters = [
|
|
887
|
+
...parameters[ParameterSource.QUERY_PROP] || [],
|
|
888
|
+
...parameters[ParameterSource.HEADER] || [],
|
|
889
|
+
...pathParams,
|
|
890
|
+
...parameters[ParameterSource.COOKIE] || []
|
|
891
|
+
].map((p) => this.buildParameter(p));
|
|
892
|
+
const bodyParams = parameters[ParameterSource.BODY] || [];
|
|
893
|
+
const formParams = parameters[ParameterSource.FORM_DATA] || [];
|
|
894
|
+
if (bodyParams.length > 1) throw new SwaggerError({
|
|
895
|
+
message: `Only one body parameter allowed per method, but ${bodyParams.length} found in '${method.name}'.`,
|
|
896
|
+
code: SwaggerErrorCode.BODY_PARAMETER_DUPLICATE
|
|
897
|
+
});
|
|
898
|
+
if (bodyParams.length > 0 && formParams.length > 0) throw new SwaggerError({
|
|
899
|
+
message: `Cannot mix body and form parameters in method '${method.name}'.`,
|
|
900
|
+
code: SwaggerErrorCode.BODY_FORM_CONFLICT
|
|
901
|
+
});
|
|
902
|
+
const bodyPropParams = parameters[ParameterSource.BODY_PROP] || [];
|
|
903
|
+
if (bodyPropParams.length > 0) {
|
|
904
|
+
if (bodyParams.length === 0) bodyParams.push({
|
|
905
|
+
in: ParameterSource.BODY,
|
|
906
|
+
name: "body",
|
|
907
|
+
description: "",
|
|
908
|
+
parameterName: bodyPropParams[0].parameterName || "body",
|
|
909
|
+
required: true,
|
|
910
|
+
type: {
|
|
911
|
+
typeName: TypeName.NESTED_OBJECT_LITERAL,
|
|
912
|
+
properties: []
|
|
913
|
+
},
|
|
914
|
+
validators: {},
|
|
915
|
+
deprecated: false,
|
|
916
|
+
extensions: []
|
|
917
|
+
});
|
|
918
|
+
if (isNestedObjectLiteralType(bodyParams[0].type)) for (const bodyPropParam of bodyPropParams) bodyParams[0].type.properties.push({
|
|
919
|
+
default: bodyPropParam.default,
|
|
920
|
+
validators: bodyPropParam.validators,
|
|
921
|
+
description: bodyPropParam.description,
|
|
922
|
+
name: bodyPropParam.name,
|
|
923
|
+
type: bodyPropParam.type,
|
|
924
|
+
required: bodyPropParam.required,
|
|
925
|
+
deprecated: bodyPropParam.deprecated
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
if (bodyParams.length > 0) output.requestBody = this.buildRequestBody(bodyParams[0]);
|
|
929
|
+
else if (formParams.length > 0) output.requestBody = this.buildRequestBodyWithFormData(formParams);
|
|
930
|
+
Object.assign(output, this.transformExtensions(method.extensions));
|
|
931
|
+
return output;
|
|
932
|
+
}
|
|
933
|
+
buildRequestBodyWithFormData(parameters) {
|
|
934
|
+
const required = [];
|
|
935
|
+
const properties = {};
|
|
936
|
+
const keys = Object.keys(parameters);
|
|
937
|
+
for (let i = 0; i < parameters.length; i++) {
|
|
938
|
+
const { schema } = this.buildMediaType(parameters[keys[i]]);
|
|
939
|
+
properties[parameters[keys[i]].name] = schema;
|
|
940
|
+
if (parameters[keys[i]].required) required.push(parameters[keys[i]].name);
|
|
941
|
+
}
|
|
942
|
+
return {
|
|
943
|
+
required: required.length > 0,
|
|
944
|
+
content: { "multipart/form-data": { schema: {
|
|
945
|
+
type: DataTypeName.OBJECT,
|
|
946
|
+
properties,
|
|
947
|
+
...required && required.length && { required }
|
|
948
|
+
} } }
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
buildRequestBody(parameter) {
|
|
952
|
+
const mediaType = this.buildMediaType(parameter);
|
|
953
|
+
return {
|
|
954
|
+
description: parameter.description,
|
|
955
|
+
required: parameter.required,
|
|
956
|
+
content: { "application/json": mediaType }
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
buildMediaType(parameter) {
|
|
960
|
+
const examples = this.transformParameterExamples(parameter);
|
|
961
|
+
return {
|
|
962
|
+
schema: this.getSchemaForType(parameter.type),
|
|
963
|
+
...Object.keys(examples).length > 0 && { examples }
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
buildResponses(input) {
|
|
967
|
+
const output = {};
|
|
968
|
+
for (const res of input) {
|
|
969
|
+
const name = res.status || "default";
|
|
970
|
+
output[name] = { description: res.description };
|
|
971
|
+
if (res.schema && !isVoidType(res.schema) && !isNeverType(res.schema)) {
|
|
972
|
+
const examples = {};
|
|
973
|
+
if (res.examples && res.examples.length > 0) for (let i = 0; i < res.examples.length; i++) {
|
|
974
|
+
const label = res.examples[i].label || `example${i + 1}`;
|
|
975
|
+
examples[label] = { value: res.examples[i].value };
|
|
976
|
+
}
|
|
977
|
+
output[name].content = output[name].content || {};
|
|
978
|
+
const contentTypes = res.produces || ["application/json"];
|
|
979
|
+
for (const contentType of contentTypes) output[name].content[contentType] = {
|
|
980
|
+
schema: this.getSchemaForType(res.schema),
|
|
981
|
+
...Object.keys(examples).length > 0 && { examples }
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
if (res.headers) {
|
|
985
|
+
const headers = {};
|
|
986
|
+
if (isRefObjectType(res.headers)) headers[res.headers.refName] = {
|
|
987
|
+
schema: this.getSchemaForReferenceType(res.headers),
|
|
988
|
+
description: res.headers.description
|
|
989
|
+
};
|
|
990
|
+
else if (isNestedObjectLiteralType(res.headers)) res.headers.properties.forEach((each) => {
|
|
991
|
+
headers[each.name] = {
|
|
992
|
+
schema: this.getSchemaForType(each.type),
|
|
993
|
+
description: each.description,
|
|
994
|
+
required: each.required
|
|
995
|
+
};
|
|
996
|
+
});
|
|
997
|
+
output[res.name].headers = headers;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
return output;
|
|
1001
|
+
}
|
|
1002
|
+
buildOperation(controllerName, method) {
|
|
1003
|
+
const operation = {
|
|
1004
|
+
operationId: this.getOperationId(method.name),
|
|
1005
|
+
responses: this.buildResponses(method.responses)
|
|
1006
|
+
};
|
|
1007
|
+
if (method.description) operation.description = method.description;
|
|
1008
|
+
if (method.security) operation.security = method.security;
|
|
1009
|
+
if (method.deprecated) operation.deprecated = method.deprecated;
|
|
1010
|
+
return operation;
|
|
1011
|
+
}
|
|
1012
|
+
transformParameterSource(source) {
|
|
1013
|
+
if (source === ParameterSource.COOKIE) return ParameterSourceV3.COOKIE;
|
|
1014
|
+
if (source === ParameterSource.HEADER) return ParameterSourceV3.HEADER;
|
|
1015
|
+
if (source === ParameterSource.PATH) return ParameterSourceV3.PATH;
|
|
1016
|
+
if (source === ParameterSource.QUERY_PROP || source === ParameterSource.QUERY) return ParameterSourceV3.QUERY;
|
|
1017
|
+
}
|
|
1018
|
+
buildParameter(input) {
|
|
1019
|
+
const sourceIn = this.transformParameterSource(input.in);
|
|
1020
|
+
if (!sourceIn) throw new SwaggerError({
|
|
1021
|
+
message: `The parameter source '${input.in}' for parameter '${input.name}' is not supported in OpenAPI 3.x.`,
|
|
1022
|
+
code: SwaggerErrorCode.PARAMETER_SOURCE_UNSUPPORTED
|
|
1023
|
+
});
|
|
1024
|
+
const parameter = {
|
|
1025
|
+
allowEmptyValue: false,
|
|
1026
|
+
deprecated: false,
|
|
1027
|
+
description: input.description,
|
|
1028
|
+
in: sourceIn,
|
|
1029
|
+
name: input.name,
|
|
1030
|
+
required: input.required,
|
|
1031
|
+
schema: {
|
|
1032
|
+
default: input.default,
|
|
1033
|
+
format: void 0,
|
|
1034
|
+
...this.transformValidators(input.validators)
|
|
1035
|
+
}
|
|
1036
|
+
};
|
|
1037
|
+
Object.assign(parameter, this.transformExtensions(input.extensions));
|
|
1038
|
+
if (input.deprecated) parameter.deprecated = true;
|
|
1039
|
+
const parameterType = this.getSchemaForType(input.type);
|
|
1040
|
+
if (parameterType.format) parameter.schema.format = parameterType.format;
|
|
1041
|
+
if (parameterType.$ref) {
|
|
1042
|
+
parameter.schema = parameterType;
|
|
1043
|
+
return parameter;
|
|
1044
|
+
}
|
|
1045
|
+
if (isAnyType(input.type)) parameter.schema.type = DataTypeName.STRING;
|
|
1046
|
+
else {
|
|
1047
|
+
if (parameterType.type) parameter.schema.type = parameterType.type;
|
|
1048
|
+
parameter.schema.items = parameterType.items;
|
|
1049
|
+
parameter.schema.enum = parameterType.enum;
|
|
1050
|
+
}
|
|
1051
|
+
parameter.examples = this.transformParameterExamples(input);
|
|
1052
|
+
return parameter;
|
|
1053
|
+
}
|
|
1054
|
+
transformParameterExamples(parameter) {
|
|
1055
|
+
const output = {};
|
|
1056
|
+
if (parameter.examples && parameter.examples.length > 0) for (let i = 0; i < parameter.examples.length; i++) {
|
|
1057
|
+
const label = parameter.examples[i].label || `example${i + 1}`;
|
|
1058
|
+
output[label] = { value: parameter.examples[i].value };
|
|
1059
|
+
}
|
|
1060
|
+
return output;
|
|
1061
|
+
}
|
|
1062
|
+
buildServers() {
|
|
1063
|
+
const servers = [];
|
|
1064
|
+
for (let i = 0; i < this.config.servers.length; i++) {
|
|
1065
|
+
const url = new URL(this.config.servers[i].url, "http://localhost:3000/");
|
|
1066
|
+
servers.push({
|
|
1067
|
+
url: `${url.protocol}//${url.host}${url.pathname || ""}`,
|
|
1068
|
+
...this.config.servers[i].description ? { description: this.config.servers[i].description } : {}
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
return servers;
|
|
1072
|
+
}
|
|
1073
|
+
resolveAdditionalProperties(type) {
|
|
1074
|
+
return this.getSchemaForType(type);
|
|
1075
|
+
}
|
|
1076
|
+
markPropertyDeprecated(schema) {
|
|
1077
|
+
schema.deprecated = true;
|
|
1078
|
+
}
|
|
1079
|
+
assignPropertyDefaults(schema, property) {
|
|
1080
|
+
schema.default = property.default;
|
|
1081
|
+
}
|
|
1082
|
+
buildSchemaForRefEnum(referenceType) {
|
|
1083
|
+
const typesUsed = this.determineTypesUsedInEnum(referenceType.members);
|
|
1084
|
+
if (typesUsed.length === 1) return super.buildSchemaForRefEnum(referenceType);
|
|
1085
|
+
const schema = {
|
|
1086
|
+
description: referenceType.description,
|
|
1087
|
+
anyOf: []
|
|
1088
|
+
};
|
|
1089
|
+
for (const element of typesUsed) schema.anyOf.push({
|
|
1090
|
+
type: element,
|
|
1091
|
+
enum: referenceType.members.filter((e) => typeof e === element)
|
|
1092
|
+
});
|
|
1093
|
+
return schema;
|
|
1094
|
+
}
|
|
1095
|
+
isV31OrLater() {
|
|
1096
|
+
return !this.openApiVersion.startsWith("3.0");
|
|
1097
|
+
}
|
|
1098
|
+
shouldStripRefSiblings() {
|
|
1099
|
+
return !this.isV31OrLater();
|
|
1100
|
+
}
|
|
1101
|
+
getSchemaForIntersectionType(type) {
|
|
1102
|
+
return { allOf: type.members.map((x) => this.getSchemaForType(x)) };
|
|
1103
|
+
}
|
|
1104
|
+
applyNullable(schema, nullable) {
|
|
1105
|
+
if (!nullable) return;
|
|
1106
|
+
if (this.isV31OrLater()) {
|
|
1107
|
+
if (schema.type && !Array.isArray(schema.type)) schema.type = [schema.type, "null"];
|
|
1108
|
+
else if (Array.isArray(schema.type) && !schema.type.includes("null")) schema.type = [...schema.type, "null"];
|
|
1109
|
+
} else schema.nullable = true;
|
|
1110
|
+
}
|
|
1111
|
+
getRefPrefix() {
|
|
1112
|
+
return "#/components/schemas/";
|
|
1113
|
+
}
|
|
1114
|
+
getSchemaForUnionType(type) {
|
|
1115
|
+
const members = [];
|
|
1116
|
+
let nullable = false;
|
|
1117
|
+
const enumMembers = {};
|
|
1118
|
+
for (let i = 0; i < type.members.length; i++) {
|
|
1119
|
+
const member = type.members[i];
|
|
1120
|
+
if (isEnumType(member)) for (let j = 0; j < member.members.length; j++) {
|
|
1121
|
+
const memberChild = member.members[j];
|
|
1122
|
+
if (memberChild === null || memberChild === void 0) {
|
|
1123
|
+
nullable = true;
|
|
1124
|
+
continue;
|
|
1125
|
+
}
|
|
1126
|
+
const typeOf = typeof memberChild;
|
|
1127
|
+
if (typeOf === "string" || typeOf === "number" || typeOf === "boolean") {
|
|
1128
|
+
enumMembers[typeOf] = enumMembers[typeOf] || [];
|
|
1129
|
+
enumMembers[typeOf].push(memberChild);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
if (!isAnyType(member) && !isUndefinedType(member) && !isNeverType(member) && !isEnumType(member)) members.push(member);
|
|
1133
|
+
}
|
|
1134
|
+
const schemas = [];
|
|
1135
|
+
for (const member of members) schemas.push(this.getSchemaForType(member));
|
|
1136
|
+
const enumMembersKeys = Object.keys(enumMembers);
|
|
1137
|
+
for (const enumMembersKey of enumMembersKeys) {
|
|
1138
|
+
const enumType = {
|
|
1139
|
+
typeName: "enum",
|
|
1140
|
+
members: enumMembers[enumMembersKey]
|
|
1141
|
+
};
|
|
1142
|
+
schemas.push(this.getSchemaForEnumType(enumType));
|
|
1143
|
+
}
|
|
1144
|
+
const useOneOf = members.length > 0 && enumMembersKeys.length === 0 && members.every((m) => V3Generator.isObjectLikeType(m));
|
|
1145
|
+
const compositionKey = useOneOf ? "oneOf" : "anyOf";
|
|
1146
|
+
if (this.isV31OrLater()) {
|
|
1147
|
+
if (nullable) schemas.push({ type: "null" });
|
|
1148
|
+
if (schemas.length === 1) return schemas[0];
|
|
1149
|
+
const schema = { [compositionKey]: schemas };
|
|
1150
|
+
if (useOneOf) this.applyDiscriminator(schema, members);
|
|
1151
|
+
return schema;
|
|
1152
|
+
}
|
|
1153
|
+
if (schemas.length === 1) {
|
|
1154
|
+
const schema = schemas[0];
|
|
1155
|
+
if (schema.$ref) return {
|
|
1156
|
+
allOf: [schema],
|
|
1157
|
+
nullable
|
|
1158
|
+
};
|
|
1159
|
+
return {
|
|
1160
|
+
...schema,
|
|
1161
|
+
nullable
|
|
1162
|
+
};
|
|
1163
|
+
}
|
|
1164
|
+
const schema = {
|
|
1165
|
+
[compositionKey]: schemas,
|
|
1166
|
+
...nullable ? { nullable } : {}
|
|
1167
|
+
};
|
|
1168
|
+
if (useOneOf) this.applyDiscriminator(schema, members);
|
|
1169
|
+
return schema;
|
|
1170
|
+
}
|
|
1171
|
+
static isObjectLikeType(type) {
|
|
1172
|
+
if (isRefObjectType(type) || isNestedObjectLiteralType(type) || isIntersectionType(type)) return true;
|
|
1173
|
+
if (isRefAliasType(type)) return V3Generator.isObjectLikeType(type.type);
|
|
1174
|
+
return false;
|
|
1175
|
+
}
|
|
1176
|
+
applyDiscriminator(schema, members) {
|
|
1177
|
+
const discriminator = this.detectDiscriminator(members);
|
|
1178
|
+
if (discriminator) schema.discriminator = discriminator;
|
|
1179
|
+
}
|
|
1180
|
+
detectDiscriminator(members) {
|
|
1181
|
+
const resolvedMembers = members.map((m) => this.resolveDiscriminatorMember(m));
|
|
1182
|
+
if (resolvedMembers.some((m) => !m)) return;
|
|
1183
|
+
const firstProps = resolvedMembers[0].properties;
|
|
1184
|
+
for (const prop of firstProps) {
|
|
1185
|
+
if (prop.type.typeName !== "enum") continue;
|
|
1186
|
+
if (prop.type.members.length !== 1) continue;
|
|
1187
|
+
const propName = prop.name;
|
|
1188
|
+
const mapping = {};
|
|
1189
|
+
let isDiscriminator = true;
|
|
1190
|
+
for (const member of resolvedMembers) {
|
|
1191
|
+
const memberProp = member.properties.find((p) => p.name === propName);
|
|
1192
|
+
if (!memberProp || !memberProp.required || memberProp.type.typeName !== "enum" || memberProp.type.members.length !== 1) {
|
|
1193
|
+
isDiscriminator = false;
|
|
1194
|
+
break;
|
|
1195
|
+
}
|
|
1196
|
+
const value = String(memberProp.type.members[0]);
|
|
1197
|
+
if (mapping[value]) {
|
|
1198
|
+
isDiscriminator = false;
|
|
1199
|
+
break;
|
|
1200
|
+
}
|
|
1201
|
+
mapping[value] = `${this.getRefPrefix()}${member.refName}`;
|
|
1202
|
+
}
|
|
1203
|
+
if (isDiscriminator && Object.keys(mapping).length === members.length) return {
|
|
1204
|
+
propertyName: propName,
|
|
1205
|
+
mapping
|
|
1206
|
+
};
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Resolve a union member to its refName and properties for discriminator
|
|
1211
|
+
* detection. Accepts both refObject and refAlias members, unwrapping
|
|
1212
|
+
* aliases to find the underlying properties while preserving the
|
|
1213
|
+
* original refName for $ref mapping.
|
|
1214
|
+
*/
|
|
1215
|
+
resolveDiscriminatorMember(member) {
|
|
1216
|
+
if (!isRefObjectType(member) && !isRefAliasType(member)) return;
|
|
1217
|
+
const { refName } = member;
|
|
1218
|
+
const referenceType = this.metadata.referenceTypes[refName];
|
|
1219
|
+
if (!referenceType) return;
|
|
1220
|
+
if (referenceType.typeName === "refObject") return {
|
|
1221
|
+
refName,
|
|
1222
|
+
properties: referenceType.properties
|
|
1223
|
+
};
|
|
1224
|
+
if (referenceType.typeName === "refAlias") {
|
|
1225
|
+
let inner = referenceType.type;
|
|
1226
|
+
for (let depth = 0; depth < 10; depth++) {
|
|
1227
|
+
if (isRefObjectType(inner)) {
|
|
1228
|
+
const resolved = this.metadata.referenceTypes[inner.refName];
|
|
1229
|
+
if (resolved?.typeName === "refObject") return {
|
|
1230
|
+
refName,
|
|
1231
|
+
properties: resolved.properties
|
|
1232
|
+
};
|
|
1233
|
+
return;
|
|
1234
|
+
}
|
|
1235
|
+
if (isNestedObjectLiteralType(inner)) return {
|
|
1236
|
+
refName,
|
|
1237
|
+
properties: inner.properties
|
|
1238
|
+
};
|
|
1239
|
+
if (isRefAliasType(inner)) {
|
|
1240
|
+
inner = inner.type;
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
return;
|
|
1244
|
+
}
|
|
1245
|
+
}
|
|
1246
|
+
}
|
|
1247
|
+
};
|
|
1248
|
+
//#endregion
|
|
1249
|
+
//#region src/app/module.ts
|
|
1250
|
+
function toSpecGeneratorOptionsInput(options) {
|
|
1251
|
+
const { data } = options;
|
|
1252
|
+
if (!data) return {};
|
|
1253
|
+
return {
|
|
1254
|
+
name: data.name,
|
|
1255
|
+
version: data.version,
|
|
1256
|
+
description: data.description,
|
|
1257
|
+
license: data.license,
|
|
1258
|
+
servers: data.servers,
|
|
1259
|
+
securityDefinitions: data.securityDefinitions,
|
|
1260
|
+
consumes: data.consumes,
|
|
1261
|
+
produces: data.produces,
|
|
1262
|
+
collectionFormat: data.collectionFormat,
|
|
1263
|
+
specificationExtra: data.extra
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1266
|
+
async function resolveMetadata(options) {
|
|
1267
|
+
if (isMetadata(options.metadata)) return options.metadata;
|
|
1268
|
+
return generateMetadata(options.metadata);
|
|
1269
|
+
}
|
|
1270
|
+
async function generateSwagger(options) {
|
|
1271
|
+
const metadata = await resolveMetadata(options);
|
|
1272
|
+
const specGeneratorOptionsInput = toSpecGeneratorOptionsInput(options);
|
|
1273
|
+
switch (options.version) {
|
|
1274
|
+
case Version.V3:
|
|
1275
|
+
case Version.V3_1:
|
|
1276
|
+
case Version.V3_2: return await new V3Generator(metadata, specGeneratorOptionsInput, options.version).build();
|
|
1277
|
+
default: return await new V2Generator(metadata, specGeneratorOptionsInput).build();
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
//#endregion
|
|
1281
|
+
//#region src/app/save.ts
|
|
1282
|
+
const EXTENSION_PATTERN = /\.(json|ya?ml)$/i;
|
|
1283
|
+
function resolveFileName(name, format) {
|
|
1284
|
+
return `${(name ?? "swagger").replace(EXTENSION_PATTERN, "")}.${format}`;
|
|
1285
|
+
}
|
|
1286
|
+
function serialise(spec, format) {
|
|
1287
|
+
if (format === DocumentFormat.YAML) return YAML.stringify(spec, 1e3);
|
|
1288
|
+
return JSON.stringify(spec, null, 4);
|
|
1289
|
+
}
|
|
1290
|
+
async function saveSwagger(spec, options = {}) {
|
|
1291
|
+
const format = options.format ?? DocumentFormat.JSON;
|
|
1292
|
+
let cwd = process.cwd();
|
|
1293
|
+
if (options.cwd) cwd = path.isAbsolute(options.cwd) ? options.cwd : path.join(process.cwd(), options.cwd);
|
|
1294
|
+
const name = resolveFileName(options.name, format);
|
|
1295
|
+
const filePath = path.join(cwd, name);
|
|
1296
|
+
await fs.promises.mkdir(cwd, { recursive: true });
|
|
1297
|
+
const content = serialise(spec, format);
|
|
1298
|
+
await fs.promises.writeFile(filePath, content, { encoding: "utf-8" });
|
|
1299
|
+
return {
|
|
1300
|
+
path: filePath,
|
|
1301
|
+
name,
|
|
1302
|
+
content
|
|
1303
|
+
};
|
|
1304
|
+
}
|
|
1305
|
+
//#endregion
|
|
1306
|
+
export { AbstractSpecGenerator, DataFormatName, DataTypeName, DocumentFormat, ParameterSourceV2, ParameterSourceV3, SecurityType, SwaggerError, SwaggerErrorCode, TransferProtocol, V2Generator, V3Generator, Version, buildSpecGeneratorOptions, generateSwagger, hasOwnProperty, normalizePathParameters, removeDuplicateSlashes, removeFinalCharacter, saveSwagger, transformValueTo };
|
|
1307
|
+
|
|
1308
|
+
//# sourceMappingURL=index.mjs.map
|