@typia/utils 12.0.0-dev.20260307-2 → 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 (103) hide show
  1. package/README.md +2 -2
  2. package/lib/http/internal/HttpLlmApplicationComposer.js +1 -0
  3. package/lib/http/internal/HttpLlmApplicationComposer.js.map +1 -1
  4. package/lib/http/internal/HttpLlmApplicationComposer.mjs +1 -0
  5. package/lib/http/internal/HttpLlmApplicationComposer.mjs.map +1 -1
  6. package/lib/utils/LlmJson.d.ts +3 -3
  7. package/lib/utils/LlmJson.js +2 -2
  8. package/lib/utils/LlmJson.js.map +1 -1
  9. package/lib/utils/LlmJson.mjs +2 -2
  10. package/lib/utils/LlmJson.mjs.map +1 -1
  11. package/lib/utils/internal/coerceLlmArguments.js +17 -1
  12. package/lib/utils/internal/coerceLlmArguments.js.map +1 -1
  13. package/lib/utils/internal/coerceLlmArguments.mjs +17 -1
  14. package/lib/utils/internal/coerceLlmArguments.mjs.map +1 -1
  15. package/lib/utils/internal/parseLenientJson.js +236 -96
  16. package/lib/utils/internal/parseLenientJson.js.map +1 -1
  17. package/lib/utils/internal/parseLenientJson.mjs +236 -96
  18. package/lib/utils/internal/parseLenientJson.mjs.map +1 -1
  19. package/lib/utils/internal/stringifyValidationFailure.js +17 -15
  20. package/lib/utils/internal/stringifyValidationFailure.js.map +1 -1
  21. package/lib/utils/internal/stringifyValidationFailure.mjs +17 -15
  22. package/lib/utils/internal/stringifyValidationFailure.mjs.map +1 -1
  23. package/package.json +2 -2
  24. package/src/converters/LlmSchemaConverter.ts +647 -647
  25. package/src/converters/OpenApiConverter.ts +285 -285
  26. package/src/converters/index.ts +5 -5
  27. package/src/converters/internal/LlmDescriptionInverter.ts +178 -178
  28. package/src/converters/internal/LlmParametersComposer.ts +52 -52
  29. package/src/converters/internal/OpenApiConstraintShifter.ts +154 -154
  30. package/src/converters/internal/OpenApiExclusiveEmender.ts +46 -46
  31. package/src/converters/internal/OpenApiV3Downgrader.ts +355 -355
  32. package/src/converters/internal/OpenApiV3Upgrader.ts +470 -470
  33. package/src/converters/internal/OpenApiV3_1Upgrader.ts +685 -685
  34. package/src/converters/internal/SwaggerV2Downgrader.ts +424 -424
  35. package/src/converters/internal/SwaggerV2Upgrader.ts +523 -523
  36. package/src/http/HttpError.ts +107 -107
  37. package/src/http/HttpLlm.ts +167 -167
  38. package/src/http/HttpMigration.ts +92 -92
  39. package/src/http/index.ts +3 -3
  40. package/src/http/internal/HttpLlmApplicationComposer.ts +361 -360
  41. package/src/http/internal/HttpLlmFunctionFetcher.ts +37 -37
  42. package/src/http/internal/HttpMigrateApplicationComposer.ts +56 -56
  43. package/src/http/internal/HttpMigrateRouteAccessor.ts +135 -135
  44. package/src/http/internal/HttpMigrateRouteComposer.ts +505 -505
  45. package/src/http/internal/HttpMigrateRouteFetcher.ts +203 -203
  46. package/src/index.ts +4 -4
  47. package/src/utils/ArrayUtil.ts +42 -42
  48. package/src/utils/LlmJson.ts +141 -141
  49. package/src/utils/MapUtil.ts +15 -15
  50. package/src/utils/NamingConvention.ts +205 -205
  51. package/src/utils/Singleton.ts +17 -17
  52. package/src/utils/StringUtil.ts +14 -14
  53. package/src/utils/dedent.ts +57 -57
  54. package/src/utils/index.ts +8 -8
  55. package/src/utils/internal/EndpointUtil.ts +44 -44
  56. package/src/utils/internal/JsonDescriptor.ts +70 -70
  57. package/src/utils/internal/OpenApiTypeCheckerBase.ts +822 -822
  58. package/src/utils/internal/coerceLlmArguments.ts +314 -297
  59. package/src/utils/internal/parseLenientJson.ts +894 -731
  60. package/src/utils/internal/stringifyValidationFailure.ts +415 -411
  61. package/src/validators/LlmTypeChecker.ts +402 -402
  62. package/src/validators/OpenApiTypeChecker.ts +297 -297
  63. package/src/validators/OpenApiV3TypeChecker.ts +70 -70
  64. package/src/validators/OpenApiV3_1TypeChecker.ts +86 -86
  65. package/src/validators/OpenApiValidator.ts +94 -94
  66. package/src/validators/SwaggerV2TypeChecker.ts +71 -71
  67. package/src/validators/functional/_isBigintString.ts +8 -8
  68. package/src/validators/functional/_isFormatByte.ts +7 -7
  69. package/src/validators/functional/_isFormatDate.ts +3 -3
  70. package/src/validators/functional/_isFormatDateTime.ts +4 -4
  71. package/src/validators/functional/_isFormatDuration.ts +4 -4
  72. package/src/validators/functional/_isFormatEmail.ts +4 -4
  73. package/src/validators/functional/_isFormatHostname.ts +4 -4
  74. package/src/validators/functional/_isFormatIdnEmail.ts +4 -4
  75. package/src/validators/functional/_isFormatIdnHostname.ts +4 -4
  76. package/src/validators/functional/_isFormatIpv4.ts +4 -4
  77. package/src/validators/functional/_isFormatIpv6.ts +4 -4
  78. package/src/validators/functional/_isFormatIri.ts +3 -3
  79. package/src/validators/functional/_isFormatIriReference.ts +4 -4
  80. package/src/validators/functional/_isFormatJsonPointer.ts +3 -3
  81. package/src/validators/functional/_isFormatPassword.ts +1 -1
  82. package/src/validators/functional/_isFormatRegex.ts +8 -8
  83. package/src/validators/functional/_isFormatRelativeJsonPointer.ts +4 -4
  84. package/src/validators/functional/_isFormatTime.ts +4 -4
  85. package/src/validators/functional/_isFormatUri.ts +6 -6
  86. package/src/validators/functional/_isFormatUriReference.ts +5 -5
  87. package/src/validators/functional/_isFormatUriTemplate.ts +4 -4
  88. package/src/validators/functional/_isFormatUrl.ts +4 -4
  89. package/src/validators/functional/_isFormatUuid.ts +3 -3
  90. package/src/validators/functional/_isUniqueItems.ts +159 -159
  91. package/src/validators/index.ts +14 -14
  92. package/src/validators/internal/IOpenApiValidatorContext.ts +17 -17
  93. package/src/validators/internal/OpenApiArrayValidator.ts +49 -49
  94. package/src/validators/internal/OpenApiBooleanValidator.ts +11 -11
  95. package/src/validators/internal/OpenApiConstantValidator.ts +11 -11
  96. package/src/validators/internal/OpenApiIntegerValidator.ts +49 -49
  97. package/src/validators/internal/OpenApiNumberValidator.ts +48 -48
  98. package/src/validators/internal/OpenApiObjectValidator.ts +83 -83
  99. package/src/validators/internal/OpenApiOneOfValidator.ts +309 -309
  100. package/src/validators/internal/OpenApiSchemaNamingRule.ts +124 -124
  101. package/src/validators/internal/OpenApiStationValidator.ts +115 -115
  102. package/src/validators/internal/OpenApiStringValidator.ts +88 -88
  103. package/src/validators/internal/OpenApiTupleValidator.ts +55 -55
