@typia/utils 12.0.0 → 12.1.0-dev.20260325

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.
@@ -1,450 +1,450 @@
1
- import { OpenApi, SwaggerV2 } from "@typia/interface";
2
-
3
- import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
-
5
- export namespace SwaggerV2Downgrader {
6
- export interface IComponentsCollection {
7
- original: OpenApi.IComponents;
8
- downgraded: Record<string, SwaggerV2.IJsonSchema>;
9
- }
10
-
11
- export const downgrade = (input: OpenApi.IDocument): SwaggerV2.IDocument => {
12
- const collection: IComponentsCollection = downgradeComponents(
13
- input.components,
14
- );
15
- return {
16
- swagger: "2.0",
17
- info: input.info,
18
- host: input.servers?.[0]?.url
19
- ? input.servers[0].url.split("://").pop()!
20
- : "",
21
- definitions: collection.downgraded,
22
- securityDefinitions: input.components?.securitySchemes
23
- ? Object.fromEntries(
24
- Object.entries(input.components.securitySchemes)
25
- .filter(([_, v]) => v !== undefined)
26
- .map(([key, value]) =>
27
- downgradeSecurityScheme(value).map((v) => [key, v]),
28
- )
29
- .flat(),
30
- )
31
- : undefined,
32
- paths: input.paths
33
- ? Object.fromEntries(
34
- Object.entries(input.paths)
35
- .filter(([_, v]) => v !== undefined)
36
- .map(
37
- ([key, value]) =>
38
- [key, downgradePathItem(collection)(value)] as const,
39
- ),
40
- )
41
- : undefined,
42
- security: input.security,
43
- tags: input.tags,
44
- };
45
- };
46
-
47
- /* -----------------------------------------------------------
48
- OPERATORS
49
- ----------------------------------------------------------- */
50
- const downgradePathItem =
51
- (collection: IComponentsCollection) =>
52
- (pathItem: OpenApi.IPath): SwaggerV2.IPath => {
53
- // Collect non-standard operations for x-additionalOperations
54
- const xAdditionalOperations: Record<string, SwaggerV2.IOperation> = {};
55
-
56
- // query method goes to x-additionalOperations
57
- if (pathItem.query) {
58
- xAdditionalOperations["query"] = downgradeOperation(collection)(pathItem.query);
59
- }
60
-
61
- // additionalOperations also go to x-additionalOperations
62
- if (pathItem.additionalOperations) {
63
- for (const [key, value] of Object.entries(pathItem.additionalOperations)) {
64
- if (value !== undefined) {
65
- xAdditionalOperations[key] = downgradeOperation(collection)(value);
66
- }
67
- }
68
- }
69
-
70
- return {
71
- ...(pathItem as any),
72
- ...(pathItem.get
73
- ? { get: downgradeOperation(collection)(pathItem.get) }
74
- : undefined),
75
- ...(pathItem.put
76
- ? { put: downgradeOperation(collection)(pathItem.put) }
77
- : undefined),
78
- ...(pathItem.post
79
- ? { post: downgradeOperation(collection)(pathItem.post) }
80
- : undefined),
81
- ...(pathItem.delete
82
- ? { delete: downgradeOperation(collection)(pathItem.delete) }
83
- : undefined),
84
- ...(pathItem.options
85
- ? { options: downgradeOperation(collection)(pathItem.options) }
86
- : undefined),
87
- ...(pathItem.head
88
- ? { head: downgradeOperation(collection)(pathItem.head) }
89
- : undefined),
90
- ...(pathItem.patch
91
- ? { patch: downgradeOperation(collection)(pathItem.patch) }
92
- : undefined),
93
- ...(pathItem.trace
94
- ? { trace: downgradeOperation(collection)(pathItem.trace) }
95
- : undefined),
96
- ...(Object.keys(xAdditionalOperations).length > 0
97
- ? { "x-additionalOperations": xAdditionalOperations }
98
- : undefined),
99
- // Remove v3.2-only properties from spread
100
- query: undefined,
101
- additionalOperations: undefined,
102
- };
103
- };
104
-
105
- const downgradeOperation =
106
- (collection: IComponentsCollection) =>
107
- (input: OpenApi.IOperation): SwaggerV2.IOperation => ({
108
- ...input,
109
- parameters:
110
- input.parameters !== undefined || input.requestBody !== undefined
111
- ? [
112
- ...(input.parameters ?? []).map(downgradeParameter(collection)),
113
- ...(input.requestBody
114
- ? [downgradeRequestBody(collection)(input.requestBody)]
115
- : []),
116
- ]
117
- : undefined,
118
- responses: input.responses
119
- ? Object.fromEntries(
120
- Object.entries(input.responses)
121
- .filter(([_, v]) => v !== undefined)
122
- .map(([key, value]) => [
123
- key,
124
- downgradeResponse(collection)(value),
125
- ]),
126
- )
127
- : undefined,
128
- ...{
129
- requestBody: undefined,
130
- servers: undefined,
131
- },
132
- });
133
-
134
- const downgradeParameter =
135
- (collection: IComponentsCollection) =>
136
- (
137
- input: OpenApi.IOperation.IParameter,
138
- i: number,
139
- ): SwaggerV2.IOperation.IParameter =>
140
- ({
141
- ...downgradeSchema(collection)(input.schema),
142
- ...input,
143
- in: input.in === "querystring" ? "query" : input.in,
144
- required: (input.schema as any)?.required,
145
- schema: undefined,
146
- name: input.name ?? `p${i}`,
147
- ...{
148
- example: undefined,
149
- examples: undefined,
150
- },
151
- }) as any;
152
-
153
- const downgradeRequestBody =
154
- (collection: IComponentsCollection) =>
155
- (
156
- input: OpenApi.IOperation.IRequestBody,
157
- ): SwaggerV2.IOperation.IParameter => ({
158
- name: "body",
159
- in: "body",
160
- description: input.description,
161
- required: input.required,
162
- schema: downgradeSchema(collection)(
163
- Object.values(input.content ?? {})[0]?.schema ?? {},
164
- ),
165
- });
166
-
167
- const downgradeResponse =
168
- (collection: IComponentsCollection) =>
169
- (input: OpenApi.IOperation.IResponse): SwaggerV2.IOperation.IResponse => ({
170
- description: input.description,
171
- schema: downgradeSchema(collection)(
172
- Object.values(input.content ?? {})[0]?.schema ?? {},
173
- ),
174
- headers: input.headers
175
- ? Object.fromEntries(
176
- Object.entries(input.headers)
177
- .filter(([_, v]) => v !== undefined)
178
- .map(([key, value]) => [
179
- key,
180
- {
181
- ...value,
182
- schema: downgradeSchema(collection)(value.schema),
183
- ...{
184
- example: undefined,
185
- examples: undefined,
186
- },
187
- },
188
- ]),
189
- )
190
- : undefined,
191
- });
192
-
193
- /* -----------------------------------------------------------
194
- DEFINITIONS
195
- ----------------------------------------------------------- */
196
- export const downgradeComponents = (
197
- input: OpenApi.IComponents,
198
- ): IComponentsCollection => {
199
- const collection: IComponentsCollection = {
200
- original: input,
201
- downgraded: {},
202
- };
203
- if (input.schemas) {
204
- collection.downgraded.schemas = {};
205
- for (const [key, value] of Object.entries(input.schemas))
206
- if (value !== undefined)
207
- collection.downgraded[key.split("/").pop()!] =
208
- downgradeSchema(collection)(value);
209
- }
210
- return collection;
211
- };
212
-
213
- export const downgradeSchema =
214
- (collection: IComponentsCollection) =>
215
- (input: OpenApi.IJsonSchema): SwaggerV2.IJsonSchema => {
216
- const nullable: boolean = isNullable(new Set())(collection.original)(
217
- input,
218
- );
219
- const union: SwaggerV2.IJsonSchema[] = [];
220
- const attribute: SwaggerV2.IJsonSchema.__IAttribute = {
221
- title: input.title,
222
- description: input.description,
223
- deprecated: input.deprecated,
224
- readOnly: input.readOnly,
225
- example: input.example,
226
- examples: input.examples ? Object.values(input.examples) : undefined,
227
- ...Object.fromEntries(
228
- Object.entries(input).filter(
229
- ([key, value]) => key.startsWith("x-") && value !== undefined,
230
- ),
231
- ),
232
- };
233
- const visit = (schema: OpenApi.IJsonSchema): void => {
234
- if (OpenApiTypeChecker.isBoolean(schema))
235
- union.push({ type: "boolean" });
236
- else if (
237
- OpenApiTypeChecker.isBoolean(schema) ||
238
- OpenApiTypeChecker.isInteger(schema) ||
239
- OpenApiTypeChecker.isNumber(schema) ||
240
- OpenApiTypeChecker.isString(schema)
241
- )
242
- union.push({
243
- ...schema,
244
- examples: schema.examples
245
- ? Object.values(schema.examples)
246
- : undefined,
247
- });
248
- else if (OpenApiTypeChecker.isReference(schema))
249
- union.push({ $ref: `#/definitions/${schema.$ref.split("/").pop()}` });
250
- else if (OpenApiTypeChecker.isArray(schema))
251
- union.push({
252
- ...schema,
253
- items: downgradeSchema(collection)(schema.items),
254
- examples: schema.examples
255
- ? Object.values(schema.examples)
256
- : undefined,
257
- });
258
- else if (OpenApiTypeChecker.isTuple(schema))
259
- union.push({
260
- ...schema,
261
- items: ((): SwaggerV2.IJsonSchema => {
262
- if (schema.additionalItems === true) return {};
263
- const elements = [
264
- ...schema.prefixItems,
265
- ...(typeof schema.additionalItems === "object"
266
- ? [downgradeSchema(collection)(schema.additionalItems)]
267
- : []),
268
- ];
269
- if (elements.length === 0) return {};
270
- return {
271
- "x-oneOf": elements.map(downgradeSchema(collection) as any),
272
- };
273
- })(),
274
- minItems: schema.prefixItems.length,
275
- maxItems:
276
- !!schema.additionalItems === true
277
- ? undefined
278
- : schema.prefixItems.length,
279
- ...{
280
- prefixItems: undefined,
281
- additionalItems: undefined,
282
- },
283
- examples: schema.examples
284
- ? Object.values(schema.examples)
285
- : undefined,
286
- });
287
- else if (OpenApiTypeChecker.isObject(schema))
288
- union.push({
289
- ...schema,
290
- properties: schema.properties
291
- ? Object.fromEntries(
292
- Object.entries(schema.properties)
293
- .filter(([_, v]) => v !== undefined)
294
- .map(([key, value]) => [
295
- key,
296
- downgradeSchema(collection)(value),
297
- ]),
298
- )
299
- : undefined,
300
- additionalProperties:
301
- typeof schema.additionalProperties === "object"
302
- ? downgradeSchema(collection)(schema.additionalProperties)
303
- : schema.additionalProperties,
304
- required: schema.required,
305
- examples: schema.examples
306
- ? Object.values(schema.examples)
307
- : undefined,
308
- });
309
- else if (OpenApiTypeChecker.isOneOf(schema))
310
- schema.oneOf.forEach(visit);
311
- };
312
- const visitConstant = (schema: OpenApi.IJsonSchema): void => {
313
- const insert = (value: any): void => {
314
- const matched: SwaggerV2.IJsonSchema.INumber | undefined = union.find(
315
- (u) =>
316
- (u as SwaggerV2.IJsonSchema.__ISignificant<any>).type === value,
317
- ) as SwaggerV2.IJsonSchema.INumber | undefined;
318
- if (matched !== undefined) {
319
- matched.enum ??= [];
320
- matched.enum.push(value);
321
- } else union.push({ type: typeof value as "number", enum: [value] });
322
- if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const);
323
- else if (OpenApiTypeChecker.isOneOf(schema))
324
- schema.oneOf.forEach(insert);
325
- };
326
- };
327
-
328
- visit(input);
329
- visitConstant(input);
330
- if (nullable) {
331
- for (const u of union)
332
- if (OpenApiTypeChecker.isReference(u as any))
333
- downgradeNullableReference(new Set())(collection)(u as any);
334
- else (u as SwaggerV2.IJsonSchema.IArray)["x-nullable"] = true;
335
- }
336
-
337
- if (nullable === true && union.length === 0)
338
- return { type: "null", ...attribute };
339
- return {
340
- ...(union.length === 0
341
- ? { type: undefined }
342
- : union.length === 1
343
- ? { ...union[0] }
344
- : { "x-oneOf": union }),
345
- ...attribute,
346
- ...(union.length > 1 ? { discriminator: undefined } : {}),
347
- };
348
- };
349
-
350
- const downgradeNullableReference =
351
- (visited: Set<string>) =>
352
- (collection: IComponentsCollection) =>
353
- (schema: SwaggerV2.IJsonSchema.IReference): void => {
354
- const key: string = schema.$ref.split("/").pop()!;
355
- if (key.endsWith(".Nullable")) return;
356
-
357
- const found: OpenApi.IJsonSchema | undefined =
358
- collection.original.schemas?.[key];
359
- if (found === undefined) return;
360
- else if (isNullable(visited)(collection.original)(found) === true) return;
361
- else if (collection.downgraded[`${key}.Nullable`] === undefined) {
362
- collection.downgraded[`${key}.Nullable`] = {};
363
- collection.downgraded[`${key}.Nullable`] = downgradeSchema(collection)(
364
- OpenApiTypeChecker.isOneOf(found)
365
- ? {
366
- ...found,
367
- oneOf: [...found.oneOf, { type: "null" }],
368
- }
369
- : {
370
- title: found.title,
371
- description: found.description,
372
- example: found.example,
373
- examples: found.examples
374
- ? Object.values(found.examples)
375
- : undefined,
376
- ...Object.fromEntries(
377
- Object.entries(found).filter(
378
- ([key, value]) =>
379
- key.startsWith("x-") && value !== undefined,
380
- ),
381
- ),
382
- oneOf: [found, { type: "null" }],
383
- },
384
- );
385
- }
386
- schema.$ref += ".Nullable";
387
- };
388
-
389
- const downgradeSecurityScheme = (
390
- input: OpenApi.ISecurityScheme,
391
- ): SwaggerV2.ISecurityDefinition[] => {
392
- if (input.type === "apiKey") return [input];
393
- else if (input.type === "http")
394
- if (input.scheme === "basic")
395
- return [{ type: "basic", description: input.description }];
396
- else return [];
397
- else if (input.type === "oauth2") {
398
- const output: SwaggerV2.ISecurityDefinition[] = [];
399
- if (input.flows.implicit)
400
- output.push({
401
- type: "oauth2",
402
- flow: "implicit",
403
- authorizationUrl: input.flows.implicit.authorizationUrl,
404
- scopes: input.flows.implicit.scopes,
405
- });
406
- if (input.flows.password)
407
- output.push({
408
- type: "oauth2",
409
- flow: "password",
410
- tokenUrl: input.flows.password.tokenUrl,
411
- scopes: input.flows.password.scopes,
412
- });
413
- if (input.flows.clientCredentials)
414
- output.push({
415
- type: "oauth2",
416
- flow: "application",
417
- tokenUrl: input.flows.clientCredentials.tokenUrl,
418
- scopes: input.flows.clientCredentials.scopes,
419
- });
420
- if (input.flows.authorizationCode)
421
- output.push({
422
- type: "oauth2",
423
- flow: "accessCode",
424
- authorizationUrl: input.flows.authorizationCode.authorizationUrl,
425
- tokenUrl: input.flows.authorizationCode.tokenUrl,
426
- scopes: input.flows.authorizationCode.scopes,
427
- });
428
- return output;
429
- }
430
- return [];
431
- };
432
-
433
- const isNullable =
434
- (visited: Set<string>) =>
435
- (components: OpenApi.IComponents) =>
436
- (schema: OpenApi.IJsonSchema): boolean => {
437
- if (OpenApiTypeChecker.isNull(schema)) return true;
438
- else if (OpenApiTypeChecker.isReference(schema)) {
439
- if (visited.has(schema.$ref)) return false;
440
- visited.add(schema.$ref);
441
- const key: string = schema.$ref.split("/").pop()!;
442
- const next: OpenApi.IJsonSchema | undefined = components.schemas?.[key];
443
- return next ? isNullable(visited)(components)(next) : false;
444
- }
445
- return (
446
- OpenApiTypeChecker.isOneOf(schema) &&
447
- schema.oneOf.some(isNullable(visited)(components))
448
- );
449
- };
450
- }
1
+ import { OpenApi, SwaggerV2 } from "@typia/interface";
2
+
3
+ import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
+
5
+ export namespace SwaggerV2Downgrader {
6
+ export interface IComponentsCollection {
7
+ original: OpenApi.IComponents;
8
+ downgraded: Record<string, SwaggerV2.IJsonSchema>;
9
+ }
10
+
11
+ export const downgrade = (input: OpenApi.IDocument): SwaggerV2.IDocument => {
12
+ const collection: IComponentsCollection = downgradeComponents(
13
+ input.components,
14
+ );
15
+ return {
16
+ swagger: "2.0",
17
+ info: input.info,
18
+ host: input.servers?.[0]?.url
19
+ ? input.servers[0].url.split("://").pop()!
20
+ : "",
21
+ definitions: collection.downgraded,
22
+ securityDefinitions: input.components?.securitySchemes
23
+ ? Object.fromEntries(
24
+ Object.entries(input.components.securitySchemes)
25
+ .filter(([_, v]) => v !== undefined)
26
+ .map(([key, value]) =>
27
+ downgradeSecurityScheme(value).map((v) => [key, v]),
28
+ )
29
+ .flat(),
30
+ )
31
+ : undefined,
32
+ paths: input.paths
33
+ ? Object.fromEntries(
34
+ Object.entries(input.paths)
35
+ .filter(([_, v]) => v !== undefined)
36
+ .map(
37
+ ([key, value]) =>
38
+ [key, downgradePathItem(collection)(value)] as const,
39
+ ),
40
+ )
41
+ : undefined,
42
+ security: input.security,
43
+ tags: input.tags,
44
+ };
45
+ };
46
+
47
+ /* -----------------------------------------------------------
48
+ OPERATORS
49
+ ----------------------------------------------------------- */
50
+ const downgradePathItem =
51
+ (collection: IComponentsCollection) =>
52
+ (pathItem: OpenApi.IPath): SwaggerV2.IPath => {
53
+ // Collect non-standard operations for x-additionalOperations
54
+ const xAdditionalOperations: Record<string, SwaggerV2.IOperation> = {};
55
+
56
+ // query method goes to x-additionalOperations
57
+ if (pathItem.query) {
58
+ xAdditionalOperations["query"] = downgradeOperation(collection)(pathItem.query);
59
+ }
60
+
61
+ // additionalOperations also go to x-additionalOperations
62
+ if (pathItem.additionalOperations) {
63
+ for (const [key, value] of Object.entries(pathItem.additionalOperations)) {
64
+ if (value !== undefined) {
65
+ xAdditionalOperations[key] = downgradeOperation(collection)(value);
66
+ }
67
+ }
68
+ }
69
+
70
+ return {
71
+ ...(pathItem as any),
72
+ ...(pathItem.get
73
+ ? { get: downgradeOperation(collection)(pathItem.get) }
74
+ : undefined),
75
+ ...(pathItem.put
76
+ ? { put: downgradeOperation(collection)(pathItem.put) }
77
+ : undefined),
78
+ ...(pathItem.post
79
+ ? { post: downgradeOperation(collection)(pathItem.post) }
80
+ : undefined),
81
+ ...(pathItem.delete
82
+ ? { delete: downgradeOperation(collection)(pathItem.delete) }
83
+ : undefined),
84
+ ...(pathItem.options
85
+ ? { options: downgradeOperation(collection)(pathItem.options) }
86
+ : undefined),
87
+ ...(pathItem.head
88
+ ? { head: downgradeOperation(collection)(pathItem.head) }
89
+ : undefined),
90
+ ...(pathItem.patch
91
+ ? { patch: downgradeOperation(collection)(pathItem.patch) }
92
+ : undefined),
93
+ ...(pathItem.trace
94
+ ? { trace: downgradeOperation(collection)(pathItem.trace) }
95
+ : undefined),
96
+ ...(Object.keys(xAdditionalOperations).length > 0
97
+ ? { "x-additionalOperations": xAdditionalOperations }
98
+ : undefined),
99
+ // Remove v3.2-only properties from spread
100
+ query: undefined,
101
+ additionalOperations: undefined,
102
+ };
103
+ };
104
+
105
+ const downgradeOperation =
106
+ (collection: IComponentsCollection) =>
107
+ (input: OpenApi.IOperation): SwaggerV2.IOperation => ({
108
+ ...input,
109
+ parameters:
110
+ input.parameters !== undefined || input.requestBody !== undefined
111
+ ? [
112
+ ...(input.parameters ?? []).map(downgradeParameter(collection)),
113
+ ...(input.requestBody
114
+ ? [downgradeRequestBody(collection)(input.requestBody)]
115
+ : []),
116
+ ]
117
+ : undefined,
118
+ responses: input.responses
119
+ ? Object.fromEntries(
120
+ Object.entries(input.responses)
121
+ .filter(([_, v]) => v !== undefined)
122
+ .map(([key, value]) => [
123
+ key,
124
+ downgradeResponse(collection)(value),
125
+ ]),
126
+ )
127
+ : undefined,
128
+ ...{
129
+ requestBody: undefined,
130
+ servers: undefined,
131
+ },
132
+ });
133
+
134
+ const downgradeParameter =
135
+ (collection: IComponentsCollection) =>
136
+ (
137
+ input: OpenApi.IOperation.IParameter,
138
+ i: number,
139
+ ): SwaggerV2.IOperation.IParameter =>
140
+ ({
141
+ ...downgradeSchema(collection)(input.schema),
142
+ ...input,
143
+ in: input.in === "querystring" ? "query" : input.in,
144
+ required: (input.schema as any)?.required,
145
+ schema: undefined,
146
+ name: input.name ?? `p${i}`,
147
+ ...{
148
+ example: undefined,
149
+ examples: undefined,
150
+ },
151
+ }) as any;
152
+
153
+ const downgradeRequestBody =
154
+ (collection: IComponentsCollection) =>
155
+ (
156
+ input: OpenApi.IOperation.IRequestBody,
157
+ ): SwaggerV2.IOperation.IParameter => ({
158
+ name: "body",
159
+ in: "body",
160
+ description: input.description,
161
+ required: input.required,
162
+ schema: downgradeSchema(collection)(
163
+ Object.values(input.content ?? {})[0]?.schema ?? {},
164
+ ),
165
+ });
166
+
167
+ const downgradeResponse =
168
+ (collection: IComponentsCollection) =>
169
+ (input: OpenApi.IOperation.IResponse): SwaggerV2.IOperation.IResponse => ({
170
+ description: input.description,
171
+ schema: downgradeSchema(collection)(
172
+ Object.values(input.content ?? {})[0]?.schema ?? {},
173
+ ),
174
+ headers: input.headers
175
+ ? Object.fromEntries(
176
+ Object.entries(input.headers)
177
+ .filter(([_, v]) => v !== undefined)
178
+ .map(([key, value]) => [
179
+ key,
180
+ {
181
+ ...value,
182
+ schema: downgradeSchema(collection)(value.schema),
183
+ ...{
184
+ example: undefined,
185
+ examples: undefined,
186
+ },
187
+ },
188
+ ]),
189
+ )
190
+ : undefined,
191
+ });
192
+
193
+ /* -----------------------------------------------------------
194
+ DEFINITIONS
195
+ ----------------------------------------------------------- */
196
+ export const downgradeComponents = (
197
+ input: OpenApi.IComponents,
198
+ ): IComponentsCollection => {
199
+ const collection: IComponentsCollection = {
200
+ original: input,
201
+ downgraded: {},
202
+ };
203
+ if (input.schemas) {
204
+ collection.downgraded.schemas = {};
205
+ for (const [key, value] of Object.entries(input.schemas))
206
+ if (value !== undefined)
207
+ collection.downgraded[key.split("/").pop()!] =
208
+ downgradeSchema(collection)(value);
209
+ }
210
+ return collection;
211
+ };
212
+
213
+ export const downgradeSchema =
214
+ (collection: IComponentsCollection) =>
215
+ (input: OpenApi.IJsonSchema): SwaggerV2.IJsonSchema => {
216
+ const nullable: boolean = isNullable(new Set())(collection.original)(
217
+ input,
218
+ );
219
+ const union: SwaggerV2.IJsonSchema[] = [];
220
+ const attribute: SwaggerV2.IJsonSchema.__IAttribute = {
221
+ title: input.title,
222
+ description: input.description,
223
+ deprecated: input.deprecated,
224
+ readOnly: input.readOnly,
225
+ example: input.example,
226
+ examples: input.examples ? Object.values(input.examples) : undefined,
227
+ ...Object.fromEntries(
228
+ Object.entries(input).filter(
229
+ ([key, value]) => key.startsWith("x-") && value !== undefined,
230
+ ),
231
+ ),
232
+ };
233
+ const visit = (schema: OpenApi.IJsonSchema): void => {
234
+ if (OpenApiTypeChecker.isBoolean(schema))
235
+ union.push({ type: "boolean" });
236
+ else if (
237
+ OpenApiTypeChecker.isBoolean(schema) ||
238
+ OpenApiTypeChecker.isInteger(schema) ||
239
+ OpenApiTypeChecker.isNumber(schema) ||
240
+ OpenApiTypeChecker.isString(schema)
241
+ )
242
+ union.push({
243
+ ...schema,
244
+ examples: schema.examples
245
+ ? Object.values(schema.examples)
246
+ : undefined,
247
+ });
248
+ else if (OpenApiTypeChecker.isReference(schema))
249
+ union.push({ $ref: `#/definitions/${schema.$ref.split("/").pop()}` });
250
+ else if (OpenApiTypeChecker.isArray(schema))
251
+ union.push({
252
+ ...schema,
253
+ items: downgradeSchema(collection)(schema.items),
254
+ examples: schema.examples
255
+ ? Object.values(schema.examples)
256
+ : undefined,
257
+ });
258
+ else if (OpenApiTypeChecker.isTuple(schema))
259
+ union.push({
260
+ ...schema,
261
+ items: ((): SwaggerV2.IJsonSchema => {
262
+ if (schema.additionalItems === true) return {};
263
+ const elements = [
264
+ ...schema.prefixItems,
265
+ ...(typeof schema.additionalItems === "object"
266
+ ? [downgradeSchema(collection)(schema.additionalItems)]
267
+ : []),
268
+ ];
269
+ if (elements.length === 0) return {};
270
+ return {
271
+ "x-oneOf": elements.map(downgradeSchema(collection) as any),
272
+ };
273
+ })(),
274
+ minItems: schema.prefixItems.length,
275
+ maxItems:
276
+ !!schema.additionalItems === true
277
+ ? undefined
278
+ : schema.prefixItems.length,
279
+ ...{
280
+ prefixItems: undefined,
281
+ additionalItems: undefined,
282
+ },
283
+ examples: schema.examples
284
+ ? Object.values(schema.examples)
285
+ : undefined,
286
+ });
287
+ else if (OpenApiTypeChecker.isObject(schema))
288
+ union.push({
289
+ ...schema,
290
+ properties: schema.properties
291
+ ? Object.fromEntries(
292
+ Object.entries(schema.properties)
293
+ .filter(([_, v]) => v !== undefined)
294
+ .map(([key, value]) => [
295
+ key,
296
+ downgradeSchema(collection)(value),
297
+ ]),
298
+ )
299
+ : undefined,
300
+ additionalProperties:
301
+ typeof schema.additionalProperties === "object"
302
+ ? downgradeSchema(collection)(schema.additionalProperties)
303
+ : schema.additionalProperties,
304
+ required: schema.required,
305
+ examples: schema.examples
306
+ ? Object.values(schema.examples)
307
+ : undefined,
308
+ });
309
+ else if (OpenApiTypeChecker.isOneOf(schema))
310
+ schema.oneOf.forEach(visit);
311
+ };
312
+ const visitConstant = (schema: OpenApi.IJsonSchema): void => {
313
+ const insert = (value: any): void => {
314
+ const matched: SwaggerV2.IJsonSchema.INumber | undefined = union.find(
315
+ (u) =>
316
+ (u as SwaggerV2.IJsonSchema.__ISignificant<any>).type === value,
317
+ ) as SwaggerV2.IJsonSchema.INumber | undefined;
318
+ if (matched !== undefined) {
319
+ matched.enum ??= [];
320
+ matched.enum.push(value);
321
+ } else union.push({ type: typeof value as "number", enum: [value] });
322
+ if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const);
323
+ else if (OpenApiTypeChecker.isOneOf(schema))
324
+ schema.oneOf.forEach(insert);
325
+ };
326
+ };
327
+
328
+ visit(input);
329
+ visitConstant(input);
330
+ if (nullable) {
331
+ for (const u of union)
332
+ if (OpenApiTypeChecker.isReference(u as any))
333
+ downgradeNullableReference(new Set())(collection)(u as any);
334
+ else (u as SwaggerV2.IJsonSchema.IArray)["x-nullable"] = true;
335
+ }
336
+
337
+ if (nullable === true && union.length === 0)
338
+ return { type: "null", ...attribute };
339
+ return {
340
+ ...(union.length === 0
341
+ ? { type: undefined }
342
+ : union.length === 1
343
+ ? { ...union[0] }
344
+ : { "x-oneOf": union }),
345
+ ...attribute,
346
+ ...(union.length > 1 ? { discriminator: undefined } : {}),
347
+ };
348
+ };
349
+
350
+ const downgradeNullableReference =
351
+ (visited: Set<string>) =>
352
+ (collection: IComponentsCollection) =>
353
+ (schema: SwaggerV2.IJsonSchema.IReference): void => {
354
+ const key: string = schema.$ref.split("/").pop()!;
355
+ if (key.endsWith(".Nullable")) return;
356
+
357
+ const found: OpenApi.IJsonSchema | undefined =
358
+ collection.original.schemas?.[key];
359
+ if (found === undefined) return;
360
+ else if (isNullable(visited)(collection.original)(found) === true) return;
361
+ else if (collection.downgraded[`${key}.Nullable`] === undefined) {
362
+ collection.downgraded[`${key}.Nullable`] = {};
363
+ collection.downgraded[`${key}.Nullable`] = downgradeSchema(collection)(
364
+ OpenApiTypeChecker.isOneOf(found)
365
+ ? {
366
+ ...found,
367
+ oneOf: [...found.oneOf, { type: "null" }],
368
+ }
369
+ : {
370
+ title: found.title,
371
+ description: found.description,
372
+ example: found.example,
373
+ examples: found.examples
374
+ ? Object.values(found.examples)
375
+ : undefined,
376
+ ...Object.fromEntries(
377
+ Object.entries(found).filter(
378
+ ([key, value]) =>
379
+ key.startsWith("x-") && value !== undefined,
380
+ ),
381
+ ),
382
+ oneOf: [found, { type: "null" }],
383
+ },
384
+ );
385
+ }
386
+ schema.$ref += ".Nullable";
387
+ };
388
+
389
+ const downgradeSecurityScheme = (
390
+ input: OpenApi.ISecurityScheme,
391
+ ): SwaggerV2.ISecurityDefinition[] => {
392
+ if (input.type === "apiKey") return [input];
393
+ else if (input.type === "http")
394
+ if (input.scheme === "basic")
395
+ return [{ type: "basic", description: input.description }];
396
+ else return [];
397
+ else if (input.type === "oauth2") {
398
+ const output: SwaggerV2.ISecurityDefinition[] = [];
399
+ if (input.flows.implicit)
400
+ output.push({
401
+ type: "oauth2",
402
+ flow: "implicit",
403
+ authorizationUrl: input.flows.implicit.authorizationUrl,
404
+ scopes: input.flows.implicit.scopes,
405
+ });
406
+ if (input.flows.password)
407
+ output.push({
408
+ type: "oauth2",
409
+ flow: "password",
410
+ tokenUrl: input.flows.password.tokenUrl,
411
+ scopes: input.flows.password.scopes,
412
+ });
413
+ if (input.flows.clientCredentials)
414
+ output.push({
415
+ type: "oauth2",
416
+ flow: "application",
417
+ tokenUrl: input.flows.clientCredentials.tokenUrl,
418
+ scopes: input.flows.clientCredentials.scopes,
419
+ });
420
+ if (input.flows.authorizationCode)
421
+ output.push({
422
+ type: "oauth2",
423
+ flow: "accessCode",
424
+ authorizationUrl: input.flows.authorizationCode.authorizationUrl,
425
+ tokenUrl: input.flows.authorizationCode.tokenUrl,
426
+ scopes: input.flows.authorizationCode.scopes,
427
+ });
428
+ return output;
429
+ }
430
+ return [];
431
+ };
432
+
433
+ const isNullable =
434
+ (visited: Set<string>) =>
435
+ (components: OpenApi.IComponents) =>
436
+ (schema: OpenApi.IJsonSchema): boolean => {
437
+ if (OpenApiTypeChecker.isNull(schema)) return true;
438
+ else if (OpenApiTypeChecker.isReference(schema)) {
439
+ if (visited.has(schema.$ref)) return false;
440
+ visited.add(schema.$ref);
441
+ const key: string = schema.$ref.split("/").pop()!;
442
+ const next: OpenApi.IJsonSchema | undefined = components.schemas?.[key];
443
+ return next ? isNullable(visited)(components)(next) : false;
444
+ }
445
+ return (
446
+ OpenApiTypeChecker.isOneOf(schema) &&
447
+ schema.oneOf.some(isNullable(visited)(components))
448
+ );
449
+ };
450
+ }