@typia/utils 12.0.0-dev.20260316 → 12.0.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.
@@ -1,547 +1,547 @@
1
- import { IJsonSchemaAttribute, OpenApi, SwaggerV2 } from "@typia/interface";
2
-
3
- import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
- import { SwaggerV2TypeChecker } from "../../validators/SwaggerV2TypeChecker";
5
- import { OpenApiExclusiveEmender } from "./OpenApiExclusiveEmender";
6
-
7
- export namespace SwaggerV2Upgrader {
8
- export const convert = (input: SwaggerV2.IDocument): OpenApi.IDocument => ({
9
- openapi: "3.2.0",
10
- info: input.info,
11
- components: convertComponents(input),
12
- paths: input.paths
13
- ? Object.fromEntries(
14
- Object.entries(input.paths)
15
- .filter(([_, v]) => v !== undefined)
16
- .map(
17
- ([key, value]) => [key, convertPathItem(input)(value)] as const,
18
- ),
19
- )
20
- : undefined,
21
- servers: input.host
22
- ? [
23
- {
24
- url: input.host,
25
- },
26
- ]
27
- : undefined,
28
- security: input.security,
29
- tags: input.tags,
30
- "x-typia-emended-v12": true,
31
- });
32
-
33
- /* -----------------------------------------------------------
34
- OPERATORS
35
- ----------------------------------------------------------- */
36
- const convertPathItem =
37
- (doc: SwaggerV2.IDocument) =>
38
- (pathItem: SwaggerV2.IPath): OpenApi.IPath => {
39
- // Convert x-additionalOperations to additionalOperations
40
- // Promote "query" to standard method (it's a v3.2 standard method)
41
- const xAdditional = pathItem["x-additionalOperations"];
42
- const queryOp = xAdditional?.["query"];
43
- const additionalOperations = xAdditional
44
- ? Object.fromEntries(
45
- Object.entries(xAdditional)
46
- .filter(([key, v]) => key !== "query" && v !== undefined)
47
- .map(([key, value]) => [
48
- key,
49
- convertOperation(doc)(pathItem)(value),
50
- ]),
51
- )
52
- : undefined;
53
-
54
- return {
55
- ...(pathItem as any),
56
- ...(pathItem.get
57
- ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
58
- : undefined),
59
- ...(pathItem.put
60
- ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
61
- : undefined),
62
- ...(pathItem.post
63
- ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
64
- : undefined),
65
- ...(pathItem.delete
66
- ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
67
- : undefined),
68
- ...(pathItem.options
69
- ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
70
- : undefined),
71
- ...(pathItem.head
72
- ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
73
- : undefined),
74
- ...(pathItem.patch
75
- ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
76
- : undefined),
77
- ...(pathItem.trace
78
- ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
79
- : undefined),
80
- ...(queryOp
81
- ? { query: convertOperation(doc)(pathItem)(queryOp) }
82
- : undefined),
83
- ...(additionalOperations && Object.keys(additionalOperations).length > 0
84
- ? { additionalOperations }
85
- : undefined),
86
- "x-additionalOperations": undefined,
87
- };
88
- };
89
-
90
- const convertOperation =
91
- (doc: SwaggerV2.IDocument) =>
92
- (pathItem: SwaggerV2.IPath) =>
93
- (input: SwaggerV2.IOperation): OpenApi.IOperation => ({
94
- ...input,
95
- parameters:
96
- pathItem.parameters !== undefined || input.parameters !== undefined
97
- ? (
98
- [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
99
- .map((p) =>
100
- SwaggerV2TypeChecker.isReference(p)
101
- ? doc.parameters?.[p.$ref.split("/").pop() ?? ""]!
102
- : p,
103
- )
104
- .filter(
105
- (p) =>
106
- p !== undefined &&
107
- p.in !== "body" &&
108
- (p as SwaggerV2.IOperation.IBodyParameter).schema ===
109
- undefined,
110
- ) as SwaggerV2.IOperation.IGeneralParameter[]
111
- ).map(convertParameter(doc.definitions ?? {}))
112
- : undefined,
113
- requestBody: (() => {
114
- const found: SwaggerV2.IOperation.IBodyParameter | undefined =
115
- input.parameters?.find((p) => {
116
- if (SwaggerV2TypeChecker.isReference(p))
117
- p = doc.parameters?.[p.$ref.split("/").pop() ?? ""]!;
118
- return (
119
- (p as SwaggerV2.IOperation.IBodyParameter)?.schema !== undefined
120
- );
121
- }) as SwaggerV2.IOperation.IBodyParameter | undefined;
122
- return found
123
- ? convertRequestBody(doc.definitions ?? {})(found)
124
- : undefined;
125
- })(),
126
- responses: input.responses
127
- ? Object.fromEntries(
128
- Object.entries(input.responses)
129
- .filter(([_, v]) => v !== undefined)
130
- .map(
131
- ([key, value]) => [key, convertResponse(doc)(value)!] as const,
132
- )
133
- .filter(([_, v]) => v !== undefined),
134
- )
135
- : undefined,
136
- });
137
-
138
- const convertParameter =
139
- (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
140
- (
141
- input: SwaggerV2.IOperation.IGeneralParameter,
142
- ): OpenApi.IOperation.IParameter => ({
143
- name: input.name,
144
- in: input.in as any,
145
- description: input.description,
146
- schema: convertSchema(definitions)(input),
147
- required: true,
148
- });
149
- const convertRequestBody =
150
- (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
151
- (
152
- input: SwaggerV2.IOperation.IBodyParameter,
153
- ): OpenApi.IOperation.IRequestBody => ({
154
- description: input.description,
155
- content: {
156
- "application/json": {
157
- schema: convertSchema(definitions)(input.schema),
158
- },
159
- },
160
- });
161
-
162
- const convertResponse =
163
- (doc: SwaggerV2.IDocument) =>
164
- (
165
- input:
166
- | SwaggerV2.IOperation.IResponse
167
- | SwaggerV2.IJsonSchema.IReference<`#/definitions/responses/${string}`>,
168
- ): OpenApi.IOperation.IResponse | undefined => {
169
- if (SwaggerV2TypeChecker.isReference(input)) {
170
- const found: SwaggerV2.IOperation.IResponse | undefined =
171
- doc.responses?.[input.$ref.split("/").pop() ?? ""]!;
172
- if (found === undefined) return undefined;
173
- input = found;
174
- }
175
- return {
176
- description: input.description,
177
- content: input.schema
178
- ? {
179
- "application/json": {
180
- schema: convertSchema(doc.definitions ?? {})(input.schema),
181
- example: input.example,
182
- },
183
- }
184
- : undefined,
185
- headers: input.headers
186
- ? Object.fromEntries(
187
- Object.entries(input.headers)
188
- .filter(([_, v]) => v !== undefined)
189
- .map(
190
- ([key, value]) =>
191
- [
192
- key,
193
- {
194
- schema: convertSchema(doc.definitions ?? {})(value),
195
- in: "header",
196
- },
197
- ] as const,
198
- ),
199
- )
200
- : undefined,
201
- };
202
- };
203
-
204
- /* -----------------------------------------------------------
205
- DEFINITIONS
206
- ----------------------------------------------------------- */
207
- export const convertComponents = (
208
- input: SwaggerV2.IDocument,
209
- ): OpenApi.IComponents => ({
210
- schemas: Object.fromEntries(
211
- Object.entries(input.definitions ?? {})
212
- .filter(([_, v]) => v !== undefined)
213
- .map(([key, value]) => [
214
- key,
215
- convertSchema(input.definitions ?? {})(value),
216
- ]),
217
- ),
218
- securitySchemes: input.securityDefinitions
219
- ? Object.fromEntries(
220
- Object.entries(input.securityDefinitions)
221
- .filter(([_, v]) => v !== undefined)
222
- .map(([key, value]) => [key, convertSecurityScheme(value)])
223
- .filter(([_, v]) => v !== undefined),
224
- )
225
- : undefined,
226
- });
227
-
228
- const convertSecurityScheme = (
229
- input: SwaggerV2.ISecurityDefinition,
230
- ): OpenApi.ISecurityScheme => {
231
- if (input.type === "apiKey") return input;
232
- else if (input.type === "basic")
233
- return {
234
- type: "http",
235
- scheme: "basic",
236
- description: input.description,
237
- };
238
- else if (input.type === "oauth2")
239
- if (input.flow === "implicit")
240
- return {
241
- type: "oauth2",
242
- description: input.description,
243
- flows: {
244
- implicit: {
245
- authorizationUrl: input.authorizationUrl,
246
- scopes: input.scopes,
247
- },
248
- },
249
- };
250
- else if (input.flow === "accessCode")
251
- return {
252
- type: "oauth2",
253
- description: input.description,
254
- flows: {
255
- authorizationCode: {
256
- authorizationUrl: input.authorizationUrl,
257
- tokenUrl: input.tokenUrl,
258
- scopes: input.scopes,
259
- },
260
- },
261
- };
262
- else if (input.flow === "password")
263
- return {
264
- type: "oauth2",
265
- description: input.description,
266
- flows: {
267
- password: {
268
- tokenUrl: input.tokenUrl,
269
- scopes: input.scopes,
270
- },
271
- },
272
- };
273
- else if (input.flow === "application")
274
- return {
275
- type: "oauth2",
276
- description: input.description,
277
- flows: {
278
- clientCredentials: {
279
- tokenUrl: input.tokenUrl,
280
- scopes: input.scopes,
281
- },
282
- },
283
- };
284
- else return undefined!;
285
- return undefined!;
286
- };
287
-
288
- export const convertSchema =
289
- (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
290
- (input: SwaggerV2.IJsonSchema): OpenApi.IJsonSchema => {
291
- const nullable: { value: boolean; default?: null } = {
292
- value: false,
293
- default: undefined,
294
- };
295
- const union: OpenApi.IJsonSchema[] = [];
296
- const attribute: IJsonSchemaAttribute = {
297
- title: input.title,
298
- description: input.description,
299
- deprecated: input.deprecated,
300
- readOnly: input.readOnly,
301
- example: input.example,
302
- examples: input.examples
303
- ? Object.fromEntries(input.examples.map((v, i) => [`v${i}`, v]))
304
- : undefined,
305
- ...Object.fromEntries(
306
- Object.entries(input).filter(
307
- ([key, value]) => key.startsWith("x-") && value !== undefined,
308
- ),
309
- ),
310
- };
311
- const visit = (schema: SwaggerV2.IJsonSchema): void => {
312
- // NULLABLE PROPERTY
313
- if (
314
- (schema as SwaggerV2.IJsonSchema.__ISignificant<any>)[
315
- "x-nullable"
316
- ] === true
317
- ) {
318
- nullable.value ||= true;
319
- if ((schema as SwaggerV2.IJsonSchema.INumber).default === null)
320
- nullable.default = null;
321
- }
322
- if (
323
- Array.isArray((schema as SwaggerV2.IJsonSchema.INumber).enum) &&
324
- (schema as SwaggerV2.IJsonSchema.INumber).enum?.length &&
325
- (schema as SwaggerV2.IJsonSchema.INumber).enum?.some(
326
- (e) => e === null,
327
- )
328
- )
329
- nullable.value ||= true;
330
- // UNION TYPE CASE
331
- if (SwaggerV2TypeChecker.isAnyOf(schema))
332
- schema["x-anyOf"].forEach(visit);
333
- else if (SwaggerV2TypeChecker.isOneOf(schema))
334
- schema["x-oneOf"].forEach(visit);
335
- else if (SwaggerV2TypeChecker.isAllOf(schema))
336
- if (schema.allOf.length === 1) visit(schema.allOf[0]!);
337
- else union.push(convertAllOfSchema(definitions)(schema));
338
- // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
339
- else if (
340
- SwaggerV2TypeChecker.isBoolean(schema) ||
341
- SwaggerV2TypeChecker.isInteger(schema) ||
342
- SwaggerV2TypeChecker.isNumber(schema) ||
343
- SwaggerV2TypeChecker.isString(schema)
344
- )
345
- if (
346
- schema.enum?.length &&
347
- schema.enum.filter((e) => e !== null).length
348
- )
349
- union.push(
350
- ...schema.enum
351
- .filter((v) => v !== null)
352
- .map((value) => ({ const: value })),
353
- );
354
- else if (
355
- SwaggerV2TypeChecker.isInteger(schema) ||
356
- SwaggerV2TypeChecker.isNumber(schema)
357
- )
358
- union.push(
359
- OpenApiExclusiveEmender.emend({
360
- ...schema,
361
- default: (schema.default ?? undefined) satisfies
362
- | boolean
363
- | number
364
- | string
365
- | undefined as any,
366
- examples: schema.examples
367
- ? Object.fromEntries(
368
- schema.examples.map((v, i) => [`v${i}`, v]),
369
- )
370
- : undefined,
371
- exclusiveMinimum:
372
- typeof schema.exclusiveMinimum === "boolean"
373
- ? schema.exclusiveMinimum === true
374
- ? schema.minimum
375
- : undefined
376
- : schema.exclusiveMinimum,
377
- exclusiveMaximum:
378
- typeof schema.exclusiveMaximum === "boolean"
379
- ? schema.exclusiveMaximum === true
380
- ? schema.maximum
381
- : undefined
382
- : schema.exclusiveMaximum,
383
- minimum:
384
- schema.exclusiveMinimum === true ? undefined : schema.minimum,
385
- maximum:
386
- schema.exclusiveMaximum === true ? undefined : schema.maximum,
387
- ...{ enum: undefined },
388
- }),
389
- );
390
- else
391
- union.push({
392
- ...schema,
393
- default: (schema.default ?? undefined) satisfies
394
- | boolean
395
- | number
396
- | string
397
- | undefined as any,
398
- examples: schema.examples
399
- ? Object.fromEntries(
400
- schema.examples.map((v, i) => [`v${i}`, v]),
401
- )
402
- : undefined,
403
- ...{ enum: undefined },
404
- });
405
- // INSTANCE TYPE CASE
406
- else if (SwaggerV2TypeChecker.isArray(schema))
407
- union.push({
408
- ...schema,
409
- items: convertSchema(definitions)(schema.items),
410
- examples: schema.examples
411
- ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
412
- : undefined,
413
- });
414
- else if (SwaggerV2TypeChecker.isObject(schema))
415
- union.push({
416
- ...schema,
417
- ...{
418
- properties: schema.properties
419
- ? Object.fromEntries(
420
- Object.entries(schema.properties)
421
- .filter(([_, v]) => v !== undefined)
422
- .map(([key, value]) => [
423
- key,
424
- convertSchema(definitions)(value),
425
- ]),
426
- )
427
- : {},
428
- additionalProperties: schema.additionalProperties
429
- ? typeof schema.additionalProperties === "object" &&
430
- schema.additionalProperties !== null
431
- ? convertSchema(definitions)(schema.additionalProperties)
432
- : schema.additionalProperties
433
- : undefined,
434
- },
435
- examples: schema.examples
436
- ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
437
- : undefined,
438
- required: schema.required ?? [],
439
- });
440
- else if (SwaggerV2TypeChecker.isReference(schema))
441
- union.push({
442
- ...schema,
443
- $ref: schema.$ref.replace(
444
- "#/definitions/",
445
- "#/components/schemas/",
446
- ),
447
- examples: schema.examples
448
- ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
449
- : undefined,
450
- });
451
- else
452
- union.push({
453
- ...schema,
454
- examples: schema.examples
455
- ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
456
- : undefined,
457
- });
458
- };
459
-
460
- visit(input);
461
- if (
462
- nullable.value === true &&
463
- !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
464
- )
465
- union.push({
466
- type: "null",
467
- default: nullable.default,
468
- });
469
- if (
470
- union.length === 2 &&
471
- union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
472
- ) {
473
- const type: OpenApi.IJsonSchema = union.filter(
474
- (x) => OpenApiTypeChecker.isNull(x) === false,
475
- )[0]!;
476
- for (const key of [
477
- "title",
478
- "description",
479
- "deprecated",
480
- "example",
481
- "examples",
482
- ] as const)
483
- if (type[key] !== undefined) delete type[key];
484
- }
485
- return {
486
- ...(union.length === 0
487
- ? { type: undefined }
488
- : union.length === 1
489
- ? { ...union[0] }
490
- : { oneOf: union.map((u) => ({ ...u, "x-nullable": undefined })) }),
491
- ...attribute,
492
- ...{ "x-nullable": undefined },
493
- };
494
- };
495
-
496
- const convertAllOfSchema =
497
- (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
498
- (input: SwaggerV2.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
499
- const objects: Array<SwaggerV2.IJsonSchema.IObject | null> =
500
- input.allOf.map((schema) => retrieveObject(definitions)(schema));
501
- if (objects.some((obj) => obj === null))
502
- return {
503
- type: undefined,
504
- ...{
505
- allOf: undefined,
506
- },
507
- };
508
- return {
509
- ...input,
510
- type: "object",
511
- properties: Object.fromEntries(
512
- objects
513
- .map((o) => Object.entries(o?.properties ?? {}))
514
- .flat()
515
- .map(
516
- ([key, value]) =>
517
- [key, convertSchema(definitions)(value)] as const,
518
- ),
519
- ),
520
- ...{
521
- allOf: undefined,
522
- required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
523
- },
524
- };
525
- };
526
-
527
- const retrieveObject =
528
- (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
529
- (
530
- input: SwaggerV2.IJsonSchema,
531
- visited: Set<SwaggerV2.IJsonSchema> = new Set(),
532
- ): SwaggerV2.IJsonSchema.IObject | null => {
533
- if (SwaggerV2TypeChecker.isObject(input))
534
- return input.properties !== undefined && !input.additionalProperties
535
- ? input
536
- : null;
537
- else if (visited.has(input)) return null;
538
- else visited.add(input);
539
-
540
- if (SwaggerV2TypeChecker.isReference(input))
541
- return retrieveObject(definitions)(
542
- definitions?.[input.$ref.split("/").pop() ?? ""] ?? {},
543
- visited,
544
- );
545
- return null;
546
- };
547
- }
1
+ import { IJsonSchemaAttribute, OpenApi, SwaggerV2 } from "@typia/interface";
2
+
3
+ import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
+ import { SwaggerV2TypeChecker } from "../../validators/SwaggerV2TypeChecker";
5
+ import { OpenApiExclusiveEmender } from "./OpenApiExclusiveEmender";
6
+
7
+ export namespace SwaggerV2Upgrader {
8
+ export const convert = (input: SwaggerV2.IDocument): OpenApi.IDocument => ({
9
+ openapi: "3.2.0",
10
+ info: input.info,
11
+ components: convertComponents(input),
12
+ paths: input.paths
13
+ ? Object.fromEntries(
14
+ Object.entries(input.paths)
15
+ .filter(([_, v]) => v !== undefined)
16
+ .map(
17
+ ([key, value]) => [key, convertPathItem(input)(value)] as const,
18
+ ),
19
+ )
20
+ : undefined,
21
+ servers: input.host
22
+ ? [
23
+ {
24
+ url: input.host,
25
+ },
26
+ ]
27
+ : undefined,
28
+ security: input.security,
29
+ tags: input.tags,
30
+ "x-typia-emended-v12": true,
31
+ });
32
+
33
+ /* -----------------------------------------------------------
34
+ OPERATORS
35
+ ----------------------------------------------------------- */
36
+ const convertPathItem =
37
+ (doc: SwaggerV2.IDocument) =>
38
+ (pathItem: SwaggerV2.IPath): OpenApi.IPath => {
39
+ // Convert x-additionalOperations to additionalOperations
40
+ // Promote "query" to standard method (it's a v3.2 standard method)
41
+ const xAdditional = pathItem["x-additionalOperations"];
42
+ const queryOp = xAdditional?.["query"];
43
+ const additionalOperations = xAdditional
44
+ ? Object.fromEntries(
45
+ Object.entries(xAdditional)
46
+ .filter(([key, v]) => key !== "query" && v !== undefined)
47
+ .map(([key, value]) => [
48
+ key,
49
+ convertOperation(doc)(pathItem)(value),
50
+ ]),
51
+ )
52
+ : undefined;
53
+
54
+ return {
55
+ ...(pathItem as any),
56
+ ...(pathItem.get
57
+ ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
58
+ : undefined),
59
+ ...(pathItem.put
60
+ ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
61
+ : undefined),
62
+ ...(pathItem.post
63
+ ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
64
+ : undefined),
65
+ ...(pathItem.delete
66
+ ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
67
+ : undefined),
68
+ ...(pathItem.options
69
+ ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
70
+ : undefined),
71
+ ...(pathItem.head
72
+ ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
73
+ : undefined),
74
+ ...(pathItem.patch
75
+ ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
76
+ : undefined),
77
+ ...(pathItem.trace
78
+ ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
79
+ : undefined),
80
+ ...(queryOp
81
+ ? { query: convertOperation(doc)(pathItem)(queryOp) }
82
+ : undefined),
83
+ ...(additionalOperations && Object.keys(additionalOperations).length > 0
84
+ ? { additionalOperations }
85
+ : undefined),
86
+ "x-additionalOperations": undefined,
87
+ };
88
+ };
89
+
90
+ const convertOperation =
91
+ (doc: SwaggerV2.IDocument) =>
92
+ (pathItem: SwaggerV2.IPath) =>
93
+ (input: SwaggerV2.IOperation): OpenApi.IOperation => ({
94
+ ...input,
95
+ parameters:
96
+ pathItem.parameters !== undefined || input.parameters !== undefined
97
+ ? (
98
+ [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
99
+ .map((p) =>
100
+ SwaggerV2TypeChecker.isReference(p)
101
+ ? doc.parameters?.[p.$ref.split("/").pop() ?? ""]!
102
+ : p,
103
+ )
104
+ .filter(
105
+ (p) =>
106
+ p !== undefined &&
107
+ p.in !== "body" &&
108
+ (p as SwaggerV2.IOperation.IBodyParameter).schema ===
109
+ undefined,
110
+ ) as SwaggerV2.IOperation.IGeneralParameter[]
111
+ ).map(convertParameter(doc.definitions ?? {}))
112
+ : undefined,
113
+ requestBody: (() => {
114
+ const found: SwaggerV2.IOperation.IBodyParameter | undefined =
115
+ input.parameters?.find((p) => {
116
+ if (SwaggerV2TypeChecker.isReference(p))
117
+ p = doc.parameters?.[p.$ref.split("/").pop() ?? ""]!;
118
+ return (
119
+ (p as SwaggerV2.IOperation.IBodyParameter)?.schema !== undefined
120
+ );
121
+ }) as SwaggerV2.IOperation.IBodyParameter | undefined;
122
+ return found
123
+ ? convertRequestBody(doc.definitions ?? {})(found)
124
+ : undefined;
125
+ })(),
126
+ responses: input.responses
127
+ ? Object.fromEntries(
128
+ Object.entries(input.responses)
129
+ .filter(([_, v]) => v !== undefined)
130
+ .map(
131
+ ([key, value]) => [key, convertResponse(doc)(value)!] as const,
132
+ )
133
+ .filter(([_, v]) => v !== undefined),
134
+ )
135
+ : undefined,
136
+ });
137
+
138
+ const convertParameter =
139
+ (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
140
+ (
141
+ input: SwaggerV2.IOperation.IGeneralParameter,
142
+ ): OpenApi.IOperation.IParameter => ({
143
+ name: input.name,
144
+ in: input.in as any,
145
+ description: input.description,
146
+ schema: convertSchema(definitions)(input),
147
+ required: true,
148
+ });
149
+ const convertRequestBody =
150
+ (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
151
+ (
152
+ input: SwaggerV2.IOperation.IBodyParameter,
153
+ ): OpenApi.IOperation.IRequestBody => ({
154
+ description: input.description,
155
+ content: {
156
+ "application/json": {
157
+ schema: convertSchema(definitions)(input.schema),
158
+ },
159
+ },
160
+ });
161
+
162
+ const convertResponse =
163
+ (doc: SwaggerV2.IDocument) =>
164
+ (
165
+ input:
166
+ | SwaggerV2.IOperation.IResponse
167
+ | SwaggerV2.IJsonSchema.IReference<`#/definitions/responses/${string}`>,
168
+ ): OpenApi.IOperation.IResponse | undefined => {
169
+ if (SwaggerV2TypeChecker.isReference(input)) {
170
+ const found: SwaggerV2.IOperation.IResponse | undefined =
171
+ doc.responses?.[input.$ref.split("/").pop() ?? ""]!;
172
+ if (found === undefined) return undefined;
173
+ input = found;
174
+ }
175
+ return {
176
+ description: input.description,
177
+ content: input.schema
178
+ ? {
179
+ "application/json": {
180
+ schema: convertSchema(doc.definitions ?? {})(input.schema),
181
+ example: input.example,
182
+ },
183
+ }
184
+ : undefined,
185
+ headers: input.headers
186
+ ? Object.fromEntries(
187
+ Object.entries(input.headers)
188
+ .filter(([_, v]) => v !== undefined)
189
+ .map(
190
+ ([key, value]) =>
191
+ [
192
+ key,
193
+ {
194
+ schema: convertSchema(doc.definitions ?? {})(value),
195
+ in: "header",
196
+ },
197
+ ] as const,
198
+ ),
199
+ )
200
+ : undefined,
201
+ };
202
+ };
203
+
204
+ /* -----------------------------------------------------------
205
+ DEFINITIONS
206
+ ----------------------------------------------------------- */
207
+ export const convertComponents = (
208
+ input: SwaggerV2.IDocument,
209
+ ): OpenApi.IComponents => ({
210
+ schemas: Object.fromEntries(
211
+ Object.entries(input.definitions ?? {})
212
+ .filter(([_, v]) => v !== undefined)
213
+ .map(([key, value]) => [
214
+ key,
215
+ convertSchema(input.definitions ?? {})(value),
216
+ ]),
217
+ ),
218
+ securitySchemes: input.securityDefinitions
219
+ ? Object.fromEntries(
220
+ Object.entries(input.securityDefinitions)
221
+ .filter(([_, v]) => v !== undefined)
222
+ .map(([key, value]) => [key, convertSecurityScheme(value)])
223
+ .filter(([_, v]) => v !== undefined),
224
+ )
225
+ : undefined,
226
+ });
227
+
228
+ const convertSecurityScheme = (
229
+ input: SwaggerV2.ISecurityDefinition,
230
+ ): OpenApi.ISecurityScheme => {
231
+ if (input.type === "apiKey") return input;
232
+ else if (input.type === "basic")
233
+ return {
234
+ type: "http",
235
+ scheme: "basic",
236
+ description: input.description,
237
+ };
238
+ else if (input.type === "oauth2")
239
+ if (input.flow === "implicit")
240
+ return {
241
+ type: "oauth2",
242
+ description: input.description,
243
+ flows: {
244
+ implicit: {
245
+ authorizationUrl: input.authorizationUrl,
246
+ scopes: input.scopes,
247
+ },
248
+ },
249
+ };
250
+ else if (input.flow === "accessCode")
251
+ return {
252
+ type: "oauth2",
253
+ description: input.description,
254
+ flows: {
255
+ authorizationCode: {
256
+ authorizationUrl: input.authorizationUrl,
257
+ tokenUrl: input.tokenUrl,
258
+ scopes: input.scopes,
259
+ },
260
+ },
261
+ };
262
+ else if (input.flow === "password")
263
+ return {
264
+ type: "oauth2",
265
+ description: input.description,
266
+ flows: {
267
+ password: {
268
+ tokenUrl: input.tokenUrl,
269
+ scopes: input.scopes,
270
+ },
271
+ },
272
+ };
273
+ else if (input.flow === "application")
274
+ return {
275
+ type: "oauth2",
276
+ description: input.description,
277
+ flows: {
278
+ clientCredentials: {
279
+ tokenUrl: input.tokenUrl,
280
+ scopes: input.scopes,
281
+ },
282
+ },
283
+ };
284
+ else return undefined!;
285
+ return undefined!;
286
+ };
287
+
288
+ export const convertSchema =
289
+ (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
290
+ (input: SwaggerV2.IJsonSchema): OpenApi.IJsonSchema => {
291
+ const nullable: { value: boolean; default?: null } = {
292
+ value: false,
293
+ default: undefined,
294
+ };
295
+ const union: OpenApi.IJsonSchema[] = [];
296
+ const attribute: IJsonSchemaAttribute = {
297
+ title: input.title,
298
+ description: input.description,
299
+ deprecated: input.deprecated,
300
+ readOnly: input.readOnly,
301
+ example: input.example,
302
+ examples: input.examples
303
+ ? Object.fromEntries(input.examples.map((v, i) => [`v${i}`, v]))
304
+ : undefined,
305
+ ...Object.fromEntries(
306
+ Object.entries(input).filter(
307
+ ([key, value]) => key.startsWith("x-") && value !== undefined,
308
+ ),
309
+ ),
310
+ };
311
+ const visit = (schema: SwaggerV2.IJsonSchema): void => {
312
+ // NULLABLE PROPERTY
313
+ if (
314
+ (schema as SwaggerV2.IJsonSchema.__ISignificant<any>)[
315
+ "x-nullable"
316
+ ] === true
317
+ ) {
318
+ nullable.value ||= true;
319
+ if ((schema as SwaggerV2.IJsonSchema.INumber).default === null)
320
+ nullable.default = null;
321
+ }
322
+ if (
323
+ Array.isArray((schema as SwaggerV2.IJsonSchema.INumber).enum) &&
324
+ (schema as SwaggerV2.IJsonSchema.INumber).enum?.length &&
325
+ (schema as SwaggerV2.IJsonSchema.INumber).enum?.some(
326
+ (e) => e === null,
327
+ )
328
+ )
329
+ nullable.value ||= true;
330
+ // UNION TYPE CASE
331
+ if (SwaggerV2TypeChecker.isAnyOf(schema))
332
+ schema["x-anyOf"].forEach(visit);
333
+ else if (SwaggerV2TypeChecker.isOneOf(schema))
334
+ schema["x-oneOf"].forEach(visit);
335
+ else if (SwaggerV2TypeChecker.isAllOf(schema))
336
+ if (schema.allOf.length === 1) visit(schema.allOf[0]!);
337
+ else union.push(convertAllOfSchema(definitions)(schema));
338
+ // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
339
+ else if (
340
+ SwaggerV2TypeChecker.isBoolean(schema) ||
341
+ SwaggerV2TypeChecker.isInteger(schema) ||
342
+ SwaggerV2TypeChecker.isNumber(schema) ||
343
+ SwaggerV2TypeChecker.isString(schema)
344
+ )
345
+ if (
346
+ schema.enum?.length &&
347
+ schema.enum.filter((e) => e !== null).length
348
+ )
349
+ union.push(
350
+ ...schema.enum
351
+ .filter((v) => v !== null)
352
+ .map((value) => ({ const: value })),
353
+ );
354
+ else if (
355
+ SwaggerV2TypeChecker.isInteger(schema) ||
356
+ SwaggerV2TypeChecker.isNumber(schema)
357
+ )
358
+ union.push(
359
+ OpenApiExclusiveEmender.emend({
360
+ ...schema,
361
+ default: (schema.default ?? undefined) satisfies
362
+ | boolean
363
+ | number
364
+ | string
365
+ | undefined as any,
366
+ examples: schema.examples
367
+ ? Object.fromEntries(
368
+ schema.examples.map((v, i) => [`v${i}`, v]),
369
+ )
370
+ : undefined,
371
+ exclusiveMinimum:
372
+ typeof schema.exclusiveMinimum === "boolean"
373
+ ? schema.exclusiveMinimum === true
374
+ ? schema.minimum
375
+ : undefined
376
+ : schema.exclusiveMinimum,
377
+ exclusiveMaximum:
378
+ typeof schema.exclusiveMaximum === "boolean"
379
+ ? schema.exclusiveMaximum === true
380
+ ? schema.maximum
381
+ : undefined
382
+ : schema.exclusiveMaximum,
383
+ minimum:
384
+ schema.exclusiveMinimum === true ? undefined : schema.minimum,
385
+ maximum:
386
+ schema.exclusiveMaximum === true ? undefined : schema.maximum,
387
+ ...{ enum: undefined },
388
+ }),
389
+ );
390
+ else
391
+ union.push({
392
+ ...schema,
393
+ default: (schema.default ?? undefined) satisfies
394
+ | boolean
395
+ | number
396
+ | string
397
+ | undefined as any,
398
+ examples: schema.examples
399
+ ? Object.fromEntries(
400
+ schema.examples.map((v, i) => [`v${i}`, v]),
401
+ )
402
+ : undefined,
403
+ ...{ enum: undefined },
404
+ });
405
+ // INSTANCE TYPE CASE
406
+ else if (SwaggerV2TypeChecker.isArray(schema))
407
+ union.push({
408
+ ...schema,
409
+ items: convertSchema(definitions)(schema.items),
410
+ examples: schema.examples
411
+ ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
412
+ : undefined,
413
+ });
414
+ else if (SwaggerV2TypeChecker.isObject(schema))
415
+ union.push({
416
+ ...schema,
417
+ ...{
418
+ properties: schema.properties
419
+ ? Object.fromEntries(
420
+ Object.entries(schema.properties)
421
+ .filter(([_, v]) => v !== undefined)
422
+ .map(([key, value]) => [
423
+ key,
424
+ convertSchema(definitions)(value),
425
+ ]),
426
+ )
427
+ : {},
428
+ additionalProperties: schema.additionalProperties
429
+ ? typeof schema.additionalProperties === "object" &&
430
+ schema.additionalProperties !== null
431
+ ? convertSchema(definitions)(schema.additionalProperties)
432
+ : schema.additionalProperties
433
+ : undefined,
434
+ },
435
+ examples: schema.examples
436
+ ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
437
+ : undefined,
438
+ required: schema.required ?? [],
439
+ });
440
+ else if (SwaggerV2TypeChecker.isReference(schema))
441
+ union.push({
442
+ ...schema,
443
+ $ref: schema.$ref.replace(
444
+ "#/definitions/",
445
+ "#/components/schemas/",
446
+ ),
447
+ examples: schema.examples
448
+ ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
449
+ : undefined,
450
+ });
451
+ else
452
+ union.push({
453
+ ...schema,
454
+ examples: schema.examples
455
+ ? Object.fromEntries(schema.examples.map((v, i) => [`v${i}`, v]))
456
+ : undefined,
457
+ });
458
+ };
459
+
460
+ visit(input);
461
+ if (
462
+ nullable.value === true &&
463
+ !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
464
+ )
465
+ union.push({
466
+ type: "null",
467
+ default: nullable.default,
468
+ });
469
+ if (
470
+ union.length === 2 &&
471
+ union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
472
+ ) {
473
+ const type: OpenApi.IJsonSchema = union.filter(
474
+ (x) => OpenApiTypeChecker.isNull(x) === false,
475
+ )[0]!;
476
+ for (const key of [
477
+ "title",
478
+ "description",
479
+ "deprecated",
480
+ "example",
481
+ "examples",
482
+ ] as const)
483
+ if (type[key] !== undefined) delete type[key];
484
+ }
485
+ return {
486
+ ...(union.length === 0
487
+ ? { type: undefined }
488
+ : union.length === 1
489
+ ? { ...union[0] }
490
+ : { oneOf: union.map((u) => ({ ...u, "x-nullable": undefined })) }),
491
+ ...attribute,
492
+ ...{ "x-nullable": undefined },
493
+ };
494
+ };
495
+
496
+ const convertAllOfSchema =
497
+ (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
498
+ (input: SwaggerV2.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
499
+ const objects: Array<SwaggerV2.IJsonSchema.IObject | null> =
500
+ input.allOf.map((schema) => retrieveObject(definitions)(schema));
501
+ if (objects.some((obj) => obj === null))
502
+ return {
503
+ type: undefined,
504
+ ...{
505
+ allOf: undefined,
506
+ },
507
+ };
508
+ return {
509
+ ...input,
510
+ type: "object",
511
+ properties: Object.fromEntries(
512
+ objects
513
+ .map((o) => Object.entries(o?.properties ?? {}))
514
+ .flat()
515
+ .map(
516
+ ([key, value]) =>
517
+ [key, convertSchema(definitions)(value)] as const,
518
+ ),
519
+ ),
520
+ ...{
521
+ allOf: undefined,
522
+ required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
523
+ },
524
+ };
525
+ };
526
+
527
+ const retrieveObject =
528
+ (definitions: Record<string, SwaggerV2.IJsonSchema>) =>
529
+ (
530
+ input: SwaggerV2.IJsonSchema,
531
+ visited: Set<SwaggerV2.IJsonSchema> = new Set(),
532
+ ): SwaggerV2.IJsonSchema.IObject | null => {
533
+ if (SwaggerV2TypeChecker.isObject(input))
534
+ return input.properties !== undefined && !input.additionalProperties
535
+ ? input
536
+ : null;
537
+ else if (visited.has(input)) return null;
538
+ else visited.add(input);
539
+
540
+ if (SwaggerV2TypeChecker.isReference(input))
541
+ return retrieveObject(definitions)(
542
+ definitions?.[input.$ref.split("/").pop() ?? ""] ?? {},
543
+ visited,
544
+ );
545
+ return null;
546
+ };
547
+ }