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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/lib/converters/OpenApiConverter.d.ts +29 -16
  2. package/lib/converters/OpenApiConverter.js +25 -15
  3. package/lib/converters/OpenApiConverter.js.map +1 -1
  4. package/lib/converters/OpenApiConverter.mjs +25 -15
  5. package/lib/converters/OpenApiConverter.mjs.map +1 -1
  6. package/lib/converters/internal/OpenApiV3Downgrader.js +38 -18
  7. package/lib/converters/internal/OpenApiV3Downgrader.js.map +1 -1
  8. package/lib/converters/internal/OpenApiV3Downgrader.mjs +50 -27
  9. package/lib/converters/internal/OpenApiV3Downgrader.mjs.map +1 -1
  10. package/lib/converters/internal/OpenApiV3Upgrader.js +36 -18
  11. package/lib/converters/internal/OpenApiV3Upgrader.js.map +1 -1
  12. package/lib/converters/internal/OpenApiV3Upgrader.mjs +50 -29
  13. package/lib/converters/internal/OpenApiV3Upgrader.mjs.map +1 -1
  14. package/lib/converters/internal/OpenApiV3_1Upgrader.js +38 -20
  15. package/lib/converters/internal/OpenApiV3_1Upgrader.js.map +1 -1
  16. package/lib/converters/internal/OpenApiV3_1Upgrader.mjs +51 -29
  17. package/lib/converters/internal/OpenApiV3_1Upgrader.mjs.map +1 -1
  18. package/lib/converters/internal/OpenApiV3_2Upgrader.d.ts +16 -0
  19. package/lib/converters/internal/OpenApiV3_2Upgrader.js +204 -0
  20. package/lib/converters/internal/OpenApiV3_2Upgrader.js.map +1 -0
  21. package/lib/converters/internal/OpenApiV3_2Upgrader.mjs +243 -0
  22. package/lib/converters/internal/OpenApiV3_2Upgrader.mjs.map +1 -0
  23. package/lib/converters/internal/SwaggerV2Downgrader.js +38 -18
  24. package/lib/converters/internal/SwaggerV2Downgrader.js.map +1 -1
  25. package/lib/converters/internal/SwaggerV2Downgrader.mjs +50 -27
  26. package/lib/converters/internal/SwaggerV2Downgrader.mjs.map +1 -1
  27. package/lib/converters/internal/SwaggerV2Upgrader.js +37 -19
  28. package/lib/converters/internal/SwaggerV2Upgrader.js.map +1 -1
  29. package/lib/converters/internal/SwaggerV2Upgrader.mjs +50 -29
  30. package/lib/converters/internal/SwaggerV2Upgrader.mjs.map +1 -1
  31. package/lib/http/internal/HttpMigrateApplicationComposer.js +1 -1
  32. package/lib/http/internal/HttpMigrateApplicationComposer.js.map +1 -1
  33. package/lib/http/internal/HttpMigrateApplicationComposer.mjs +1 -1
  34. package/lib/http/internal/HttpMigrateApplicationComposer.mjs.map +1 -1
  35. package/lib/http/internal/HttpMigrateRouteComposer.d.ts +1 -1
  36. package/lib/utils/LlmJson.d.ts +19 -1
  37. package/lib/utils/LlmJson.js +27 -0
  38. package/lib/utils/LlmJson.js.map +1 -1
  39. package/lib/utils/LlmJson.mjs +27 -0
  40. package/lib/utils/LlmJson.mjs.map +1 -1
  41. package/package.json +2 -2
  42. package/src/converters/OpenApiConverter.ts +322 -285
  43. package/src/converters/internal/OpenApiV3Downgrader.ts +381 -355
  44. package/src/converters/internal/OpenApiV3Upgrader.ts +494 -470
  45. package/src/converters/internal/OpenApiV3_1Upgrader.ts +710 -685
  46. package/src/converters/internal/OpenApiV3_2Upgrader.ts +342 -0
  47. package/src/converters/internal/SwaggerV2Downgrader.ts +450 -424
  48. package/src/converters/internal/SwaggerV2Upgrader.ts +547 -523
  49. package/src/http/internal/HttpMigrateApplicationComposer.ts +56 -56
  50. package/src/http/internal/HttpMigrateRouteComposer.ts +505 -505
  51. package/src/utils/LlmJson.ts +173 -141
