@typia/utils 12.0.0-dev.20260311 → 12.0.0-dev.20260312

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.
Files changed (51) hide show
  1. package/lib/converters/OpenApiConverter.d.ts +29 -16
  2. package/lib/converters/OpenApiConverter.js +25 -15
  3. package/lib/converters/OpenApiConverter.js.map +1 -1
  4. package/lib/converters/OpenApiConverter.mjs +25 -15
  5. package/lib/converters/OpenApiConverter.mjs.map +1 -1
  6. package/lib/converters/internal/OpenApiV3Downgrader.js +38 -18
  7. package/lib/converters/internal/OpenApiV3Downgrader.js.map +1 -1
  8. package/lib/converters/internal/OpenApiV3Downgrader.mjs +50 -27
  9. package/lib/converters/internal/OpenApiV3Downgrader.mjs.map +1 -1
  10. package/lib/converters/internal/OpenApiV3Upgrader.js +36 -18
  11. package/lib/converters/internal/OpenApiV3Upgrader.js.map +1 -1
  12. package/lib/converters/internal/OpenApiV3Upgrader.mjs +50 -29
  13. package/lib/converters/internal/OpenApiV3Upgrader.mjs.map +1 -1
  14. package/lib/converters/internal/OpenApiV3_1Upgrader.js +38 -20
  15. package/lib/converters/internal/OpenApiV3_1Upgrader.js.map +1 -1
  16. package/lib/converters/internal/OpenApiV3_1Upgrader.mjs +51 -29
  17. package/lib/converters/internal/OpenApiV3_1Upgrader.mjs.map +1 -1
  18. package/lib/converters/internal/OpenApiV3_2Upgrader.d.ts +16 -0
  19. package/lib/converters/internal/OpenApiV3_2Upgrader.js +204 -0
  20. package/lib/converters/internal/OpenApiV3_2Upgrader.js.map +1 -0
  21. package/lib/converters/internal/OpenApiV3_2Upgrader.mjs +243 -0
  22. package/lib/converters/internal/OpenApiV3_2Upgrader.mjs.map +1 -0
  23. package/lib/converters/internal/SwaggerV2Downgrader.js +38 -18
  24. package/lib/converters/internal/SwaggerV2Downgrader.js.map +1 -1
  25. package/lib/converters/internal/SwaggerV2Downgrader.mjs +50 -27
  26. package/lib/converters/internal/SwaggerV2Downgrader.mjs.map +1 -1
  27. package/lib/converters/internal/SwaggerV2Upgrader.js +37 -19
  28. package/lib/converters/internal/SwaggerV2Upgrader.js.map +1 -1
  29. package/lib/converters/internal/SwaggerV2Upgrader.mjs +50 -29
  30. package/lib/converters/internal/SwaggerV2Upgrader.mjs.map +1 -1
  31. package/lib/http/internal/HttpMigrateApplicationComposer.js +1 -1
  32. package/lib/http/internal/HttpMigrateApplicationComposer.js.map +1 -1
  33. package/lib/http/internal/HttpMigrateApplicationComposer.mjs +1 -1
  34. package/lib/http/internal/HttpMigrateApplicationComposer.mjs.map +1 -1
  35. package/lib/http/internal/HttpMigrateRouteComposer.d.ts +1 -1
  36. package/lib/utils/LlmJson.d.ts +19 -1
  37. package/lib/utils/LlmJson.js +27 -0
  38. package/lib/utils/LlmJson.js.map +1 -1
  39. package/lib/utils/LlmJson.mjs +27 -0
  40. package/lib/utils/LlmJson.mjs.map +1 -1
  41. package/package.json +2 -2
  42. package/src/converters/OpenApiConverter.ts +322 -285
  43. package/src/converters/internal/OpenApiV3Downgrader.ts +381 -355
  44. package/src/converters/internal/OpenApiV3Upgrader.ts +494 -470
  45. package/src/converters/internal/OpenApiV3_1Upgrader.ts +710 -685
  46. package/src/converters/internal/OpenApiV3_2Upgrader.ts +342 -0
  47. package/src/converters/internal/SwaggerV2Downgrader.ts +450 -424
  48. package/src/converters/internal/SwaggerV2Upgrader.ts +547 -523
  49. package/src/http/internal/HttpMigrateApplicationComposer.ts +56 -56
  50. package/src/http/internal/HttpMigrateRouteComposer.ts +505 -505
  51. package/src/utils/LlmJson.ts +173 -141
