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