@typia/utils 12.0.0-dev.20260309 → 12.0.0-dev.20260310

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 (92) hide show
  1. package/lib/http/internal/HttpLlmApplicationComposer.mjs +5 -1
  2. package/lib/http/internal/HttpLlmApplicationComposer.mjs.map +1 -1
  3. package/lib/index.mjs +9 -9
  4. package/lib/utils/LlmJson.mjs +9 -2
  5. package/lib/utils/LlmJson.mjs.map +1 -1
  6. package/lib/utils/internal/stringifyValidationFailure.js +17 -15
  7. package/lib/utils/internal/stringifyValidationFailure.js.map +1 -1
  8. package/lib/utils/internal/stringifyValidationFailure.mjs +17 -15
  9. package/lib/utils/internal/stringifyValidationFailure.mjs.map +1 -1
  10. package/lib/validators/internal/OpenApiOneOfValidator.mjs +5 -1
  11. package/lib/validators/internal/OpenApiOneOfValidator.mjs.map +1 -1
  12. package/package.json +2 -2
  13. package/src/converters/LlmSchemaConverter.ts +647 -647
  14. package/src/converters/OpenApiConverter.ts +285 -285
  15. package/src/converters/index.ts +5 -5
  16. package/src/converters/internal/LlmDescriptionInverter.ts +178 -178
  17. package/src/converters/internal/LlmParametersComposer.ts +52 -52
  18. package/src/converters/internal/OpenApiConstraintShifter.ts +154 -154
  19. package/src/converters/internal/OpenApiExclusiveEmender.ts +46 -46
  20. package/src/converters/internal/OpenApiV3Downgrader.ts +355 -355
  21. package/src/converters/internal/OpenApiV3Upgrader.ts +470 -470
  22. package/src/converters/internal/OpenApiV3_1Upgrader.ts +685 -685
  23. package/src/converters/internal/SwaggerV2Downgrader.ts +424 -424
  24. package/src/converters/internal/SwaggerV2Upgrader.ts +523 -523
  25. package/src/http/HttpError.ts +107 -107
  26. package/src/http/HttpLlm.ts +167 -167
  27. package/src/http/HttpMigration.ts +92 -92
  28. package/src/http/index.ts +3 -3
  29. package/src/http/internal/HttpLlmApplicationComposer.ts +361 -361
  30. package/src/http/internal/HttpLlmFunctionFetcher.ts +37 -37
  31. package/src/http/internal/HttpMigrateApplicationComposer.ts +56 -56
  32. package/src/http/internal/HttpMigrateRouteAccessor.ts +135 -135
  33. package/src/http/internal/HttpMigrateRouteComposer.ts +505 -505
  34. package/src/http/internal/HttpMigrateRouteFetcher.ts +203 -203
  35. package/src/index.ts +4 -4
  36. package/src/utils/ArrayUtil.ts +42 -42
  37. package/src/utils/LlmJson.ts +141 -141
  38. package/src/utils/MapUtil.ts +15 -15
  39. package/src/utils/NamingConvention.ts +205 -205
  40. package/src/utils/Singleton.ts +17 -17
  41. package/src/utils/StringUtil.ts +14 -14
  42. package/src/utils/dedent.ts +57 -57
  43. package/src/utils/index.ts +8 -8
  44. package/src/utils/internal/EndpointUtil.ts +44 -44
  45. package/src/utils/internal/JsonDescriptor.ts +70 -70
  46. package/src/utils/internal/OpenApiTypeCheckerBase.ts +822 -822
  47. package/src/utils/internal/coerceLlmArguments.ts +314 -314
  48. package/src/utils/internal/parseLenientJson.ts +894 -894
  49. package/src/utils/internal/stringifyValidationFailure.ts +415 -411
  50. package/src/validators/LlmTypeChecker.ts +402 -402
  51. package/src/validators/OpenApiTypeChecker.ts +297 -297
  52. package/src/validators/OpenApiV3TypeChecker.ts +70 -70
  53. package/src/validators/OpenApiV3_1TypeChecker.ts +86 -86
  54. package/src/validators/OpenApiValidator.ts +94 -94
  55. package/src/validators/SwaggerV2TypeChecker.ts +71 -71
  56. package/src/validators/functional/_isBigintString.ts +8 -8
  57. package/src/validators/functional/_isFormatByte.ts +7 -7
  58. package/src/validators/functional/_isFormatDate.ts +3 -3
  59. package/src/validators/functional/_isFormatDateTime.ts +4 -4
  60. package/src/validators/functional/_isFormatDuration.ts +4 -4
  61. package/src/validators/functional/_isFormatEmail.ts +4 -4
  62. package/src/validators/functional/_isFormatHostname.ts +4 -4
  63. package/src/validators/functional/_isFormatIdnEmail.ts +4 -4
  64. package/src/validators/functional/_isFormatIdnHostname.ts +4 -4
  65. package/src/validators/functional/_isFormatIpv4.ts +4 -4
  66. package/src/validators/functional/_isFormatIpv6.ts +4 -4
  67. package/src/validators/functional/_isFormatIri.ts +3 -3
  68. package/src/validators/functional/_isFormatIriReference.ts +4 -4
  69. package/src/validators/functional/_isFormatJsonPointer.ts +3 -3
  70. package/src/validators/functional/_isFormatPassword.ts +1 -1
  71. package/src/validators/functional/_isFormatRegex.ts +8 -8
  72. package/src/validators/functional/_isFormatRelativeJsonPointer.ts +4 -4
  73. package/src/validators/functional/_isFormatTime.ts +4 -4
  74. package/src/validators/functional/_isFormatUri.ts +6 -6
  75. package/src/validators/functional/_isFormatUriReference.ts +5 -5
  76. package/src/validators/functional/_isFormatUriTemplate.ts +4 -4
  77. package/src/validators/functional/_isFormatUrl.ts +4 -4
  78. package/src/validators/functional/_isFormatUuid.ts +3 -3
  79. package/src/validators/functional/_isUniqueItems.ts +159 -159
  80. package/src/validators/index.ts +14 -14
  81. package/src/validators/internal/IOpenApiValidatorContext.ts +17 -17
  82. package/src/validators/internal/OpenApiArrayValidator.ts +49 -49
  83. package/src/validators/internal/OpenApiBooleanValidator.ts +11 -11
  84. package/src/validators/internal/OpenApiConstantValidator.ts +11 -11
  85. package/src/validators/internal/OpenApiIntegerValidator.ts +49 -49
  86. package/src/validators/internal/OpenApiNumberValidator.ts +48 -48
  87. package/src/validators/internal/OpenApiObjectValidator.ts +83 -83
  88. package/src/validators/internal/OpenApiOneOfValidator.ts +309 -309
  89. package/src/validators/internal/OpenApiSchemaNamingRule.ts +124 -124
  90. package/src/validators/internal/OpenApiStationValidator.ts +115 -115
  91. package/src/validators/internal/OpenApiStringValidator.ts +88 -88
  92. package/src/validators/internal/OpenApiTupleValidator.ts +55 -55
@@ -1,685 +1,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 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 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
+ }