@@ -1,470 +1,470 @@
1
- import { IJsonSchemaAttribute, OpenApi, OpenApiV3 } from "@typia/interface";
2
-
3
- import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
- import { OpenApiV3TypeChecker } from "../../validators/OpenApiV3TypeChecker";
5
- import { OpenApiExclusiveEmender } from "./OpenApiExclusiveEmender";
6
-
7
- export namespace OpenApiV3Upgrader {
8
- export const convert = (input: OpenApiV3.IDocument): OpenApi.IDocument => ({
9
- ...input,
10
- components: convertComponents(input.components ?? {}),
11
- paths: input.paths
12
- ? Object.fromEntries(
13
- Object.entries(input.paths)
14
- .filter(([_, v]) => v !== undefined)
15
- .map(
16
- ([key, value]) => [key, convertPathItem(input)(value)] as const,
17
- ),
18
- )
19
- : undefined,
20
- openapi: "3.1.0",
21
- "x-samchon-emended-v4": true,
22
- });
23
-
24
- /* -----------------------------------------------------------
25
- OPERATORS
26
- ----------------------------------------------------------- */
27
- const convertPathItem =
28
- (doc: OpenApiV3.IDocument) =>
29
- (pathItem: OpenApiV3.IPath): OpenApi.IPath => ({
30
- ...(pathItem as any),
31
- ...(pathItem.get
32
- ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
33
- : undefined),
34
- ...(pathItem.put
35
- ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
36
- : undefined),
37
- ...(pathItem.post
38
- ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
39
- : undefined),
40
- ...(pathItem.delete
41
- ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
42
- : undefined),
43
- ...(pathItem.options
44
- ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
45
- : undefined),
46
- ...(pathItem.head
47
- ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
48
- : undefined),
49
- ...(pathItem.patch
50
- ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
51
- : undefined),
52
- ...(pathItem.trace
53
- ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
54
- : undefined),
55
- });
56
-
57
- const convertOperation =
58
- (doc: OpenApiV3.IDocument) =>
59
- (pathItem: OpenApiV3.IPath) =>
60
- (input: OpenApiV3.IOperation): OpenApi.IOperation => ({
61
- ...input,
62
- parameters:
63
- pathItem.parameters !== undefined || input.parameters !== undefined
64
- ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
65
- .map((p) => {
66
- if (!OpenApiV3TypeChecker.isReference(p))
67
- return convertParameter(doc.components ?? {})(p);
68
- const found:
69
- | Omit<OpenApiV3.IOperation.IParameter, "in">
70
- | undefined = p.$ref.startsWith("#/components/headers/")
71
- ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""]
72
- : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""];
73
- return found !== undefined
74
- ? convertParameter(doc.components ?? {})({
75
- ...found,
76
- in: "header",
77
- })
78
- : undefined!;
79
- })
80
- .filter((_, v) => v !== undefined)
81
- : undefined,
82
- requestBody: input.requestBody
83
- ? convertRequestBody(doc)(input.requestBody)
84
- : undefined,
85
- responses: input.responses
86
- ? Object.fromEntries(
87
- Object.entries(input.responses)
88
- .filter(([_, v]) => v !== undefined)
89
- .map(
90
- ([key, value]) => [key, convertResponse(doc)(value)!] as const,
91
- )
92
- .filter(([_, v]) => v !== undefined),
93
- )
94
- : undefined,
95
- });
96
-
97
- const convertParameter =
98
- (components: OpenApiV3.IComponents) =>
99
- (
100
- input: OpenApiV3.IOperation.IParameter,
101
- ): OpenApi.IOperation.IParameter => ({
102
- ...input,
103
- schema: convertSchema(components)(input.schema),
104
- examples: input.examples
105
- ? Object.fromEntries(
106
- Object.entries(input.examples)
107
- .map(([key, value]) => [
108
- key,
109
- OpenApiV3TypeChecker.isReference(value)
110
- ? components.examples?.[value.$ref.split("/").pop() ?? ""]
111
- : value,
112
- ])
113
- .filter(([_, v]) => v !== undefined),
114
- )
115
- : undefined,
116
- });
117
-
118
- const convertRequestBody =
119
- (doc: OpenApiV3.IDocument) =>
120
- (
121
- input:
122
- | OpenApiV3.IOperation.IRequestBody
123
- | OpenApiV3.IJsonSchema.IReference<`#/components/requestBodies/${string}`>,
124
- ): OpenApi.IOperation.IRequestBody | undefined => {
125
- if (OpenApiV3TypeChecker.isReference(input)) {
126
- const found: OpenApiV3.IOperation.IRequestBody | undefined =
127
- doc.components?.requestBodies?.[input.$ref.split("/").pop() ?? ""];
128
- if (found === undefined) return undefined;
129
- input = found;
130
- }
131
- return {
132
- ...input,
133
- content: input.content
134
- ? convertContent(doc.components ?? {})(input.content)
135
- : undefined,
136
- };
137
- };
138
-
139
- const convertResponse =
140
- (doc: OpenApiV3.IDocument) =>
141
- (
142
- input:
143
- | OpenApiV3.IOperation.IResponse
144
- | OpenApiV3.IJsonSchema.IReference<`#/components/responses/${string}`>,
145
- ): OpenApi.IOperation.IResponse | undefined => {
146
- if (OpenApiV3TypeChecker.isReference(input)) {
147
- const found: OpenApiV3.IOperation.IResponse | undefined =
148
- doc.components?.responses?.[input.$ref.split("/").pop() ?? ""];
149
- if (found === undefined) return undefined;
150
- input = found;
151
- }
152
- return {
153
- ...input,
154
- content: input.content
155
- ? convertContent(doc.components ?? {})(input.content)
156
- : undefined,
157
- headers: input.headers
158
- ? Object.fromEntries(
159
- Object.entries(input.headers)
160
- .filter(([_, v]) => v !== undefined)
161
- .map(
162
- ([key, value]) =>
163
- [
164
- key,
165
- (() => {
166
- if (OpenApiV3TypeChecker.isReference(value) === false)
167
- return convertParameter(doc.components ?? {})({
168
- ...value,
169
- in: "header",
170
- });
171
- const found:
172
- | Omit<OpenApiV3.IOperation.IParameter, "in">
173
- | undefined = value.$ref.startsWith(
174
- "#/components/headers/",
175
- )
176
- ? doc.components?.headers?.[
177
- value.$ref.split("/").pop() ?? ""
178
- ]
179
- : undefined;
180
- return found !== undefined
181
- ? convertParameter(doc.components ?? {})({
182
- ...found,
183
- in: "header",
184
- })
185
- : undefined!;
186
- })(),
187
- ] as const,
188
- )
189
- .filter(([_, v]) => v !== undefined),
190
- )
191
- : undefined,
192
- };
193
- };
194
-
195
- const convertContent =
196
- (components: OpenApiV3.IComponents) =>
197
- (
198
- record: Record<string, OpenApiV3.IOperation.IMediaType>,
199
- ): Record<string, OpenApi.IOperation.IMediaType> =>
200
- Object.fromEntries(
201
- Object.entries(record)
202
- .filter(([_, v]) => v !== undefined)
203
- .map(
204
- ([key, value]) =>
205
- [
206
- key,
207
- {
208
- ...value,
209
- schema: value.schema
210
- ? convertSchema(components)(value.schema)
211
- : undefined,
212
- examples: value.examples
213
- ? Object.fromEntries(
214
- Object.entries(value.examples)
215
- .map(([key, value]) => [
216
- key,
217
- OpenApiV3TypeChecker.isReference(value)
218
- ? components.examples?.[
219
- value.$ref.split("/").pop() ?? ""
220
- ]
221
- : value,
222
- ])
223
- .filter(([_, v]) => v !== undefined),
224
- )
225
- : undefined,
226
- },
227
- ] as const,
228
- ),
229
- );
230
-
231
- /* -----------------------------------------------------------
232
- DEFINITIONS
233
- ----------------------------------------------------------- */
234
- export const convertComponents = (
235
- input: OpenApiV3.IComponents,
236
- ): OpenApi.IComponents => ({
237
- schemas: Object.fromEntries(
238
- Object.entries(input.schemas ?? {})
239
- .filter(([_, v]) => v !== undefined)
240
- .map(([key, value]) => [key, convertSchema(input)(value)]),
241
- ),
242
- securitySchemes: input.securitySchemes,
243
- });
244
-
245
- export const convertSchema =
246
- (components: OpenApiV3.IComponents) =>
247
- (input: OpenApiV3.IJsonSchema): OpenApi.IJsonSchema => {
248
- const nullable: { value: boolean; default?: null } = {
249
- value: false,
250
- default: undefined,
251
- };
252
- const union: OpenApi.IJsonSchema[] = [];
253
- const attribute: IJsonSchemaAttribute = {
254
- title: input.title,
255
- description: input.description,
256
- deprecated: input.deprecated,
257
- readOnly: input.readOnly,
258
- writeOnly: input.writeOnly,
259
- example: input.example,
260
- examples: Array.isArray(input.examples)
261
- ? Object.fromEntries(input.examples.map((v, i) => [`v${i}`, v]))
262
- : input.examples,
263
- ...Object.fromEntries(
264
- Object.entries(input).filter(
265
- ([key, value]) => key.startsWith("x-") && value !== undefined,
266
- ),
267
- ),
268
- };
269
- const visit = (schema: OpenApiV3.IJsonSchema): void => {
270
- // NULLABLE PROPERTY
271
- if ((schema as OpenApiV3.IJsonSchema.IBoolean).nullable === true) {
272
- nullable.value ||= true;
273
- if ((schema as OpenApiV3.IJsonSchema.IBoolean).default === null)
274
- nullable.default = null;
275
- }
276
- if (
277
- Array.isArray((schema as OpenApiV3.IJsonSchema.INumber).enum) &&
278
- (schema as OpenApiV3.IJsonSchema.INumber).enum?.length &&
279
- (schema as OpenApiV3.IJsonSchema.INumber).enum?.some(
280
- (e) => e === null,
281
- )
282
- )
283
- nullable.value ||= true;
284
- // UNION TYPE CASE
285
- if (OpenApiV3TypeChecker.isAnyOf(schema)) schema.anyOf.forEach(visit);
286
- else if (OpenApiV3TypeChecker.isOneOf(schema))
287
- schema.oneOf.forEach(visit);
288
- else if (OpenApiV3TypeChecker.isAllOf(schema))
289
- if (schema.allOf.length === 1) visit(schema.allOf[0]!);
290
- else union.push(convertAllOfSchema(components)(schema));
291
- // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
292
- else if (
293
- OpenApiV3TypeChecker.isBoolean(schema) ||
294
- OpenApiV3TypeChecker.isInteger(schema) ||
295
- OpenApiV3TypeChecker.isNumber(schema) ||
296
- OpenApiV3TypeChecker.isString(schema)
297
- )
298
- if (
299
- schema.enum?.length &&
300
- schema.enum.filter((e) => e !== null).length
301
- )
302
- union.push(
303
- ...schema.enum
304
- .filter((v) => v !== null)
305
- .map((value) => ({ const: value })),
306
- );
307
- else if (
308
- OpenApiV3TypeChecker.isInteger(schema) ||
309
- OpenApiV3TypeChecker.isNumber(schema)
310
- )
311
- union.push(
312
- OpenApiExclusiveEmender.emend({
313
- ...schema,
314
- default: (schema.default ?? undefined) satisfies
315
- | boolean
316
- | number
317
- | string
318
- | undefined as any,
319
- exclusiveMinimum:
320
- typeof schema.exclusiveMinimum === "boolean"
321
- ? schema.exclusiveMinimum === true
322
- ? schema.minimum
323
- : undefined
324
- : schema.exclusiveMinimum,
325
- exclusiveMaximum:
326
- typeof schema.exclusiveMaximum === "boolean"
327
- ? schema.exclusiveMaximum === true
328
- ? schema.maximum
329
- : undefined
330
- : schema.exclusiveMaximum,
331
- minimum:
332
- schema.exclusiveMinimum === true ? undefined : schema.minimum,
333
- maximum:
334
- schema.exclusiveMaximum === true ? undefined : schema.maximum,
335
- ...{ enum: undefined },
336
- }),
337
- );
338
- else
339
- union.push({
340
- ...schema,
341
- default: (schema.default ?? undefined) satisfies
342
- | boolean
343
- | number
344
- | string
345
- | undefined as any,
346
- ...{ enum: undefined },
347
- });
348
- // INSTANCE TYPE CASE
349
- else if (OpenApiV3TypeChecker.isArray(schema))
350
- union.push({
351
- ...schema,
352
- items: convertSchema(components)(schema.items),
353
- });
354
- else if (OpenApiV3TypeChecker.isObject(schema))
355
- union.push({
356
- ...schema,
357
- ...{
358
- properties: schema.properties
359
- ? Object.fromEntries(
360
- Object.entries(schema.properties)
361
- .filter(([_, v]) => v !== undefined)
362
- .map(([key, value]) => [
363
- key,
364
- convertSchema(components)(value),
365
- ]),
366
- )
367
- : {},
368
- additionalProperties: schema.additionalProperties
369
- ? typeof schema.additionalProperties === "object" &&
370
- schema.additionalProperties !== null
371
- ? convertSchema(components)(schema.additionalProperties)
372
- : schema.additionalProperties
373
- : undefined,
374
- required: schema.required ?? [],
375
- },
376
- });
377
- else if (OpenApiV3TypeChecker.isReference(schema)) union.push(schema);
378
- else union.push(schema);
379
- };
380
-
381
- visit(input);
382
- if (
383
- nullable.value === true &&
384
- !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
385
- )
386
- union.push({
387
- type: "null",
388
- default: nullable.default,
389
- });
390
- if (
391
- union.length === 2 &&
392
- union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
393
- ) {
394
- const type: OpenApi.IJsonSchema = union.filter(
395
- (x) => OpenApiTypeChecker.isNull(x) === false,
396
- )[0]!;
397
- for (const key of [
398
- "title",
399
- "description",
400
- "deprecated",
401
- "readOnly",
402
- "writeOnly",
403
- "example",
404
- "examples",
405
- ] as const)
406
- if (type[key] !== undefined) delete type[key];
407
- }
408
- return {
409
- ...(union.length === 0
410
- ? { type: undefined }
411
- : union.length === 1
412
- ? { ...union[0] }
413
- : { oneOf: union.map((u) => ({ ...u, nullable: undefined })) }),
414
- ...attribute,
415
- ...{ nullable: undefined },
416
- };
417
- };
418
-
419
- const convertAllOfSchema =
420
- (components: OpenApiV3.IComponents) =>
421
- (input: OpenApiV3.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
422
- const objects: Array<OpenApiV3.IJsonSchema.IObject | null> =
423
- input.allOf.map((schema) => retrieveObject(components)(schema));
424
- if (objects.some((obj) => obj === null))
425
- return {
426
- type: undefined,
427
- ...{
428
- allOf: undefined,
429
- },
430
- };
431
- return {
432
- ...input,
433
- type: "object",
434
- properties: Object.fromEntries(
435
- objects
436
- .map((o) => Object.entries(o?.properties ?? {}))
437
- .flat()
438
- .map(
439
- ([key, value]) =>
440
- [key, convertSchema(components)(value)] as const,
441
- ),
442
- ),
443
- ...{
444
- allOf: undefined,
445
- required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
446
- },
447
- };
448
- };
449
-
450
- const retrieveObject =
451
- (components: OpenApiV3.IComponents) =>
452
- (
453
- input: OpenApiV3.IJsonSchema,
454
- visited: Set<OpenApiV3.IJsonSchema> = new Set(),
455
- ): OpenApiV3.IJsonSchema.IObject | null => {
456
- if (OpenApiV3TypeChecker.isObject(input))
457
- return input.properties !== undefined && !input.additionalProperties
458
- ? input
459
- : null;
460
- else if (visited.has(input)) return null;
461
- else visited.add(input);
462
-
463
- if (OpenApiV3TypeChecker.isReference(input))
464
- return retrieveObject(components)(
465
- components.schemas?.[input.$ref.split("/").pop() ?? ""] ?? {},
466
- visited,
467
- );
468
- return null;
469
- };
470
- }
1
+ import { IJsonSchemaAttribute, OpenApi, OpenApiV3 } from "@typia/interface";
2
+
3
+ import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
+ import { OpenApiV3TypeChecker } from "../../validators/OpenApiV3TypeChecker";
5
+ import { OpenApiExclusiveEmender } from "./OpenApiExclusiveEmender";
6
+
7
+ export namespace OpenApiV3Upgrader {
8
+ export const convert = (input: OpenApiV3.IDocument): OpenApi.IDocument => ({
9
+ ...input,
10
+ components: convertComponents(input.components ?? {}),
11
+ paths: input.paths
12
+ ? Object.fromEntries(
13
+ Object.entries(input.paths)
14
+ .filter(([_, v]) => v !== undefined)
15
+ .map(
16
+ ([key, value]) => [key, convertPathItem(input)(value)] as const,
17
+ ),
18
+ )
19
+ : undefined,
20
+ openapi: "3.1.0",
21
+ "x-samchon-emended-v4": true,
22
+ });
23
+
24
+ /* -----------------------------------------------------------
25
+ OPERATORS
26
+ ----------------------------------------------------------- */
27
+ const convertPathItem =
28
+ (doc: OpenApiV3.IDocument) =>
29
+ (pathItem: OpenApiV3.IPath): OpenApi.IPath => ({
30
+ ...(pathItem as any),
31
+ ...(pathItem.get
32
+ ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
33
+ : undefined),
34
+ ...(pathItem.put
35
+ ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
36
+ : undefined),
37
+ ...(pathItem.post
38
+ ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
39
+ : undefined),
40
+ ...(pathItem.delete
41
+ ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
42
+ : undefined),
43
+ ...(pathItem.options
44
+ ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
45
+ : undefined),
46
+ ...(pathItem.head
47
+ ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
48
+ : undefined),
49
+ ...(pathItem.patch
50
+ ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
51
+ : undefined),
52
+ ...(pathItem.trace
53
+ ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
54
+ : undefined),
55
+ });
56
+
57
+ const convertOperation =
58
+ (doc: OpenApiV3.IDocument) =>
59
+ (pathItem: OpenApiV3.IPath) =>
60
+ (input: OpenApiV3.IOperation): OpenApi.IOperation => ({
61
+ ...input,
62
+ parameters:
63
+ pathItem.parameters !== undefined || input.parameters !== undefined
64
+ ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
65
+ .map((p) => {
66
+ if (!OpenApiV3TypeChecker.isReference(p))
67
+ return convertParameter(doc.components ?? {})(p);
68
+ const found:
69
+ | Omit<OpenApiV3.IOperation.IParameter, "in">
70
+ | undefined = p.$ref.startsWith("#/components/headers/")
71
+ ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""]
72
+ : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""];
73
+ return found !== undefined
74
+ ? convertParameter(doc.components ?? {})({
75
+ ...found,
76
+ in: "header",
77
+ })
78
+ : undefined!;
79
+ })
80
+ .filter((_, v) => v !== undefined)
81
+ : undefined,
82
+ requestBody: input.requestBody
83
+ ? convertRequestBody(doc)(input.requestBody)
84
+ : undefined,
85
+ responses: input.responses
86
+ ? Object.fromEntries(
87
+ Object.entries(input.responses)
88
+ .filter(([_, v]) => v !== undefined)
89
+ .map(
90
+ ([key, value]) => [key, convertResponse(doc)(value)!] as const,
91
+ )
92
+ .filter(([_, v]) => v !== undefined),
93
+ )
94
+ : undefined,
95
+ });
96
+
97
+ const convertParameter =
98
+ (components: OpenApiV3.IComponents) =>
99
+ (
100
+ input: OpenApiV3.IOperation.IParameter,
101
+ ): OpenApi.IOperation.IParameter => ({
102
+ ...input,
103
+ schema: convertSchema(components)(input.schema),
104
+ examples: input.examples
105
+ ? Object.fromEntries(
106
+ Object.entries(input.examples)
107
+ .map(([key, value]) => [
108
+ key,
109
+ OpenApiV3TypeChecker.isReference(value)
110
+ ? components.examples?.[value.$ref.split("/").pop() ?? ""]
111
+ : value,
112
+ ])
113
+ .filter(([_, v]) => v !== undefined),
114
+ )
115
+ : undefined,
116
+ });
117
+
118
+ const convertRequestBody =
119
+ (doc: OpenApiV3.IDocument) =>
120
+ (
121
+ input:
122
+ | OpenApiV3.IOperation.IRequestBody
123
+ | OpenApiV3.IJsonSchema.IReference<`#/components/requestBodies/${string}`>,
124
+ ): OpenApi.IOperation.IRequestBody | undefined => {
125
+ if (OpenApiV3TypeChecker.isReference(input)) {
126
+ const found: OpenApiV3.IOperation.IRequestBody | undefined =
127
+ doc.components?.requestBodies?.[input.$ref.split("/").pop() ?? ""];
128
+ if (found === undefined) return undefined;
129
+ input = found;
130
+ }
131
+ return {
132
+ ...input,
133
+ content: input.content
134
+ ? convertContent(doc.components ?? {})(input.content)
135
+ : undefined,
136
+ };
137
+ };
138
+
139
+ const convertResponse =
140
+ (doc: OpenApiV3.IDocument) =>
141
+ (
142
+ input:
143
+ | OpenApiV3.IOperation.IResponse
144
+ | OpenApiV3.IJsonSchema.IReference<`#/components/responses/${string}`>,
145
+ ): OpenApi.IOperation.IResponse | undefined => {
146
+ if (OpenApiV3TypeChecker.isReference(input)) {
147
+ const found: OpenApiV3.IOperation.IResponse | undefined =
148
+ doc.components?.responses?.[input.$ref.split("/").pop() ?? ""];
149
+ if (found === undefined) return undefined;
150
+ input = found;
151
+ }
152
+ return {
153
+ ...input,
154
+ content: input.content
155
+ ? convertContent(doc.components ?? {})(input.content)
156
+ : undefined,
157
+ headers: input.headers
158
+ ? Object.fromEntries(
159
+ Object.entries(input.headers)
160
+ .filter(([_, v]) => v !== undefined)
161
+ .map(
162
+ ([key, value]) =>
163
+ [
164
+ key,
165
+ (() => {
166
+ if (OpenApiV3TypeChecker.isReference(value) === false)
167
+ return convertParameter(doc.components ?? {})({
168
+ ...value,
169
+ in: "header",
170
+ });
171
+ const found:
172
+ | Omit<OpenApiV3.IOperation.IParameter, "in">
173
+ | undefined = value.$ref.startsWith(
174
+ "#/components/headers/",
175
+ )
176
+ ? doc.components?.headers?.[
177
+ value.$ref.split("/").pop() ?? ""
178
+ ]
179
+ : undefined;
180
+ return found !== undefined
181
+ ? convertParameter(doc.components ?? {})({
182
+ ...found,
183
+ in: "header",
184
+ })
185
+ : undefined!;
186
+ })(),
187
+ ] as const,
188
+ )
189
+ .filter(([_, v]) => v !== undefined),
190
+ )
191
+ : undefined,
192
+ };
193
+ };
194
+
195
+ const convertContent =
196
+ (components: OpenApiV3.IComponents) =>
197
+ (
198
+ record: Record<string, OpenApiV3.IOperation.IMediaType>,
199
+ ): Record<string, OpenApi.IOperation.IMediaType> =>
200
+ Object.fromEntries(
201
+ Object.entries(record)
202
+ .filter(([_, v]) => v !== undefined)
203
+ .map(
204
+ ([key, value]) =>
205
+ [
206
+ key,
207
+ {
208
+ ...value,
209
+ schema: value.schema
210
+ ? convertSchema(components)(value.schema)
211
+ : undefined,
212
+ examples: value.examples
213
+ ? Object.fromEntries(
214
+ Object.entries(value.examples)
215
+ .map(([key, value]) => [
216
+ key,
217
+ OpenApiV3TypeChecker.isReference(value)
218
+ ? components.examples?.[
219
+ value.$ref.split("/").pop() ?? ""
220
+ ]
221
+ : value,
222
+ ])
223
+ .filter(([_, v]) => v !== undefined),
224
+ )
225
+ : undefined,
226
+ },
227
+ ] as const,
228
+ ),
229
+ );
230
+
231
+ /* -----------------------------------------------------------
232
+ DEFINITIONS
233
+ ----------------------------------------------------------- */
234
+ export const convertComponents = (
235
+ input: OpenApiV3.IComponents,
236
+ ): OpenApi.IComponents => ({
237
+ schemas: Object.fromEntries(
238
+ Object.entries(input.schemas ?? {})
239
+ .filter(([_, v]) => v !== undefined)
240
+ .map(([key, value]) => [key, convertSchema(input)(value)]),
241
+ ),
242
+ securitySchemes: input.securitySchemes,
243
+ });
244
+
245
+ export const convertSchema =
246
+ (components: OpenApiV3.IComponents) =>
247
+ (input: OpenApiV3.IJsonSchema): OpenApi.IJsonSchema => {
248
+ const nullable: { value: boolean; default?: null } = {
249
+ value: false,
250
+ default: undefined,
251
+ };
252
+ const union: OpenApi.IJsonSchema[] = [];
253
+ const attribute: IJsonSchemaAttribute = {
254
+ title: input.title,
255
+ description: input.description,
256
+ deprecated: input.deprecated,
257
+ readOnly: input.readOnly,
258
+ writeOnly: input.writeOnly,
259
+ example: input.example,
260
+ examples: Array.isArray(input.examples)
261
+ ? Object.fromEntries(input.examples.map((v, i) => [`v${i}`, v]))
262
+ : input.examples,
263
+ ...Object.fromEntries(
264
+ Object.entries(input).filter(
265
+ ([key, value]) => key.startsWith("x-") && value !== undefined,
266
+ ),
267
+ ),
268
+ };
269
+ const visit = (schema: OpenApiV3.IJsonSchema): void => {
270
+ // NULLABLE PROPERTY
271
+ if ((schema as OpenApiV3.IJsonSchema.IBoolean).nullable === true) {
272
+ nullable.value ||= true;
273
+ if ((schema as OpenApiV3.IJsonSchema.IBoolean).default === null)
274
+ nullable.default = null;
275
+ }
276
+ if (
277
+ Array.isArray((schema as OpenApiV3.IJsonSchema.INumber).enum) &&
278
+ (schema as OpenApiV3.IJsonSchema.INumber).enum?.length &&
279
+ (schema as OpenApiV3.IJsonSchema.INumber).enum?.some(
280
+ (e) => e === null,
281
+ )
282
+ )
283
+ nullable.value ||= true;
284
+ // UNION TYPE CASE
285
+ if (OpenApiV3TypeChecker.isAnyOf(schema)) schema.anyOf.forEach(visit);
286
+ else if (OpenApiV3TypeChecker.isOneOf(schema))
287
+ schema.oneOf.forEach(visit);
288
+ else if (OpenApiV3TypeChecker.isAllOf(schema))
289
+ if (schema.allOf.length === 1) visit(schema.allOf[0]!);
290
+ else union.push(convertAllOfSchema(components)(schema));
291
+ // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
292
+ else if (
293
+ OpenApiV3TypeChecker.isBoolean(schema) ||
294
+ OpenApiV3TypeChecker.isInteger(schema) ||
295
+ OpenApiV3TypeChecker.isNumber(schema) ||
296
+ OpenApiV3TypeChecker.isString(schema)
297
+ )
298
+ if (
299
+ schema.enum?.length &&
300
+ schema.enum.filter((e) => e !== null).length
301
+ )
302
+ union.push(
303
+ ...schema.enum
304
+ .filter((v) => v !== null)
305
+ .map((value) => ({ const: value })),
306
+ );
307
+ else if (
308
+ OpenApiV3TypeChecker.isInteger(schema) ||
309
+ OpenApiV3TypeChecker.isNumber(schema)
310
+ )
311
+ union.push(
312
+ OpenApiExclusiveEmender.emend({
313
+ ...schema,
314
+ default: (schema.default ?? undefined) satisfies
315
+ | boolean
316
+ | number
317
+ | string
318
+ | undefined as any,
319
+ exclusiveMinimum:
320
+ typeof schema.exclusiveMinimum === "boolean"
321
+ ? schema.exclusiveMinimum === true
322
+ ? schema.minimum
323
+ : undefined
324
+ : schema.exclusiveMinimum,
325
+ exclusiveMaximum:
326
+ typeof schema.exclusiveMaximum === "boolean"
327
+ ? schema.exclusiveMaximum === true
328
+ ? schema.maximum
329
+ : undefined
330
+ : schema.exclusiveMaximum,
331
+ minimum:
332
+ schema.exclusiveMinimum === true ? undefined : schema.minimum,
333
+ maximum:
334
+ schema.exclusiveMaximum === true ? undefined : schema.maximum,
335
+ ...{ enum: undefined },
336
+ }),
337
+ );
338
+ else
339
+ union.push({
340
+ ...schema,
341
+ default: (schema.default ?? undefined) satisfies
342
+ | boolean
343
+ | number
344
+ | string
345
+ | undefined as any,
346
+ ...{ enum: undefined },
347
+ });
348
+ // INSTANCE TYPE CASE
349
+ else if (OpenApiV3TypeChecker.isArray(schema))
350
+ union.push({
351
+ ...schema,
352
+ items: convertSchema(components)(schema.items),
353
+ });
354
+ else if (OpenApiV3TypeChecker.isObject(schema))
355
+ union.push({
356
+ ...schema,
357
+ ...{
358
+ properties: schema.properties
359
+ ? Object.fromEntries(
360
+ Object.entries(schema.properties)
361
+ .filter(([_, v]) => v !== undefined)
362
+ .map(([key, value]) => [
363
+ key,
364
+ convertSchema(components)(value),
365
+ ]),
366
+ )
367
+ : {},
368
+ additionalProperties: schema.additionalProperties
369
+ ? typeof schema.additionalProperties === "object" &&
370
+ schema.additionalProperties !== null
371
+ ? convertSchema(components)(schema.additionalProperties)
372
+ : schema.additionalProperties
373
+ : undefined,
374
+ required: schema.required ?? [],
375
+ },
376
+ });
377
+ else if (OpenApiV3TypeChecker.isReference(schema)) union.push(schema);
378
+ else union.push(schema);
379
+ };
380
+
381
+ visit(input);
382
+ if (
383
+ nullable.value === true &&
384
+ !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
385
+ )
386
+ union.push({
387
+ type: "null",
388
+ default: nullable.default,
389
+ });
390
+ if (
391
+ union.length === 2 &&
392
+ union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
393
+ ) {
394
+ const type: OpenApi.IJsonSchema = union.filter(
395
+ (x) => OpenApiTypeChecker.isNull(x) === false,
396
+ )[0]!;
397
+ for (const key of [
398
+ "title",
399
+ "description",
400
+ "deprecated",
401
+ "readOnly",
402
+ "writeOnly",
403
+ "example",
404
+ "examples",
405
+ ] as const)
406
+ if (type[key] !== undefined) delete type[key];
407
+ }
408
+ return {
409
+ ...(union.length === 0
410
+ ? { type: undefined }
411
+ : union.length === 1
412
+ ? { ...union[0] }
413
+ : { oneOf: union.map((u) => ({ ...u, nullable: undefined })) }),
414
+ ...attribute,
415
+ ...{ nullable: undefined },
416
+ };
417
+ };
418
+
419
+ const convertAllOfSchema =
420
+ (components: OpenApiV3.IComponents) =>
421
+ (input: OpenApiV3.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
422
+ const objects: Array<OpenApiV3.IJsonSchema.IObject | null> =
423
+ input.allOf.map((schema) => retrieveObject(components)(schema));
424
+ if (objects.some((obj) => obj === null))
425
+ return {
426
+ type: undefined,
427
+ ...{
428
+ allOf: undefined,
429
+ },
430
+ };
431
+ return {
432
+ ...input,
433
+ type: "object",
434
+ properties: Object.fromEntries(
435
+ objects
436
+ .map((o) => Object.entries(o?.properties ?? {}))
437
+ .flat()
438
+ .map(
439
+ ([key, value]) =>
440
+ [key, convertSchema(components)(value)] as const,
441
+ ),
442
+ ),
443
+ ...{
444
+ allOf: undefined,
445
+ required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
446
+ },
447
+ };
448
+ };
449
+
450
+ const retrieveObject =
451
+ (components: OpenApiV3.IComponents) =>
452
+ (
453
+ input: OpenApiV3.IJsonSchema,
454
+ visited: Set<OpenApiV3.IJsonSchema> = new Set(),
455
+ ): OpenApiV3.IJsonSchema.IObject | null => {
456
+ if (OpenApiV3TypeChecker.isObject(input))
457
+ return input.properties !== undefined && !input.additionalProperties
458
+ ? input
459
+ : null;
460
+ else if (visited.has(input)) return null;
461
+ else visited.add(input);
462
+
463
+ if (OpenApiV3TypeChecker.isReference(input))
464
+ return retrieveObject(components)(
465
+ components.schemas?.[input.$ref.split("/").pop() ?? ""] ?? {},
466
+ visited,
467
+ );
468
+ return null;
469
+ };
470
+ }