@typia/utils 12.0.0-dev.20260316 → 12.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,494 +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.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
- }
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
+ }