@@ -1,470 +1,494 @@
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.2.0",
21
+ "x-typia-emended-v12": true,
22
+ });
23
+
24
+ /* -----------------------------------------------------------
25
+ OPERATORS
26
+ ----------------------------------------------------------- */
27
+ const convertPathItem =
28
+ (doc: OpenApiV3.IDocument) =>
29
+ (pathItem: OpenApiV3.IPath): OpenApi.IPath => {
30
+ // Convert x-additionalOperations to additionalOperations
31
+ // Promote "query" to standard method (it's a v3.2 standard method)
32
+ const xAdditional = pathItem["x-additionalOperations"];
33
+ const queryOp = xAdditional?.["query"];
34
+ const additionalOperations = xAdditional
35
+ ? Object.fromEntries(
36
+ Object.entries(xAdditional)
37
+ .filter(([key, v]) => key !== "query" && v !== undefined)
38
+ .map(([key, value]) => [
39
+ key,
40
+ convertOperation(doc)(pathItem)(value),
41
+ ]),
42
+ )
43
+ : undefined;
44
+
45
+ return {
46
+ ...(pathItem as any),
47
+ ...(pathItem.get
48
+ ? { get: convertOperation(doc)(pathItem)(pathItem.get) }
49
+ : undefined),
50
+ ...(pathItem.put
51
+ ? { put: convertOperation(doc)(pathItem)(pathItem.put) }
52
+ : undefined),
53
+ ...(pathItem.post
54
+ ? { post: convertOperation(doc)(pathItem)(pathItem.post) }
55
+ : undefined),
56
+ ...(pathItem.delete
57
+ ? { delete: convertOperation(doc)(pathItem)(pathItem.delete) }
58
+ : undefined),
59
+ ...(pathItem.options
60
+ ? { options: convertOperation(doc)(pathItem)(pathItem.options) }
61
+ : undefined),
62
+ ...(pathItem.head
63
+ ? { head: convertOperation(doc)(pathItem)(pathItem.head) }
64
+ : undefined),
65
+ ...(pathItem.patch
66
+ ? { patch: convertOperation(doc)(pathItem)(pathItem.patch) }
67
+ : undefined),
68
+ ...(pathItem.trace
69
+ ? { trace: convertOperation(doc)(pathItem)(pathItem.trace) }
70
+ : undefined),
71
+ ...(queryOp
72
+ ? { query: convertOperation(doc)(pathItem)(queryOp) }
73
+ : undefined),
74
+ ...(additionalOperations && Object.keys(additionalOperations).length > 0
75
+ ? { additionalOperations }
76
+ : undefined),
77
+ "x-additionalOperations": undefined,
78
+ };
79
+ };
80
+
81
+ const convertOperation =
82
+ (doc: OpenApiV3.IDocument) =>
83
+ (pathItem: OpenApiV3.IPath) =>
84
+ (input: OpenApiV3.IOperation): OpenApi.IOperation => ({
85
+ ...input,
86
+ parameters:
87
+ pathItem.parameters !== undefined || input.parameters !== undefined
88
+ ? [...(pathItem.parameters ?? []), ...(input.parameters ?? [])]
89
+ .map((p) => {
90
+ if (!OpenApiV3TypeChecker.isReference(p))
91
+ return convertParameter(doc.components ?? {})(p);
92
+ const found:
93
+ | Omit<OpenApiV3.IOperation.IParameter, "in">
94
+ | undefined = p.$ref.startsWith("#/components/headers/")
95
+ ? doc.components?.headers?.[p.$ref.split("/").pop() ?? ""]
96
+ : doc.components?.parameters?.[p.$ref.split("/").pop() ?? ""];
97
+ return found !== undefined
98
+ ? convertParameter(doc.components ?? {})({
99
+ ...found,
100
+ in: "header",
101
+ })
102
+ : undefined!;
103
+ })
104
+ .filter((_, v) => v !== undefined)
105
+ : undefined,
106
+ requestBody: input.requestBody
107
+ ? convertRequestBody(doc)(input.requestBody)
108
+ : undefined,
109
+ responses: input.responses
110
+ ? Object.fromEntries(
111
+ Object.entries(input.responses)
112
+ .filter(([_, v]) => v !== undefined)
113
+ .map(
114
+ ([key, value]) => [key, convertResponse(doc)(value)!] as const,
115
+ )
116
+ .filter(([_, v]) => v !== undefined),
117
+ )
118
+ : undefined,
119
+ });
120
+
121
+ const convertParameter =
122
+ (components: OpenApiV3.IComponents) =>
123
+ (
124
+ input: OpenApiV3.IOperation.IParameter,
125
+ ): OpenApi.IOperation.IParameter => ({
126
+ ...input,
127
+ schema: convertSchema(components)(input.schema),
128
+ examples: input.examples
129
+ ? Object.fromEntries(
130
+ Object.entries(input.examples)
131
+ .map(([key, value]) => [
132
+ key,
133
+ OpenApiV3TypeChecker.isReference(value)
134
+ ? components.examples?.[value.$ref.split("/").pop() ?? ""]
135
+ : value,
136
+ ])
137
+ .filter(([_, v]) => v !== undefined),
138
+ )
139
+ : undefined,
140
+ });
141
+
142
+ const convertRequestBody =
143
+ (doc: OpenApiV3.IDocument) =>
144
+ (
145
+ input:
146
+ | OpenApiV3.IOperation.IRequestBody
147
+ | OpenApiV3.IJsonSchema.IReference<`#/components/requestBodies/${string}`>,
148
+ ): OpenApi.IOperation.IRequestBody | undefined => {
149
+ if (OpenApiV3TypeChecker.isReference(input)) {
150
+ const found: OpenApiV3.IOperation.IRequestBody | undefined =
151
+ doc.components?.requestBodies?.[input.$ref.split("/").pop() ?? ""];
152
+ if (found === undefined) return undefined;
153
+ input = found;
154
+ }
155
+ return {
156
+ ...input,
157
+ content: input.content
158
+ ? convertContent(doc.components ?? {})(input.content)
159
+ : undefined,
160
+ };
161
+ };
162
+
163
+ const convertResponse =
164
+ (doc: OpenApiV3.IDocument) =>
165
+ (
166
+ input:
167
+ | OpenApiV3.IOperation.IResponse
168
+ | OpenApiV3.IJsonSchema.IReference<`#/components/responses/${string}`>,
169
+ ): OpenApi.IOperation.IResponse | undefined => {
170
+ if (OpenApiV3TypeChecker.isReference(input)) {
171
+ const found: OpenApiV3.IOperation.IResponse | undefined =
172
+ doc.components?.responses?.[input.$ref.split("/").pop() ?? ""];
173
+ if (found === undefined) return undefined;
174
+ input = found;
175
+ }
176
+ return {
177
+ ...input,
178
+ content: input.content
179
+ ? convertContent(doc.components ?? {})(input.content)
180
+ : undefined,
181
+ headers: input.headers
182
+ ? Object.fromEntries(
183
+ Object.entries(input.headers)
184
+ .filter(([_, v]) => v !== undefined)
185
+ .map(
186
+ ([key, value]) =>
187
+ [
188
+ key,
189
+ (() => {
190
+ if (OpenApiV3TypeChecker.isReference(value) === false)
191
+ return convertParameter(doc.components ?? {})({
192
+ ...value,
193
+ in: "header",
194
+ });
195
+ const found:
196
+ | Omit<OpenApiV3.IOperation.IParameter, "in">
197
+ | undefined = value.$ref.startsWith(
198
+ "#/components/headers/",
199
+ )
200
+ ? doc.components?.headers?.[
201
+ value.$ref.split("/").pop() ?? ""
202
+ ]
203
+ : undefined;
204
+ return found !== undefined
205
+ ? convertParameter(doc.components ?? {})({
206
+ ...found,
207
+ in: "header",
208
+ })
209
+ : undefined!;
210
+ })(),
211
+ ] as const,
212
+ )
213
+ .filter(([_, v]) => v !== undefined),
214
+ )
215
+ : undefined,
216
+ };
217
+ };
218
+
219
+ const convertContent =
220
+ (components: OpenApiV3.IComponents) =>
221
+ (
222
+ record: Record<string, OpenApiV3.IOperation.IMediaType>,
223
+ ): Record<string, OpenApi.IOperation.IMediaType> =>
224
+ Object.fromEntries(
225
+ Object.entries(record)
226
+ .filter(([_, v]) => v !== undefined)
227
+ .map(
228
+ ([key, value]) =>
229
+ [
230
+ key,
231
+ {
232
+ ...value,
233
+ schema: value.schema
234
+ ? convertSchema(components)(value.schema)
235
+ : undefined,
236
+ examples: value.examples
237
+ ? Object.fromEntries(
238
+ Object.entries(value.examples)
239
+ .map(([key, value]) => [
240
+ key,
241
+ OpenApiV3TypeChecker.isReference(value)
242
+ ? components.examples?.[
243
+ value.$ref.split("/").pop() ?? ""
244
+ ]
245
+ : value,
246
+ ])
247
+ .filter(([_, v]) => v !== undefined),
248
+ )
249
+ : undefined,
250
+ },
251
+ ] as const,
252
+ ),
253
+ );
254
+
255
+ /* -----------------------------------------------------------
256
+ DEFINITIONS
257
+ ----------------------------------------------------------- */
258
+ export const convertComponents = (
259
+ input: OpenApiV3.IComponents,
260
+ ): OpenApi.IComponents => ({
261
+ schemas: Object.fromEntries(
262
+ Object.entries(input.schemas ?? {})
263
+ .filter(([_, v]) => v !== undefined)
264
+ .map(([key, value]) => [key, convertSchema(input)(value)]),
265
+ ),
266
+ securitySchemes: input.securitySchemes,
267
+ });
268
+
269
+ export const convertSchema =
270
+ (components: OpenApiV3.IComponents) =>
271
+ (input: OpenApiV3.IJsonSchema): OpenApi.IJsonSchema => {
272
+ const nullable: { value: boolean; default?: null } = {
273
+ value: false,
274
+ default: undefined,
275
+ };
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 visit = (schema: OpenApiV3.IJsonSchema): void => {
294
+ // NULLABLE PROPERTY
295
+ if ((schema as OpenApiV3.IJsonSchema.IBoolean).nullable === true) {
296
+ nullable.value ||= true;
297
+ if ((schema as OpenApiV3.IJsonSchema.IBoolean).default === null)
298
+ nullable.default = null;
299
+ }
300
+ if (
301
+ Array.isArray((schema as OpenApiV3.IJsonSchema.INumber).enum) &&
302
+ (schema as OpenApiV3.IJsonSchema.INumber).enum?.length &&
303
+ (schema as OpenApiV3.IJsonSchema.INumber).enum?.some(
304
+ (e) => e === null,
305
+ )
306
+ )
307
+ nullable.value ||= true;
308
+ // UNION TYPE CASE
309
+ if (OpenApiV3TypeChecker.isAnyOf(schema)) schema.anyOf.forEach(visit);
310
+ else if (OpenApiV3TypeChecker.isOneOf(schema))
311
+ schema.oneOf.forEach(visit);
312
+ else if (OpenApiV3TypeChecker.isAllOf(schema))
313
+ if (schema.allOf.length === 1) visit(schema.allOf[0]!);
314
+ else union.push(convertAllOfSchema(components)(schema));
315
+ // ATOMIC TYPE CASE (CONSIDER ENUM VALUES)
316
+ else if (
317
+ OpenApiV3TypeChecker.isBoolean(schema) ||
318
+ OpenApiV3TypeChecker.isInteger(schema) ||
319
+ OpenApiV3TypeChecker.isNumber(schema) ||
320
+ OpenApiV3TypeChecker.isString(schema)
321
+ )
322
+ if (
323
+ schema.enum?.length &&
324
+ schema.enum.filter((e) => e !== null).length
325
+ )
326
+ union.push(
327
+ ...schema.enum
328
+ .filter((v) => v !== null)
329
+ .map((value) => ({ const: value })),
330
+ );
331
+ else if (
332
+ OpenApiV3TypeChecker.isInteger(schema) ||
333
+ OpenApiV3TypeChecker.isNumber(schema)
334
+ )
335
+ union.push(
336
+ OpenApiExclusiveEmender.emend({
337
+ ...schema,
338
+ default: (schema.default ?? undefined) satisfies
339
+ | boolean
340
+ | number
341
+ | string
342
+ | undefined as any,
343
+ exclusiveMinimum:
344
+ typeof schema.exclusiveMinimum === "boolean"
345
+ ? schema.exclusiveMinimum === true
346
+ ? schema.minimum
347
+ : undefined
348
+ : schema.exclusiveMinimum,
349
+ exclusiveMaximum:
350
+ typeof schema.exclusiveMaximum === "boolean"
351
+ ? schema.exclusiveMaximum === true
352
+ ? schema.maximum
353
+ : undefined
354
+ : schema.exclusiveMaximum,
355
+ minimum:
356
+ schema.exclusiveMinimum === true ? undefined : schema.minimum,
357
+ maximum:
358
+ schema.exclusiveMaximum === true ? undefined : schema.maximum,
359
+ ...{ enum: undefined },
360
+ }),
361
+ );
362
+ else
363
+ union.push({
364
+ ...schema,
365
+ default: (schema.default ?? undefined) satisfies
366
+ | boolean
367
+ | number
368
+ | string
369
+ | undefined as any,
370
+ ...{ enum: undefined },
371
+ });
372
+ // INSTANCE TYPE CASE
373
+ else if (OpenApiV3TypeChecker.isArray(schema))
374
+ union.push({
375
+ ...schema,
376
+ items: convertSchema(components)(schema.items),
377
+ });
378
+ else if (OpenApiV3TypeChecker.isObject(schema))
379
+ union.push({
380
+ ...schema,
381
+ ...{
382
+ properties: schema.properties
383
+ ? Object.fromEntries(
384
+ Object.entries(schema.properties)
385
+ .filter(([_, v]) => v !== undefined)
386
+ .map(([key, value]) => [
387
+ key,
388
+ convertSchema(components)(value),
389
+ ]),
390
+ )
391
+ : {},
392
+ additionalProperties: schema.additionalProperties
393
+ ? typeof schema.additionalProperties === "object" &&
394
+ schema.additionalProperties !== null
395
+ ? convertSchema(components)(schema.additionalProperties)
396
+ : schema.additionalProperties
397
+ : undefined,
398
+ required: schema.required ?? [],
399
+ },
400
+ });
401
+ else if (OpenApiV3TypeChecker.isReference(schema)) union.push(schema);
402
+ else union.push(schema);
403
+ };
404
+
405
+ visit(input);
406
+ if (
407
+ nullable.value === true &&
408
+ !union.some((e) => (e as OpenApi.IJsonSchema.INull).type === "null")
409
+ )
410
+ union.push({
411
+ type: "null",
412
+ default: nullable.default,
413
+ });
414
+ if (
415
+ union.length === 2 &&
416
+ union.filter((x) => OpenApiTypeChecker.isNull(x)).length === 1
417
+ ) {
418
+ const type: OpenApi.IJsonSchema = union.filter(
419
+ (x) => OpenApiTypeChecker.isNull(x) === false,
420
+ )[0]!;
421
+ for (const key of [
422
+ "title",
423
+ "description",
424
+ "deprecated",
425
+ "readOnly",
426
+ "writeOnly",
427
+ "example",
428
+ "examples",
429
+ ] as const)
430
+ if (type[key] !== undefined) delete type[key];
431
+ }
432
+ return {
433
+ ...(union.length === 0
434
+ ? { type: undefined }
435
+ : union.length === 1
436
+ ? { ...union[0] }
437
+ : { oneOf: union.map((u) => ({ ...u, nullable: undefined })) }),
438
+ ...attribute,
439
+ ...{ nullable: undefined },
440
+ };
441
+ };
442
+
443
+ const convertAllOfSchema =
444
+ (components: OpenApiV3.IComponents) =>
445
+ (input: OpenApiV3.IJsonSchema.IAllOf): OpenApi.IJsonSchema => {
446
+ const objects: Array<OpenApiV3.IJsonSchema.IObject | null> =
447
+ input.allOf.map((schema) => retrieveObject(components)(schema));
448
+ if (objects.some((obj) => obj === null))
449
+ return {
450
+ type: undefined,
451
+ ...{
452
+ allOf: undefined,
453
+ },
454
+ };
455
+ return {
456
+ ...input,
457
+ type: "object",
458
+ properties: Object.fromEntries(
459
+ objects
460
+ .map((o) => Object.entries(o?.properties ?? {}))
461
+ .flat()
462
+ .map(
463
+ ([key, value]) =>
464
+ [key, convertSchema(components)(value)] as const,
465
+ ),
466
+ ),
467
+ ...{
468
+ allOf: undefined,
469
+ required: [...new Set(objects.map((o) => o?.required ?? []).flat())],
470
+ },
471
+ };
472
+ };
473
+
474
+ const retrieveObject =
475
+ (components: OpenApiV3.IComponents) =>
476
+ (
477
+ input: OpenApiV3.IJsonSchema,
478
+ visited: Set<OpenApiV3.IJsonSchema> = new Set(),
479
+ ): OpenApiV3.IJsonSchema.IObject | null => {
480
+ if (OpenApiV3TypeChecker.isObject(input))
481
+ return input.properties !== undefined && !input.additionalProperties
482
+ ? input
483
+ : null;
484
+ else if (visited.has(input)) return null;
485
+ else visited.add(input);
486
+
487
+ if (OpenApiV3TypeChecker.isReference(input))
488
+ return retrieveObject(components)(
489
+ components.schemas?.[input.$ref.split("/").pop() ?? ""] ?? {},
490
+ visited,
491
+ );
492
+ return null;
493
+ };
494
+ }