@@ -1,685 +1,710 @@
1
- import { IJsonSchemaAttribute, OpenApi, OpenApiV3_1 } from "@typia/interface";
2
-
3
- import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
- import { OpenApiV3_1TypeChecker } from "../../validators/OpenApiV3_1TypeChecker";
5
- import { OpenApiExclusiveEmender } from "./OpenApiExclusiveEmender";
6
-
7
- export namespace OpenApiV3_1Upgrader {
8
- export const convert = (input: OpenApiV3_1.IDocument): OpenApi.IDocument => {
9
- if ((input as OpenApi.IDocument)["x-samchon-emended-v4"] === true)
10
- return input as OpenApi.IDocument;
11
- return {
12
- ...input,
13
- components: convertComponents(input.components ?? {}),
14
- paths: input.paths
15
- ? Object.fromEntries(
16
- Object.entries(input.paths)
17
- .filter(([_, v]) => v !== undefined)
18
- .map(
19
- ([key, value]) => [key, convertPathItem(input)(value)] as const,
20
- ),
21
- )
22
- : undefined,
23
- webhooks: input.webhooks
24
- ? Object.fromEntries(
25
- Object.entries(input.webhooks)
26
- .filter(([_, v]) => v !== undefined)
27
- .map(
28
- ([key, value]) =>
29
- [key, convertWebhooks(input)(value)!] as const,
30
- )
31
- .filter(([_, value]) => value !== undefined),
32
- )
33
- : undefined,
34
- "x-samchon-emended-v4": true,
35
- };
36
- };
37
-
38
- /* -----------------------------------------------------------
39
- OPERATORS
40
- ----------------------------------------------------------- */
41
- const convertWebhooks =
42
- (doc: OpenApiV3_1.IDocument) =>
43
- (
44
- webhook:
45
- | OpenApiV3_1.IPath
46
- | OpenApiV3_1.IJsonSchema.IReference<`#/components/pathItems/${string}`>,
47
- ): OpenApi.IPath | undefined => {
48
- if (!OpenApiV3_1TypeChecker.isReference(webhook))
49
- return convertPathItem(doc)(webhook);
50
- const found: OpenApiV3_1.IPath | undefined =
51
- doc.components?.pathItems?.[webhook.$ref.split("/").pop() ?? ""];
52
- return found ? convertPathItem(doc)(found) : undefined;
53
- };
54
-
55
- const convertPathItem =
56
- (doc: OpenApiV3_1.IDocument) =>
57
- (pathItem: OpenApiV3_1.IPath): OpenApi.IPath => ({
58
- ...(pathItem as any),
59
- ...(pathItem.get
60
- ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
61
- : undefined),
62
- ...(pathItem.put
63
- ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
64
- : undefined),
65
- ...(pathItem.post
66
- ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
67
- : undefined),
68
- ...(pathItem.delete
69
- ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
70
- : undefined),
71
- ...(pathItem.options
72
- ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
73
- : undefined),
74
- ...(pathItem.head
75
- ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
76
- : undefined),
77
- ...(pathItem.patch
78
- ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
79
- : undefined),
80
- ...(pathItem.trace
81
- ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
82
- : undefined),
83
- });
84
-
85
- const convertOperation =
86
- (doc: OpenApiV3_1.IDocument) =>
87
- (pathItem: OpenApiV3_1.IPath) =>
88
- (input: OpenApiV3_1.IOperation): OpenApi.IOperation => ({
89
- ...input,
90
- parameters:
91
- pathItem.parameters !== undefined || input.parameters !== undefined
92
- ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
93
- .map((p) => {
94
- if (!OpenApiV3_1TypeChecker.isReference(p))
95
- return convertParameter(doc.components ?? {})(p);
96
- const found:
97
- | Omit<OpenApiV3_1.IOperation.IParameter, "in">
98
- | undefined = p.$ref.startsWith("#/components/headers/")
99
- ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""]
100
- : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""];
101
- return found !== undefined
102
- ? convertParameter(doc.components ?? {})({
103
- ...found,
104
- in: "header",
105
- })
106
- : undefined!;
107
- })
108
- .filter((_, v) => v !== undefined)
109
- : undefined,
110
- requestBody: input.requestBody
111
- ? convertRequestBody(doc)(input.requestBody)
112
- : undefined,
113
- responses: input.responses
114
- ? Object.fromEntries(
115
- Object.entries(input.responses)
116
- .filter(([_, v]) => v !== undefined)
117
- .map(
118
- ([key, value]) => [key, convertResponse(doc)(value)!] as const,
119
- )
120
- .filter(([_, value]) => value !== undefined),
121
- )
122
- : undefined,
123
- });
124
-
125
- const convertParameter =
126
- (components: OpenApiV3_1.IComponents) =>
127
- (
128
- input: OpenApiV3_1.IOperation.IParameter,
129
- ): OpenApi.IOperation.IParameter => ({
130
- ...input,
131
- schema: convertSchema(components)(input.schema),
132
- examples: input.examples
133
- ? Object.fromEntries(
134
- Object.entries(input.examples)
135
- .map(([key, value]) => [
136
- key,
137
- OpenApiV3_1TypeChecker.isReference(value)
138
- ? components.examples?.[value.$ref.split("/").pop() ?? ""]
139
- : value,
140
- ])
141
- .filter(([_, v]) => v !== undefined),
142
- )
143
- : undefined,
144
- });
145
-
146
- const convertRequestBody =
147
- (doc: OpenApiV3_1.IDocument) =>
148
- (
149
- input:
150
- | OpenApiV3_1.IOperation.IRequestBody
151
- | OpenApiV3_1.IJsonSchema.IReference<`#/components/requestBodies/${string}`>,
152
- ): OpenApi.IOperation.IRequestBody | undefined => {
153
- if (OpenApiV3_1TypeChecker.isReference(input)) {
154
- const found: OpenApiV3_1.IOperation.IRequestBody | undefined =
155
- doc.components?.requestBodies?.[input.$ref.split("/").pop() ?? ""];
156
- if (found === undefined) return undefined;
157
- input = found;
158
- }
159
- return {
160
- ...input,
161
- content: input.content
162
- ? convertContent(doc.components ?? {})(input.content)
163
- : undefined,
164
- };
165
- };
166
-
167
- const convertResponse =
168
- (doc: OpenApiV3_1.IDocument) =>
169
- (
170
- input:
171
- | OpenApiV3_1.IOperation.IResponse
172
- | OpenApiV3_1.IJsonSchema.IReference<`#/components/responses/${string}`>,
173
- ): OpenApi.IOperation.IResponse | undefined => {
174
- if (OpenApiV3_1TypeChecker.isReference(input)) {
175
- const found: OpenApiV3_1.IOperation.IResponse | undefined =
176
- doc.components?.responses?.[input.$ref.split("/").pop() ?? ""];
177
- if (found === undefined) return undefined;
178
- input = found;
179
- }
180
- return {
181
- ...input,
182
- content: input.content
183
- ? convertContent(doc.components ?? {})(input.content)
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
- if (OpenApiV3_1TypeChecker.isReference(value) === false)
195
- return convertParameter(doc.components ?? {})({
196
- ...value,
197
- in: "header",
198
- });
199
- const found:
200
- | Omit<OpenApiV3_1.IOperation.IParameter, "in">
201
- | undefined = value.$ref.startsWith(
202
- "#/components/headers/",
203
- )
204
- ? doc.components?.headers?.[
205
- value.$ref.split("/").pop() ?? ""
206
- ]
207
- : undefined;
208
- return found !== undefined
209
- ? convertParameter(doc.components ?? {})({
210
- ...found,
211
- in: "header",
212
- })
213
- : undefined!;
214
- })(),
215
- ] as const,
216
- )
217
- .filter(([_, v]) => v !== undefined),
218
- )
219
- : undefined,
220
- };
221
- };
222
-
223
- const convertContent =
224
- (components: OpenApiV3_1.IComponents) =>
225
- (
226
- record: Record<string, OpenApiV3_1.IOperation.IMediaType>,
227
- ): Record<string, OpenApi.IOperation.IMediaType> =>
228
- Object.fromEntries(
229
- Object.entries(record)
230
- .filter(([_, v]) => v !== undefined)
231
- .map(
232
- ([key, value]) =>
233
- [
234
- key,
235
- {
236
- ...value,
237
- schema: value.schema
238
- ? convertSchema(components)(value.schema)
239
- : undefined,
240
- examples: value.examples
241
- ? Object.fromEntries(
242
- Object.entries(value.examples)
243
- .map(([key, value]) => [
244
- key,
245
- OpenApiV3_1TypeChecker.isReference(value)
246
- ? components.examples?.[
247
- value.$ref.split("/").pop() ?? ""
248
- ]
249
- : value,
250
- ])
251
- .filter(([_, v]) => v !== undefined),
252
- )
253
- : undefined,
254
- },
255
- ] as const,
256
- ),
257
- );
258
-
259
- /* -----------------------------------------------------------
260
- DEFINITIONS
261
- ----------------------------------------------------------- */
262
- export const convertComponents = (
263
- input: OpenApiV3_1.IComponents,
264
- ): OpenApi.IComponents => ({
265
- schemas: Object.fromEntries(
266
- Object.entries(input.schemas ?? {})
267
- .filter(([_, v]) => v !== undefined)
268
- .map(([key, value]) => [key, convertSchema(input)(value)] as const),
269
- ),
270
- securitySchemes: input.securitySchemes,
271
- });
272
-
273
- export const convertSchema =
274
- (components: OpenApiV3_1.IComponents) =>
275
- (input: OpenApiV3_1.IJsonSchema): OpenApi.IJsonSchema => {
276
- const union: OpenApi.IJsonSchema[] = [];
277
- const attribute: IJsonSchemaAttribute = {
278
- title: input.title,
279
- description: input.description,
280
- deprecated: input.deprecated,
281
- readOnly: input.readOnly,
282
- writeOnly: input.writeOnly,
283
- example: input.example,
284
- examples: Array.isArray(input.examples)
285
- ? Object.fromEntries(input.examples.map((v, i) => [`v${i}`, v]))
286
- : input.examples,
287
- ...Object.fromEntries(
288
- Object.entries(input).filter(
289
- ([key, value]) => key.startsWith("x-") && value !== undefined,
290
- ),
291
- ),
292
- };
293
- const nullable: { value: boolean; default?: null } = {
294
- value: false,
295
- default: undefined,
296
- };
297
-
298
- const visit = (schema: OpenApiV3_1.IJsonSchema): void => {
299
- // NULLABLE PROPERTY
300
- if ((schema as OpenApiV3_1.IJsonSchema.INumber).nullable === true) {
301
- nullable.value ||= true;
302
- if ((schema as OpenApiV3_1.IJsonSchema.INumber).default === null)
303
- nullable.default = null;
304
- }
305
- if (
306
- Array.isArray((schema as OpenApiV3_1.IJsonSchema.INumber).enum) &&
307
- (schema as OpenApiV3_1.IJsonSchema.INumber).enum?.length &&
308
- (schema as OpenApiV3_1.IJsonSchema.INumber).enum?.some(
309
- (e) => e === null,
310
- )
311
- )
312
- nullable.value ||= true;
313
-
314
- // MIXED TYPE CASE
315
- if (OpenApiV3_1TypeChecker.isMixed(schema)) {
316
- if (schema.const !== undefined)
317
- visit({
318
- ...schema,
319
- ...{
320
- type: undefined,
321
- oneOf: undefined,
322
- anyOf: undefined,
323
- allOf: undefined,
324
- $ref: undefined,
325
- },
326
- });
327
- if (schema.oneOf !== undefined)
328
- visit({
329
- ...schema,
330
- ...{
331
- type: undefined,
332
- anyOf: undefined,
333
- allOf: undefined,
334
- $ref: undefined,
335
- },
336
- });
337
- if (schema.anyOf !== undefined)
338
- visit({
339
- ...schema,
340
- ...{
341
- type: undefined,
342
- oneOf: undefined,
343
- allOf: undefined,
344
- $ref: undefined,
345
- },
346
- });
347
- if (schema.allOf !== undefined)
348
- visit({
349
- ...schema,
350
- ...{
351
- type: undefined,
352
- oneOf: undefined,
353
- anyOf: undefined,
354
- $ref: undefined,
355
- },
356
- });
357
- for (const type of schema.type)
358
- if (type === "boolean" || type === "number" || type === "string")
359
- visit({
360
- ...schema,
361
- ...{
362
- enum:
363
- schema.enum?.length && schema.enum.filter((e) => e !== null)
364
- ? schema.enum.filter((x) => typeof x === type)
365
- : undefined,
366
- },
367
- type: type as any,
368
- });
369
- else if (type === "integer")
370
- visit({
371
- ...schema,
372
- ...{
373
- enum:
374
- schema.enum?.length && schema.enum.filter((e) => e !== null)
375
- ? schema.enum.filter(
376
- (x) =>
377
- x !== null &&
378
- typeof x === "number" &&
379
- Number.isInteger(x),
380
- )
381
- : undefined,
382
- },
383
- type: type as any,
384
- });
385
- else visit({ ...schema, type: type as any });
386
- }
387
- // UNION TYPE CASE
388
- else if (OpenApiV3_1TypeChecker.isOneOf(schema))
389
- schema.oneOf.forEach(visit);
390
- else if (OpenApiV3_1TypeChecker.isAnyOf(schema))
391
- schema.anyOf.forEach(visit);
392
- else if (OpenApiV3_1TypeChecker.isAllOf(schema))
393
- if (schema.allOf.length === 1) visit(schema.allOf[0]!);
394
- else union.push(convertAllOfSchema(components)(schema));
395
- // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
396
- else if (OpenApiV3_1TypeChecker.isBoolean(schema))
397
- if (
398
- schema.enum?.length &&
399
- schema.enum.filter((e) => e !== null).length
400
- )
401
- for (const value of schema.enum.filter((e) => e !== null))
402
- union.push({
403
- const: value,
404
- ...({
405
- ...schema,
406
- type: undefined as any,
407
- enum: undefined,
408
- default: undefined,
409
- } satisfies OpenApiV3_1.IJsonSchema.IBoolean as any),
410
- } satisfies OpenApi.IJsonSchema.IConstant);
411
- else
412
- union.push({
413
- ...schema,
414
- default: schema.default ?? undefined,
415
- ...{
416
- enum: undefined,
417
- },
418
- });
419
- else if (
420
- OpenApiV3_1TypeChecker.isInteger(schema) ||
421
- OpenApiV3_1TypeChecker.isNumber(schema)
422
- )
423
- if (schema.enum?.length && schema.enum.filter((e) => e !== null))
424
- for (const value of schema.enum.filter((e) => e !== null))
425
- union.push({
426
- const: value,
427
- ...({
428
- ...schema,
429
- type: undefined as any,
430
- enum: undefined,
431
- default: undefined,
432
- minimum: undefined,
433
- maximum: undefined,
434
- exclusiveMinimum: undefined,
435
- exclusiveMaximum: undefined,
436
- multipleOf: undefined,
437
- } satisfies OpenApiV3_1.IJsonSchema.IInteger as any),
438
- } satisfies OpenApi.IJsonSchema.IConstant);
439
- else
440
- union.push(
441
- OpenApiExclusiveEmender.emend({
442
- ...schema,
443
- default: schema.default ?? undefined,
444
- ...{
445
- enum: undefined,
446
- },
447
- exclusiveMinimum:
448
- typeof schema.exclusiveMinimum === "boolean"
449
- ? schema.exclusiveMinimum === true
450
- ? schema.minimum
451
- : undefined
452
- : schema.exclusiveMinimum,
453
- exclusiveMaximum:
454
- typeof schema.exclusiveMaximum === "boolean"
455
- ? schema.exclusiveMaximum === true
456
- ? schema.maximum
457
- : undefined
458
- : schema.exclusiveMaximum,
459
- minimum:
460
- schema.exclusiveMinimum === true ? undefined : schema.minimum,
461
- maximum:
462
- schema.exclusiveMaximum === true ? undefined : schema.maximum,
463
- }),
464
- );
465
- else if (OpenApiV3_1TypeChecker.isString(schema))
466
- if (
467
- schema.enum?.length &&
468
- schema.enum.filter((e) => e !== null).length
469
- )
470
- for (const value of schema.enum.filter((e) => e !== null))
471
- union.push({
472
- const: value,
473
- ...({
474
- ...schema,
475
- type: undefined as any,
476
- enum: undefined,
477
- default: undefined,
478
- } satisfies OpenApiV3_1.IJsonSchema.IString as any),
479
- } satisfies OpenApi.IJsonSchema.IConstant);
480
- else
481
- union.push({
482
- ...schema,
483
- default: schema.default ?? undefined,
484
- ...{
485
- enum: undefined,
486
- },
487
- });
488
- // ARRAY TYPE CASE (CONSIDER TUPLE)
489
- else if (OpenApiV3_1TypeChecker.isArray(schema)) {
490
- if (Array.isArray(schema.items))
491
- union.push({
492
- ...schema,
493
- ...{
494
- items: undefined!,
495
- prefixItems: schema.items.map(convertSchema(components)),
496
- additionalItems:
497
- typeof schema.additionalItems === "object" &&
498
- schema.additionalItems !== null
499
- ? convertSchema(components)(schema.additionalItems)
500
- : schema.additionalItems,
501
- },
502
- } satisfies OpenApi.IJsonSchema.ITuple);
503
- else if (Array.isArray(schema.prefixItems))
504
- union.push({
505
- ...schema,
506
- ...{
507
- items: undefined!,
508
- prefixItems: schema.prefixItems.map(convertSchema(components)),
509
- additionalItems:
510
- typeof schema.additionalItems === "object" &&
511
- schema.additionalItems !== null
512
- ? convertSchema(components)(schema.additionalItems)
513
- : schema.additionalItems,
514
- },
515
- });
516
- else if (schema.items === undefined)
517
- union.push({
518
- ...schema,
519
- ...{
520
- items: undefined!,
521
- prefixItems: [],
522
- },
523
- });
524
- else
525
- union.push({
526
- ...schema,
527
- ...{
528
- items: convertSchema(components)(schema.items),
529
- prefixItems: undefined,
530
- additionalItems: undefined,
531
- },
532
- });
533
- }
534
- // OBJECT TYPE CASE
535
- else if (OpenApiV3_1TypeChecker.isObject(schema))
536
- union.push({
537
- ...schema,
538
- ...{
539
- properties: schema.properties
540
- ? Object.fromEntries(
541
- Object.entries(schema.properties)
542
- .filter(([_, v]) => v !== undefined)
543
- .map(
544
- ([key, value]) =>
545
- [key, convertSchema(components)(value)] as const,
546
- ),
547
- )
548
- : {},
549
- additionalProperties: schema.additionalProperties
550
- ? typeof schema.additionalProperties === "object" &&
551
- schema.additionalProperties !== null
552
- ? convertSchema(components)(schema.additionalProperties)
553
- : schema.additionalProperties
554
- : undefined,
555
- required: schema.required ?? [],
556
- },
557
- });
558
- else if (OpenApiV3_1TypeChecker.isReference(schema))
559
- union.push({
560
- ...schema,
561
- ...{
562
- $ref: `#/components/schemas/${schema.$ref.split("/").pop()}`,
563
- },
564
- });
565
- else if (OpenApiV3_1TypeChecker.isRecursiveReference(schema))
566
- union.push({
567
- ...schema,
568
- ...{
569
- $ref: `#/components/schemas/${schema.$recursiveRef.split("/").pop()}`,
570
- $recursiveRef: undefined,
571
- },
572
- });
573
- // THE OTHERS
574
- else union.push(schema);
575
- };
576
-
577
- visit(input);
578
- if (
579
- nullable.value === true &&
580
- !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
581
- )
582
- union.push({
583
- type: "null",
584
- default: nullable.default,
585
- });
586
- if (
587
- union.length === 2 &&
588
- union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
589
- ) {
590
- const type: OpenApi.IJsonSchema = union.filter(
591
- (x) => OpenApiTypeChecker.isNull(x) === false,
592
- )[0]!;
593
- for (const key of [
594
- "title",
595
- "description",
596
- "deprecated",
597
- "readOnly",
598
- "writeOnly",
599
- "example",
600
- "examples",
601
- ] as const)
602
- if (type[key] !== undefined) delete type[key];
603
- }
604
- return {
605
- ...(union.length === 0
606
- ? {
607
- type: undefined,
608
- }
609
- : union.length === 1
610
- ? {
611
- ...union[0],
612
- }
613
- : {
614
- oneOf: union.map((u) => ({
615
- ...u,
616
- nullable: undefined,
617
- $defs: undefined,
618
- })),
619
- }),
620
- ...attribute,
621
- ...{
622
- nullable: undefined,
623
- $defs: undefined,
624
- },
625
- };
626
- };
627
-
628
- const convertAllOfSchema =
629
- (components: OpenApiV3_1.IComponents) =>
630
- (input: OpenApiV3_1.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
631
- const objects: Array<OpenApiV3_1.IJsonSchema.IObject | null> =
632
- input.allOf.map((schema) => retrieveObject(components)(schema));
633
- if (objects.some((obj) => obj === null))
634
- return {
635
- type: undefined,
636
- ...{
637
- allOf: undefined,
638
- },
639
- };
640
- return {
641
- ...input,
642
- type: "object",
643
- properties: Object.fromEntries(
644
- objects
645
- .map((o) => Object.entries(o?.properties ?? {}))
646
- .flat()
647
- .map(
648
- ([key, value]) =>
649
- [key, convertSchema(components)(value)] as const,
650
- ),
651
- ),
652
- ...{
653
- allOf: undefined,
654
- required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
655
- },
656
- };
657
- };
658
-
659
- const retrieveObject =
660
- (components: OpenApiV3_1.IComponents) =>
661
- (
662
- input: OpenApiV3_1.IJsonSchema,
663
- visited: Set<OpenApiV3_1.IJsonSchema> = new Set(),
664
- ): OpenApiV3_1.IJsonSchema.IObject | null => {
665
- if (OpenApiV3_1TypeChecker.isObject(input))
666
- return input.properties !== undefined && !input.additionalProperties
667
- ? input
668
- : null;
669
- else if (visited.has(input)) return null;
670
- else visited.add(input);
671
-
672
- if (OpenApiV3_1TypeChecker.isReference(input))
673
- return retrieveObject(components)(
674
- components.schemas?.[input.$ref.split("/").pop() ?? ""] ?? {},
675
- visited,
676
- );
677
- else if (OpenApiV3_1TypeChecker.isRecursiveReference(input))
678
- return retrieveObject(components)(
679
- components.schemas?.[input.$recursiveRef.split("/").pop() ?? ""] ??
680
- {},
681
- visited,
682
- );
683
- return null;
684
- };
685
- }
1
+ import { IJsonSchemaAttribute, OpenApi, OpenApiV3_1 } from "@typia/interface";
2
+
3
+ import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
+ import { OpenApiV3_1TypeChecker } from "../../validators/OpenApiV3_1TypeChecker";
5
+ import { OpenApiExclusiveEmender } from "./OpenApiExclusiveEmender";
6
+
7
+ export namespace OpenApiV3_1Upgrader {
8
+ export const convert = (input: OpenApiV3_1.IDocument): OpenApi.IDocument => {
9
+ if ((input as unknown as OpenApi.IDocument)["x-typia-emended-v12"] === true)
10
+ return input as unknown as OpenApi.IDocument;
11
+ return {
12
+ ...input,
13
+ openapi: "3.2.0",
14
+ components: convertComponents(input.components ?? {}),
15
+ paths: input.paths
16
+ ? Object.fromEntries(
17
+ Object.entries(input.paths)
18
+ .filter(([_, v]) => v !== undefined)
19
+ .map(
20
+ ([key, value]) => [key, convertPathItem(input)(value)] as const,
21
+ ),
22
+ )
23
+ : undefined,
24
+ webhooks: input.webhooks
25
+ ? Object.fromEntries(
26
+ Object.entries(input.webhooks)
27
+ .filter(([_, v]) => v !== undefined)
28
+ .map(
29
+ ([key, value]) =>
30
+ [key, convertWebhooks(input)(value)!] as const,
31
+ )
32
+ .filter(([_, value]) => value !== undefined),
33
+ )
34
+ : undefined,
35
+ "x-typia-emended-v12": true,
36
+ };
37
+ };
38
+
39
+ /* -----------------------------------------------------------
40
+ OPERATORS
41
+ ----------------------------------------------------------- */
42
+ const convertWebhooks =
43
+ (doc: OpenApiV3_1.IDocument) =>
44
+ (
45
+ webhook:
46
+ | OpenApiV3_1.IPath
47
+ | OpenApiV3_1.IJsonSchema.IReference<`#/components/pathItems/${string}`>,
48
+ ): OpenApi.IPath | undefined => {
49
+ if (!OpenApiV3_1TypeChecker.isReference(webhook))
50
+ return convertPathItem(doc)(webhook);
51
+ const found: OpenApiV3_1.IPath | undefined =
52
+ doc.components?.pathItems?.[webhook.$ref.split("/").pop() ?? ""];
53
+ return found ? convertPathItem(doc)(found) : undefined;
54
+ };
55
+
56
+ const convertPathItem =
57
+ (doc: OpenApiV3_1.IDocument) =>
58
+ (pathItem: OpenApiV3_1.IPath): OpenApi.IPath => {
59
+ // Convert x-additionalOperations to additionalOperations
60
+ // Promote "query" to standard method (it's a v3.2 standard method)
61
+ const xAdditional = pathItem["x-additionalOperations"];
62
+ const queryOp = xAdditional?.["query"];
63
+ const additionalOperations = xAdditional
64
+ ? Object.fromEntries(
65
+ Object.entries(xAdditional)
66
+ .filter(([key, v]) => key !== "query" && v !== undefined)
67
+ .map(([key, value]) => [
68
+ key,
69
+ convertOperation(doc)(pathItem)(value),
70
+ ]),
71
+ )
72
+ : undefined;
73
+
74
+ return {
75
+ ...(pathItem as any),
76
+ ...(pathItem.get
77
+ ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
78
+ : undefined),
79
+ ...(pathItem.put
80
+ ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
81
+ : undefined),
82
+ ...(pathItem.post
83
+ ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
84
+ : undefined),
85
+ ...(pathItem.delete
86
+ ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
87
+ : undefined),
88
+ ...(pathItem.options
89
+ ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
90
+ : undefined),
91
+ ...(pathItem.head
92
+ ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
93
+ : undefined),
94
+ ...(pathItem.patch
95
+ ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
96
+ : undefined),
97
+ ...(pathItem.trace
98
+ ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
99
+ : undefined),
100
+ ...(queryOp
101
+ ? { query: convertOperation(doc)(pathItem)(queryOp) }
102
+ : undefined),
103
+ ...(additionalOperations && Object.keys(additionalOperations).length > 0
104
+ ? { additionalOperations }
105
+ : undefined),
106
+ "x-additionalOperations": undefined,
107
+ };
108
+ };
109
+
110
+ const convertOperation =
111
+ (doc: OpenApiV3_1.IDocument) =>
112
+ (pathItem: OpenApiV3_1.IPath) =>
113
+ (input: OpenApiV3_1.IOperation): OpenApi.IOperation => ({
114
+ ...input,
115
+ parameters:
116
+ pathItem.parameters !== undefined || input.parameters !== undefined
117
+ ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
118
+ .map((p) => {
119
+ if (!OpenApiV3_1TypeChecker.isReference(p))
120
+ return convertParameter(doc.components ?? {})(p);
121
+ const found:
122
+ | Omit<OpenApiV3_1.IOperation.IParameter, "in">
123
+ | undefined = p.$ref.startsWith("#/components/headers/")
124
+ ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""]
125
+ : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""];
126
+ return found !== undefined
127
+ ? convertParameter(doc.components ?? {})({
128
+ ...found,
129
+ in: "header",
130
+ })
131
+ : undefined!;
132
+ })
133
+ .filter((_, v) => v !== undefined)
134
+ : undefined,
135
+ requestBody: input.requestBody
136
+ ? convertRequestBody(doc)(input.requestBody)
137
+ : undefined,
138
+ responses: input.responses
139
+ ? Object.fromEntries(
140
+ Object.entries(input.responses)
141
+ .filter(([_, v]) => v !== undefined)
142
+ .map(
143
+ ([key, value]) => [key, convertResponse(doc)(value)!] as const,
144
+ )
145
+ .filter(([_, value]) => value !== undefined),
146
+ )
147
+ : undefined,
148
+ });
149
+
150
+ const convertParameter =
151
+ (components: OpenApiV3_1.IComponents) =>
152
+ (
153
+ input: OpenApiV3_1.IOperation.IParameter,
154
+ ): OpenApi.IOperation.IParameter => ({
155
+ ...input,
156
+ schema: convertSchema(components)(input.schema),
157
+ examples: input.examples
158
+ ? Object.fromEntries(
159
+ Object.entries(input.examples)
160
+ .map(([key, value]) => [
161
+ key,
162
+ OpenApiV3_1TypeChecker.isReference(value)
163
+ ? components.examples?.[value.$ref.split("/").pop() ?? ""]
164
+ : value,
165
+ ])
166
+ .filter(([_, v]) => v !== undefined),
167
+ )
168
+ : undefined,
169
+ });
170
+
171
+ const convertRequestBody =
172
+ (doc: OpenApiV3_1.IDocument) =>
173
+ (
174
+ input:
175
+ | OpenApiV3_1.IOperation.IRequestBody
176
+ | OpenApiV3_1.IJsonSchema.IReference<`#/components/requestBodies/${string}`>,
177
+ ): OpenApi.IOperation.IRequestBody | undefined => {
178
+ if (OpenApiV3_1TypeChecker.isReference(input)) {
179
+ const found: OpenApiV3_1.IOperation.IRequestBody | undefined =
180
+ doc.components?.requestBodies?.[input.$ref.split("/").pop() ?? ""];
181
+ if (found === undefined) return undefined;
182
+ input = found;
183
+ }
184
+ return {
185
+ ...input,
186
+ content: input.content
187
+ ? convertContent(doc.components ?? {})(input.content)
188
+ : undefined,
189
+ };
190
+ };
191
+
192
+ const convertResponse =
193
+ (doc: OpenApiV3_1.IDocument) =>
194
+ (
195
+ input:
196
+ | OpenApiV3_1.IOperation.IResponse
197
+ | OpenApiV3_1.IJsonSchema.IReference<`#/components/responses/${string}`>,
198
+ ): OpenApi.IOperation.IResponse | undefined => {
199
+ if (OpenApiV3_1TypeChecker.isReference(input)) {
200
+ const found: OpenApiV3_1.IOperation.IResponse | undefined =
201
+ doc.components?.responses?.[input.$ref.split("/").pop() ?? ""];
202
+ if (found === undefined) return undefined;
203
+ input = found;
204
+ }
205
+ return {
206
+ ...input,
207
+ content: input.content
208
+ ? convertContent(doc.components ?? {})(input.content)
209
+ : undefined,
210
+ headers: input.headers
211
+ ? Object.fromEntries(
212
+ Object.entries(input.headers)
213
+ .filter(([_, v]) => v !== undefined)
214
+ .map(
215
+ ([key, value]) =>
216
+ [
217
+ key,
218
+ (() => {
219
+ if (OpenApiV3_1TypeChecker.isReference(value) === false)
220
+ return convertParameter(doc.components ?? {})({
221
+ ...value,
222
+ in: "header",
223
+ });
224
+ const found:
225
+ | Omit<OpenApiV3_1.IOperation.IParameter, "in">
226
+ | undefined = value.$ref.startsWith(
227
+ "#/components/headers/",
228
+ )
229
+ ? doc.components?.headers?.[
230
+ value.$ref.split("/").pop() ?? ""
231
+ ]
232
+ : undefined;
233
+ return found !== undefined
234
+ ? convertParameter(doc.components ?? {})({
235
+ ...found,
236
+ in: "header",
237
+ })
238
+ : undefined!;
239
+ })(),
240
+ ] as const,
241
+ )
242
+ .filter(([_, v]) => v !== undefined),
243
+ )
244
+ : undefined,
245
+ };
246
+ };
247
+
248
+ const convertContent =
249
+ (components: OpenApiV3_1.IComponents) =>
250
+ (
251
+ record: Record<string, OpenApiV3_1.IOperation.IMediaType>,
252
+ ): Record<string, OpenApi.IOperation.IMediaType> =>
253
+ Object.fromEntries(
254
+ Object.entries(record)
255
+ .filter(([_, v]) => v !== undefined)
256
+ .map(
257
+ ([key, value]) =>
258
+ [
259
+ key,
260
+ {
261
+ ...value,
262
+ schema: value.schema
263
+ ? convertSchema(components)(value.schema)
264
+ : undefined,
265
+ examples: value.examples
266
+ ? Object.fromEntries(
267
+ Object.entries(value.examples)
268
+ .map(([key, value]) => [
269
+ key,
270
+ OpenApiV3_1TypeChecker.isReference(value)
271
+ ? components.examples?.[
272
+ value.$ref.split("/").pop() ?? ""
273
+ ]
274
+ : value,
275
+ ])
276
+ .filter(([_, v]) => v !== undefined),
277
+ )
278
+ : undefined,
279
+ },
280
+ ] as const,
281
+ ),
282
+ );
283
+
284
+ /* -----------------------------------------------------------
285
+ DEFINITIONS
286
+ ----------------------------------------------------------- */
287
+ export const convertComponents = (
288
+ input: OpenApiV3_1.IComponents,
289
+ ): OpenApi.IComponents => ({
290
+ schemas: Object.fromEntries(
291
+ Object.entries(input.schemas ?? {})
292
+ .filter(([_, v]) => v !== undefined)
293
+ .map(([key, value]) => [key, convertSchema(input)(value)] as const),
294
+ ),
295
+ securitySchemes: input.securitySchemes,
296
+ });
297
+
298
+ export const convertSchema =
299
+ (components: OpenApiV3_1.IComponents) =>
300
+ (input: OpenApiV3_1.IJsonSchema): OpenApi.IJsonSchema => {
301
+ const union: OpenApi.IJsonSchema[] = [];
302
+ const attribute: IJsonSchemaAttribute = {
303
+ title: input.title,
304
+ description: input.description,
305
+ deprecated: input.deprecated,
306
+ readOnly: input.readOnly,
307
+ writeOnly: input.writeOnly,
308
+ example: input.example,
309
+ examples: Array.isArray(input.examples)
310
+ ? Object.fromEntries(input.examples.map((v, i) => [`v${i}`, v]))
311
+ : input.examples,
312
+ ...Object.fromEntries(
313
+ Object.entries(input).filter(
314
+ ([key, value]) => key.startsWith("x-") && value !== undefined,
315
+ ),
316
+ ),
317
+ };
318
+ const nullable: { value: boolean; default?: null } = {
319
+ value: false,
320
+ default: undefined,
321
+ };
322
+
323
+ const visit = (schema: OpenApiV3_1.IJsonSchema): void => {
324
+ // NULLABLE PROPERTY
325
+ if ((schema as OpenApiV3_1.IJsonSchema.INumber).nullable === true) {
326
+ nullable.value ||= true;
327
+ if ((schema as OpenApiV3_1.IJsonSchema.INumber).default === null)
328
+ nullable.default = null;
329
+ }
330
+ if (
331
+ Array.isArray((schema as OpenApiV3_1.IJsonSchema.INumber).enum) &&
332
+ (schema as OpenApiV3_1.IJsonSchema.INumber).enum?.length &&
333
+ (schema as OpenApiV3_1.IJsonSchema.INumber).enum?.some(
334
+ (e) => e === null,
335
+ )
336
+ )
337
+ nullable.value ||= true;
338
+
339
+ // MIXED TYPE CASE
340
+ if (OpenApiV3_1TypeChecker.isMixed(schema)) {
341
+ if (schema.const !== undefined)
342
+ visit({
343
+ ...schema,
344
+ ...{
345
+ type: undefined,
346
+ oneOf: undefined,
347
+ anyOf: undefined,
348
+ allOf: undefined,
349
+ $ref: undefined,
350
+ },
351
+ });
352
+ if (schema.oneOf !== undefined)
353
+ visit({
354
+ ...schema,
355
+ ...{
356
+ type: undefined,
357
+ anyOf: undefined,
358
+ allOf: undefined,
359
+ $ref: undefined,
360
+ },
361
+ });
362
+ if (schema.anyOf !== undefined)
363
+ visit({
364
+ ...schema,
365
+ ...{
366
+ type: undefined,
367
+ oneOf: undefined,
368
+ allOf: undefined,
369
+ $ref: undefined,
370
+ },
371
+ });
372
+ if (schema.allOf !== undefined)
373
+ visit({
374
+ ...schema,
375
+ ...{
376
+ type: undefined,
377
+ oneOf: undefined,
378
+ anyOf: undefined,
379
+ $ref: undefined,
380
+ },
381
+ });
382
+ for (const type of schema.type)
383
+ if (type === "boolean" || type === "number" || type === "string")
384
+ visit({
385
+ ...schema,
386
+ ...{
387
+ enum:
388
+ schema.enum?.length && schema.enum.filter((e) => e !== null)
389
+ ? schema.enum.filter((x) => typeof x === type)
390
+ : undefined,
391
+ },
392
+ type: type as any,
393
+ });
394
+ else if (type === "integer")
395
+ visit({
396
+ ...schema,
397
+ ...{
398
+ enum:
399
+ schema.enum?.length && schema.enum.filter((e) => e !== null)
400
+ ? schema.enum.filter(
401
+ (x) =>
402
+ x !== null &&
403
+ typeof x === "number" &&
404
+ Number.isInteger(x),
405
+ )
406
+ : undefined,
407
+ },
408
+ type: type as any,
409
+ });
410
+ else visit({ ...schema, type: type as any });
411
+ }
412
+ // UNION TYPE CASE
413
+ else if (OpenApiV3_1TypeChecker.isOneOf(schema))
414
+ schema.oneOf.forEach(visit);
415
+ else if (OpenApiV3_1TypeChecker.isAnyOf(schema))
416
+ schema.anyOf.forEach(visit);
417
+ else if (OpenApiV3_1TypeChecker.isAllOf(schema))
418
+ if (schema.allOf.length === 1) visit(schema.allOf[0]!);
419
+ else union.push(convertAllOfSchema(components)(schema));
420
+ // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
421
+ else if (OpenApiV3_1TypeChecker.isBoolean(schema))
422
+ if (
423
+ schema.enum?.length &&
424
+ schema.enum.filter((e) => e !== null).length
425
+ )
426
+ for (const value of schema.enum.filter((e) => e !== null))
427
+ union.push({
428
+ const: value,
429
+ ...({
430
+ ...schema,
431
+ type: undefined as any,
432
+ enum: undefined,
433
+ default: undefined,
434
+ } satisfies OpenApiV3_1.IJsonSchema.IBoolean as any),
435
+ } satisfies OpenApi.IJsonSchema.IConstant);
436
+ else
437
+ union.push({
438
+ ...schema,
439
+ default: schema.default ?? undefined,
440
+ ...{
441
+ enum: undefined,
442
+ },
443
+ });
444
+ else if (
445
+ OpenApiV3_1TypeChecker.isInteger(schema) ||
446
+ OpenApiV3_1TypeChecker.isNumber(schema)
447
+ )
448
+ if (schema.enum?.length && schema.enum.filter((e) => e !== null))
449
+ for (const value of schema.enum.filter((e) => e !== null))
450
+ union.push({
451
+ const: value,
452
+ ...({
453
+ ...schema,
454
+ type: undefined as any,
455
+ enum: undefined,
456
+ default: undefined,
457
+ minimum: undefined,
458
+ maximum: undefined,
459
+ exclusiveMinimum: undefined,
460
+ exclusiveMaximum: undefined,
461
+ multipleOf: undefined,
462
+ } satisfies OpenApiV3_1.IJsonSchema.IInteger as any),
463
+ } satisfies OpenApi.IJsonSchema.IConstant);
464
+ else
465
+ union.push(
466
+ OpenApiExclusiveEmender.emend({
467
+ ...schema,
468
+ default: schema.default ?? undefined,
469
+ ...{
470
+ enum: undefined,
471
+ },
472
+ exclusiveMinimum:
473
+ typeof schema.exclusiveMinimum === "boolean"
474
+ ? schema.exclusiveMinimum === true
475
+ ? schema.minimum
476
+ : undefined
477
+ : schema.exclusiveMinimum,
478
+ exclusiveMaximum:
479
+ typeof schema.exclusiveMaximum === "boolean"
480
+ ? schema.exclusiveMaximum === true
481
+ ? schema.maximum
482
+ : undefined
483
+ : schema.exclusiveMaximum,
484
+ minimum:
485
+ schema.exclusiveMinimum === true ? undefined : schema.minimum,
486
+ maximum:
487
+ schema.exclusiveMaximum === true ? undefined : schema.maximum,
488
+ }),
489
+ );
490
+ else if (OpenApiV3_1TypeChecker.isString(schema))
491
+ if (
492
+ schema.enum?.length &&
493
+ schema.enum.filter((e) => e !== null).length
494
+ )
495
+ for (const value of schema.enum.filter((e) => e !== null))
496
+ union.push({
497
+ const: value,
498
+ ...({
499
+ ...schema,
500
+ type: undefined as any,
501
+ enum: undefined,
502
+ default: undefined,
503
+ } satisfies OpenApiV3_1.IJsonSchema.IString as any),
504
+ } satisfies OpenApi.IJsonSchema.IConstant);
505
+ else
506
+ union.push({
507
+ ...schema,
508
+ default: schema.default ?? undefined,
509
+ ...{
510
+ enum: undefined,
511
+ },
512
+ });
513
+ // ARRAY TYPE CASE (CONSIDER TUPLE)
514
+ else if (OpenApiV3_1TypeChecker.isArray(schema)) {
515
+ if (Array.isArray(schema.items))
516
+ union.push({
517
+ ...schema,
518
+ ...{
519
+ items: undefined!,
520
+ prefixItems: schema.items.map(convertSchema(components)),
521
+ additionalItems:
522
+ typeof schema.additionalItems === "object" &&
523
+ schema.additionalItems !== null
524
+ ? convertSchema(components)(schema.additionalItems)
525
+ : schema.additionalItems,
526
+ },
527
+ } satisfies OpenApi.IJsonSchema.ITuple);
528
+ else if (Array.isArray(schema.prefixItems))
529
+ union.push({
530
+ ...schema,
531
+ ...{
532
+ items: undefined!,
533
+ prefixItems: schema.prefixItems.map(convertSchema(components)),
534
+ additionalItems:
535
+ typeof schema.additionalItems === "object" &&
536
+ schema.additionalItems !== null
537
+ ? convertSchema(components)(schema.additionalItems)
538
+ : schema.additionalItems,
539
+ },
540
+ });
541
+ else if (schema.items === undefined)
542
+ union.push({
543
+ ...schema,
544
+ ...{
545
+ items: undefined!,
546
+ prefixItems: [],
547
+ },
548
+ });
549
+ else
550
+ union.push({
551
+ ...schema,
552
+ ...{
553
+ items: convertSchema(components)(schema.items),
554
+ prefixItems: undefined,
555
+ additionalItems: undefined,
556
+ },
557
+ });
558
+ }
559
+ // OBJECT TYPE CASE
560
+ else if (OpenApiV3_1TypeChecker.isObject(schema))
561
+ union.push({
562
+ ...schema,
563
+ ...{
564
+ properties: schema.properties
565
+ ? Object.fromEntries(
566
+ Object.entries(schema.properties)
567
+ .filter(([_, v]) => v !== undefined)
568
+ .map(
569
+ ([key, value]) =>
570
+ [key, convertSchema(components)(value)] as const,
571
+ ),
572
+ )
573
+ : {},
574
+ additionalProperties: schema.additionalProperties
575
+ ? typeof schema.additionalProperties === "object" &&
576
+ schema.additionalProperties !== null
577
+ ? convertSchema(components)(schema.additionalProperties)
578
+ : schema.additionalProperties
579
+ : undefined,
580
+ required: schema.required ?? [],
581
+ },
582
+ });
583
+ else if (OpenApiV3_1TypeChecker.isReference(schema))
584
+ union.push({
585
+ ...schema,
586
+ ...{
587
+ $ref: `#/components/schemas/${schema.$ref.split("/").pop()}`,
588
+ },
589
+ });
590
+ else if (OpenApiV3_1TypeChecker.isRecursiveReference(schema))
591
+ union.push({
592
+ ...schema,
593
+ ...{
594
+ $ref: `#/components/schemas/${schema.$recursiveRef.split("/").pop()}`,
595
+ $recursiveRef: undefined,
596
+ },
597
+ });
598
+ // THE OTHERS
599
+ else union.push(schema);
600
+ };
601
+
602
+ visit(input);
603
+ if (
604
+ nullable.value === true &&
605
+ !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
606
+ )
607
+ union.push({
608
+ type: "null",
609
+ default: nullable.default,
610
+ });
611
+ if (
612
+ union.length === 2 &&
613
+ union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
614
+ ) {
615
+ const type: OpenApi.IJsonSchema = union.filter(
616
+ (x) => OpenApiTypeChecker.isNull(x) === false,
617
+ )[0]!;
618
+ for (const key of [
619
+ "title",
620
+ "description",
621
+ "deprecated",
622
+ "readOnly",
623
+ "writeOnly",
624
+ "example",
625
+ "examples",
626
+ ] as const)
627
+ if (type[key] !== undefined) delete type[key];
628
+ }
629
+ return {
630
+ ...(union.length === 0
631
+ ? {
632
+ type: undefined,
633
+ }
634
+ : union.length === 1
635
+ ? {
636
+ ...union[0],
637
+ }
638
+ : {
639
+ oneOf: union.map((u) => ({
640
+ ...u,
641
+ nullable: undefined,
642
+ $defs: undefined,
643
+ })),
644
+ }),
645
+ ...attribute,
646
+ ...{
647
+ nullable: undefined,
648
+ $defs: undefined,
649
+ },
650
+ };
651
+ };
652
+
653
+ const convertAllOfSchema =
654
+ (components: OpenApiV3_1.IComponents) =>
655
+ (input: OpenApiV3_1.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
656
+ const objects: Array<OpenApiV3_1.IJsonSchema.IObject | null> =
657
+ input.allOf.map((schema) => retrieveObject(components)(schema));
658
+ if (objects.some((obj) => obj === null))
659
+ return {
660
+ type: undefined,
661
+ ...{
662
+ allOf: undefined,
663
+ },
664
+ };
665
+ return {
666
+ ...input,
667
+ type: "object",
668
+ properties: Object.fromEntries(
669
+ objects
670
+ .map((o) => Object.entries(o?.properties ?? {}))
671
+ .flat()
672
+ .map(
673
+ ([key, value]) =>
674
+ [key, convertSchema(components)(value)] as const,
675
+ ),
676
+ ),
677
+ ...{
678
+ allOf: undefined,
679
+ required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
680
+ },
681
+ };
682
+ };
683
+
684
+ const retrieveObject =
685
+ (components: OpenApiV3_1.IComponents) =>
686
+ (
687
+ input: OpenApiV3_1.IJsonSchema,
688
+ visited: Set<OpenApiV3_1.IJsonSchema> = new Set(),
689
+ ): OpenApiV3_1.IJsonSchema.IObject | null => {
690
+ if (OpenApiV3_1TypeChecker.isObject(input))
691
+ return input.properties !== undefined && !input.additionalProperties
692
+ ? input
693
+ : null;
694
+ else if (visited.has(input)) return null;
695
+ else visited.add(input);
696
+
697
+ if (OpenApiV3_1TypeChecker.isReference(input))
698
+ return retrieveObject(components)(
699
+ components.schemas?.[input.$ref.split("/").pop() ?? ""] ?? {},
700
+ visited,
701
+ );
702
+ else if (OpenApiV3_1TypeChecker.isRecursiveReference(input))
703
+ return retrieveObject(components)(
704
+ components.schemas?.[input.$recursiveRef.split("/").pop() ?? ""] ??
705
+ {},
706
+ visited,
707
+ );
708
+ return null;
709
+ };
710
+ }