@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,710 +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 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
- }
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
+ }