@typia/utils 12.0.0 → 12.1.0-dev.20260325

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,381 +1,381 @@
1
- import { OpenApi, OpenApiV3 } from "@typia/interface";
2
-
3
- import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
-
5
- export namespace OpenApiV3Downgrader {
6
- export interface IComponentsCollection {
7
- original: OpenApi.IComponents;
8
- downgraded: OpenApiV3.IComponents;
9
- }
10
-
11
- export const downgrade = (input: OpenApi.IDocument): OpenApiV3.IDocument => {
12
- const collection: IComponentsCollection = downgradeComponents(
13
- input.components,
14
- );
15
- return {
16
- openapi: "3.0.0",
17
- servers: input.servers,
18
- info: input.info,
19
- components: collection.downgraded,
20
- paths: input.paths
21
- ? Object.fromEntries(
22
- Object.entries(input.paths)
23
- .filter(([_, v]) => v !== undefined)
24
- .map(
25
- ([key, value]) =>
26
- [key, downgradePathItem(collection)(value)] as const,
27
- ),
28
- )
29
- : undefined,
30
- security: input.security,
31
- tags: input.tags,
32
- };
33
- };
34
-
35
- /* -----------------------------------------------------------
36
- OPERATORS
37
- ----------------------------------------------------------- */
38
- const downgradePathItem =
39
- (collection: IComponentsCollection) =>
40
- (pathItem: OpenApi.IPath): OpenApiV3.IPath => {
41
- // Collect non-standard operations for x-additionalOperations
42
- const xAdditionalOperations: Record<string, OpenApiV3.IOperation> = {};
43
-
44
- // query method goes to x-additionalOperations
45
- if (pathItem.query) {
46
- xAdditionalOperations["query"] = downgradeOperation(collection)(pathItem.query);
47
- }
48
-
49
- // additionalOperations also go to x-additionalOperations
50
- if (pathItem.additionalOperations) {
51
- for (const [key, value] of Object.entries(pathItem.additionalOperations)) {
52
- if (value !== undefined) {
53
- xAdditionalOperations[key] = downgradeOperation(collection)(value);
54
- }
55
- }
56
- }
57
-
58
- return {
59
- ...(pathItem as any),
60
- ...(pathItem.get
61
- ? { get: downgradeOperation(collection)(pathItem.get) }
62
- : undefined),
63
- ...(pathItem.put
64
- ? { put: downgradeOperation(collection)(pathItem.put) }
65
- : undefined),
66
- ...(pathItem.post
67
- ? { post: downgradeOperation(collection)(pathItem.post) }
68
- : undefined),
69
- ...(pathItem.delete
70
- ? { delete: downgradeOperation(collection)(pathItem.delete) }
71
- : undefined),
72
- ...(pathItem.options
73
- ? { options: downgradeOperation(collection)(pathItem.options) }
74
- : undefined),
75
- ...(pathItem.head
76
- ? { head: downgradeOperation(collection)(pathItem.head) }
77
- : undefined),
78
- ...(pathItem.patch
79
- ? { patch: downgradeOperation(collection)(pathItem.patch) }
80
- : undefined),
81
- ...(pathItem.trace
82
- ? { trace: downgradeOperation(collection)(pathItem.trace) }
83
- : undefined),
84
- ...(Object.keys(xAdditionalOperations).length > 0
85
- ? { "x-additionalOperations": xAdditionalOperations }
86
- : undefined),
87
- // Remove v3.2-only properties from spread
88
- query: undefined,
89
- additionalOperations: undefined,
90
- };
91
- };
92
-
93
- const downgradeOperation =
94
- (collection: IComponentsCollection) =>
95
- (input: OpenApi.IOperation): OpenApiV3.IOperation => ({
96
- ...input,
97
- parameters: input.parameters
98
- ? input.parameters.map(downgradeParameter(collection))
99
- : undefined,
100
- requestBody: input.requestBody
101
- ? downgradeRequestBody(collection)(input.requestBody)
102
- : undefined,
103
- responses: input.responses
104
- ? Object.fromEntries(
105
- Object.entries(input.responses)
106
- .filter(([_, v]) => v !== undefined)
107
- .map(([key, value]) => [
108
- key,
109
- downgradeResponse(collection)(value),
110
- ]),
111
- )
112
- : undefined,
113
- });
114
-
115
- const downgradeParameter =
116
- (collection: IComponentsCollection) =>
117
- (
118
- input: OpenApi.IOperation.IParameter,
119
- ): OpenApiV3.IOperation.IParameter => ({
120
- ...input,
121
- in: input.in === "querystring" ? "query" : input.in,
122
- schema: downgradeSchema(collection)(input.schema),
123
- });
124
-
125
- const downgradeRequestBody =
126
- (collection: IComponentsCollection) =>
127
- (
128
- input: OpenApi.IOperation.IRequestBody,
129
- ): OpenApiV3.IOperation.IRequestBody => ({
130
- ...input,
131
- content: input.content
132
- ? downgradeContent(collection)(input.content)
133
- : undefined,
134
- });
135
-
136
- const downgradeResponse =
137
- (collection: IComponentsCollection) =>
138
- (input: OpenApi.IOperation.IResponse): OpenApiV3.IOperation.IResponse => ({
139
- ...input,
140
- content: input.content
141
- ? downgradeContent(collection)(input.content)
142
- : undefined,
143
- headers: input.headers
144
- ? Object.fromEntries(
145
- Object.entries(input.headers)
146
- .filter(([_, v]) => v !== undefined)
147
- .map(([key, value]) => [
148
- key,
149
- {
150
- ...value,
151
- schema: downgradeSchema(collection)(value.schema),
152
- },
153
- ]),
154
- )
155
- : undefined,
156
- });
157
-
158
- const downgradeContent =
159
- (collection: IComponentsCollection) =>
160
- (
161
- record: OpenApi.IOperation.IContent,
162
- ): Record<string, OpenApiV3.IOperation.IMediaType> =>
163
- Object.fromEntries(
164
- Object.entries(record)
165
- .filter(([_, v]) => v !== undefined)
166
- .map(
167
- ([key, value]) =>
168
- [
169
- key,
170
- {
171
- ...value,
172
- schema: value?.schema
173
- ? downgradeSchema(collection)(value.schema)
174
- : undefined,
175
- },
176
- ] as const,
177
- ),
178
- );
179
-
180
- /* -----------------------------------------------------------
181
- DEFINITIONS
182
- ----------------------------------------------------------- */
183
- export const downgradeComponents = (
184
- input: OpenApi.IComponents,
185
- ): IComponentsCollection => {
186
- const collection: IComponentsCollection = {
187
- original: input,
188
- downgraded: {
189
- securitySchemes: input.securitySchemes,
190
- },
191
- };
192
- if (input.schemas) {
193
- collection.downgraded.schemas = {};
194
- for (const [key, value] of Object.entries(input.schemas))
195
- if (value !== undefined)
196
- collection.downgraded.schemas[key] =
197
- downgradeSchema(collection)(value);
198
- }
199
- return collection;
200
- };
201
-
202
- export const downgradeSchema =
203
- (collection: IComponentsCollection) =>
204
- (input: OpenApi.IJsonSchema): OpenApiV3.IJsonSchema => {
205
- const nullable: boolean = isNullable(new Set())(collection.original)(
206
- input,
207
- );
208
- const union: OpenApiV3.IJsonSchema[] = [];
209
- const attribute: OpenApiV3.IJsonSchema.__IAttribute = {
210
- title: input.title,
211
- description: input.description,
212
- deprecated: input.deprecated,
213
- readOnly: input.readOnly,
214
- writeOnly: input.writeOnly,
215
- example: input.example,
216
- examples: input.examples,
217
- ...Object.fromEntries(
218
- Object.entries(input).filter(
219
- ([key, value]) => key.startsWith("x-") && value !== undefined,
220
- ),
221
- ),
222
- };
223
- const visit = (schema: OpenApi.IJsonSchema): void => {
224
- if (OpenApiTypeChecker.isBoolean(schema))
225
- union.push({ type: "boolean" });
226
- else if (
227
- OpenApiTypeChecker.isBoolean(schema) ||
228
- OpenApiTypeChecker.isInteger(schema) ||
229
- OpenApiTypeChecker.isNumber(schema) ||
230
- OpenApiTypeChecker.isString(schema) ||
231
- OpenApiTypeChecker.isReference(schema)
232
- )
233
- union.push({ ...schema });
234
- else if (OpenApiTypeChecker.isArray(schema))
235
- union.push({
236
- ...schema,
237
- items: downgradeSchema(collection)(schema.items),
238
- });
239
- else if (OpenApiTypeChecker.isTuple(schema))
240
- union.push({
241
- ...schema,
242
- items: ((): OpenApiV3.IJsonSchema => {
243
- if (schema.additionalItems === true) return {};
244
- const elements = [
245
- ...schema.prefixItems,
246
- ...(typeof schema.additionalItems === "object"
247
- ? [downgradeSchema(collection)(schema.additionalItems)]
248
- : []),
249
- ];
250
- if (elements.length === 0) return {};
251
- return {
252
- oneOf: elements.map(downgradeSchema(collection) as any),
253
- };
254
- })(),
255
- minItems: schema.prefixItems.length,
256
- maxItems:
257
- !!schema.additionalItems === true
258
- ? undefined
259
- : schema.prefixItems.length,
260
- ...{
261
- prefixItems: undefined,
262
- additionalItems: undefined,
263
- },
264
- });
265
- else if (OpenApiTypeChecker.isObject(schema))
266
- union.push({
267
- ...schema,
268
- properties: schema.properties
269
- ? Object.fromEntries(
270
- Object.entries(schema.properties)
271
- .filter(([_, v]) => v !== undefined)
272
- .map(([key, value]) => [
273
- key,
274
- downgradeSchema(collection)(value),
275
- ]),
276
- )
277
- : undefined,
278
- additionalProperties:
279
- typeof schema.additionalProperties === "object"
280
- ? downgradeSchema(collection)(schema.additionalProperties)
281
- : schema.additionalProperties,
282
- required: schema.required,
283
- });
284
- else if (OpenApiTypeChecker.isOneOf(schema))
285
- schema.oneOf.forEach(visit);
286
- };
287
- const visitConstant = (schema: OpenApi.IJsonSchema): void => {
288
- const insert = (value: any): void => {
289
- const matched: OpenApiV3.IJsonSchema.INumber | undefined = union.find(
290
- (u) => (u as OpenApiV3.IJsonSchema.INumber).type === typeof value,
291
- ) as OpenApiV3.IJsonSchema.INumber | undefined;
292
- if (matched !== undefined) {
293
- matched.enum ??= [];
294
- matched.enum.push(value);
295
- } else union.push({ type: typeof value as "number", enum: [value] });
296
- };
297
- if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const);
298
- else if (OpenApiTypeChecker.isOneOf(schema))
299
- for (const u of schema.oneOf)
300
- if (OpenApiTypeChecker.isConstant(u)) insert(u.const);
301
- };
302
-
303
- visit(input);
304
- visitConstant(input);
305
- if (nullable === true)
306
- for (const u of union)
307
- if (OpenApiTypeChecker.isReference(u as any))
308
- downgradeNullableReference(new Set())(collection)(u as any);
309
- else (u as OpenApiV3.IJsonSchema.IArray).nullable = true;
310
- if (nullable === true && union.length === 0)
311
- return { type: "null", ...attribute };
312
- return {
313
- ...(union.length === 0
314
- ? { type: undefined }
315
- : union.length === 1
316
- ? { ...union[0] }
317
- : { oneOf: union }),
318
- ...attribute,
319
- };
320
- };
321
-
322
- const downgradeNullableReference =
323
- (visited: Set<string>) =>
324
- (collection: IComponentsCollection) =>
325
- (schema: OpenApiV3.IJsonSchema.IReference): void => {
326
- const key: string = schema.$ref.split("/").pop()!;
327
- if (key.endsWith(".Nullable")) return;
328
-
329
- const found: OpenApi.IJsonSchema | undefined =
330
- collection.original.schemas?.[key];
331
- if (found === undefined) return;
332
- else if (isNullable(visited)(collection.original)(found) === true) return;
333
- else if (
334
- collection.downgraded.schemas?.[`${key}.Nullable`] === undefined
335
- ) {
336
- collection.downgraded.schemas ??= {};
337
- collection.downgraded.schemas[`${key}.Nullable`] = {};
338
- collection.downgraded.schemas[`${key}.Nullable`] = downgradeSchema(
339
- collection,
340
- )(
341
- OpenApiTypeChecker.isOneOf(found)
342
- ? {
343
- ...found,
344
- oneOf: [...found.oneOf, { type: "null" }],
345
- }
346
- : {
347
- oneOf: [found, { type: "null" }],
348
- title: found.title,
349
- description: found.description,
350
- example: found.example,
351
- examples: found.examples,
352
- ...Object.fromEntries(
353
- Object.entries(found).filter(
354
- ([key, value]) =>
355
- key.startsWith("x-") && value !== undefined,
356
- ),
357
- ),
358
- },
359
- );
360
- }
361
- schema.$ref += ".Nullable";
362
- };
363
-
364
- const isNullable =
365
- (visited: Set<string>) =>
366
- (components: OpenApi.IComponents) =>
367
- (schema: OpenApi.IJsonSchema): boolean => {
368
- if (OpenApiTypeChecker.isNull(schema)) return true;
369
- else if (OpenApiTypeChecker.isReference(schema)) {
370
- if (visited.has(schema.$ref)) return false;
371
- visited.add(schema.$ref);
372
- const key: string = schema.$ref.split("/").pop()!;
373
- const next: OpenApi.IJsonSchema | undefined = components.schemas?.[key];
374
- return next ? isNullable(visited)(components)(next) : false;
375
- }
376
- return (
377
- OpenApiTypeChecker.isOneOf(schema) &&
378
- schema.oneOf.some(isNullable(visited)(components))
379
- );
380
- };
381
- }
1
+ import { OpenApi, OpenApiV3 } from "@typia/interface";
2
+
3
+ import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
+
5
+ export namespace OpenApiV3Downgrader {
6
+ export interface IComponentsCollection {
7
+ original: OpenApi.IComponents;
8
+ downgraded: OpenApiV3.IComponents;
9
+ }
10
+
11
+ export const downgrade = (input: OpenApi.IDocument): OpenApiV3.IDocument => {
12
+ const collection: IComponentsCollection = downgradeComponents(
13
+ input.components,
14
+ );
15
+ return {
16
+ openapi: "3.0.0",
17
+ servers: input.servers,
18
+ info: input.info,
19
+ components: collection.downgraded,
20
+ paths: input.paths
21
+ ? Object.fromEntries(
22
+ Object.entries(input.paths)
23
+ .filter(([_, v]) => v !== undefined)
24
+ .map(
25
+ ([key, value]) =>
26
+ [key, downgradePathItem(collection)(value)] as const,
27
+ ),
28
+ )
29
+ : undefined,
30
+ security: input.security,
31
+ tags: input.tags,
32
+ };
33
+ };
34
+
35
+ /* -----------------------------------------------------------
36
+ OPERATORS
37
+ ----------------------------------------------------------- */
38
+ const downgradePathItem =
39
+ (collection: IComponentsCollection) =>
40
+ (pathItem: OpenApi.IPath): OpenApiV3.IPath => {
41
+ // Collect non-standard operations for x-additionalOperations
42
+ const xAdditionalOperations: Record<string, OpenApiV3.IOperation> = {};
43
+
44
+ // query method goes to x-additionalOperations
45
+ if (pathItem.query) {
46
+ xAdditionalOperations["query"] = downgradeOperation(collection)(pathItem.query);
47
+ }
48
+
49
+ // additionalOperations also go to x-additionalOperations
50
+ if (pathItem.additionalOperations) {
51
+ for (const [key, value] of Object.entries(pathItem.additionalOperations)) {
52
+ if (value !== undefined) {
53
+ xAdditionalOperations[key] = downgradeOperation(collection)(value);
54
+ }
55
+ }
56
+ }
57
+
58
+ return {
59
+ ...(pathItem as any),
60
+ ...(pathItem.get
61
+ ? { get: downgradeOperation(collection)(pathItem.get) }
62
+ : undefined),
63
+ ...(pathItem.put
64
+ ? { put: downgradeOperation(collection)(pathItem.put) }
65
+ : undefined),
66
+ ...(pathItem.post
67
+ ? { post: downgradeOperation(collection)(pathItem.post) }
68
+ : undefined),
69
+ ...(pathItem.delete
70
+ ? { delete: downgradeOperation(collection)(pathItem.delete) }
71
+ : undefined),
72
+ ...(pathItem.options
73
+ ? { options: downgradeOperation(collection)(pathItem.options) }
74
+ : undefined),
75
+ ...(pathItem.head
76
+ ? { head: downgradeOperation(collection)(pathItem.head) }
77
+ : undefined),
78
+ ...(pathItem.patch
79
+ ? { patch: downgradeOperation(collection)(pathItem.patch) }
80
+ : undefined),
81
+ ...(pathItem.trace
82
+ ? { trace: downgradeOperation(collection)(pathItem.trace) }
83
+ : undefined),
84
+ ...(Object.keys(xAdditionalOperations).length > 0
85
+ ? { "x-additionalOperations": xAdditionalOperations }
86
+ : undefined),
87
+ // Remove v3.2-only properties from spread
88
+ query: undefined,
89
+ additionalOperations: undefined,
90
+ };
91
+ };
92
+
93
+ const downgradeOperation =
94
+ (collection: IComponentsCollection) =>
95
+ (input: OpenApi.IOperation): OpenApiV3.IOperation => ({
96
+ ...input,
97
+ parameters: input.parameters
98
+ ? input.parameters.map(downgradeParameter(collection))
99
+ : undefined,
100
+ requestBody: input.requestBody
101
+ ? downgradeRequestBody(collection)(input.requestBody)
102
+ : undefined,
103
+ responses: input.responses
104
+ ? Object.fromEntries(
105
+ Object.entries(input.responses)
106
+ .filter(([_, v]) => v !== undefined)
107
+ .map(([key, value]) => [
108
+ key,
109
+ downgradeResponse(collection)(value),
110
+ ]),
111
+ )
112
+ : undefined,
113
+ });
114
+
115
+ const downgradeParameter =
116
+ (collection: IComponentsCollection) =>
117
+ (
118
+ input: OpenApi.IOperation.IParameter,
119
+ ): OpenApiV3.IOperation.IParameter => ({
120
+ ...input,
121
+ in: input.in === "querystring" ? "query" : input.in,
122
+ schema: downgradeSchema(collection)(input.schema),
123
+ });
124
+
125
+ const downgradeRequestBody =
126
+ (collection: IComponentsCollection) =>
127
+ (
128
+ input: OpenApi.IOperation.IRequestBody,
129
+ ): OpenApiV3.IOperation.IRequestBody => ({
130
+ ...input,
131
+ content: input.content
132
+ ? downgradeContent(collection)(input.content)
133
+ : undefined,
134
+ });
135
+
136
+ const downgradeResponse =
137
+ (collection: IComponentsCollection) =>
138
+ (input: OpenApi.IOperation.IResponse): OpenApiV3.IOperation.IResponse => ({
139
+ ...input,
140
+ content: input.content
141
+ ? downgradeContent(collection)(input.content)
142
+ : undefined,
143
+ headers: input.headers
144
+ ? Object.fromEntries(
145
+ Object.entries(input.headers)
146
+ .filter(([_, v]) => v !== undefined)
147
+ .map(([key, value]) => [
148
+ key,
149
+ {
150
+ ...value,
151
+ schema: downgradeSchema(collection)(value.schema),
152
+ },
153
+ ]),
154
+ )
155
+ : undefined,
156
+ });
157
+
158
+ const downgradeContent =
159
+ (collection: IComponentsCollection) =>
160
+ (
161
+ record: OpenApi.IOperation.IContent,
162
+ ): Record<string, OpenApiV3.IOperation.IMediaType> =>
163
+ Object.fromEntries(
164
+ Object.entries(record)
165
+ .filter(([_, v]) => v !== undefined)
166
+ .map(
167
+ ([key, value]) =>
168
+ [
169
+ key,
170
+ {
171
+ ...value,
172
+ schema: value?.schema
173
+ ? downgradeSchema(collection)(value.schema)
174
+ : undefined,
175
+ },
176
+ ] as const,
177
+ ),
178
+ );
179
+
180
+ /* -----------------------------------------------------------
181
+ DEFINITIONS
182
+ ----------------------------------------------------------- */
183
+ export const downgradeComponents = (
184
+ input: OpenApi.IComponents,
185
+ ): IComponentsCollection => {
186
+ const collection: IComponentsCollection = {
187
+ original: input,
188
+ downgraded: {
189
+ securitySchemes: input.securitySchemes,
190
+ },
191
+ };
192
+ if (input.schemas) {
193
+ collection.downgraded.schemas = {};
194
+ for (const [key, value] of Object.entries(input.schemas))
195
+ if (value !== undefined)
196
+ collection.downgraded.schemas[key] =
197
+ downgradeSchema(collection)(value);
198
+ }
199
+ return collection;
200
+ };
201
+
202
+ export const downgradeSchema =
203
+ (collection: IComponentsCollection) =>
204
+ (input: OpenApi.IJsonSchema): OpenApiV3.IJsonSchema => {
205
+ const nullable: boolean = isNullable(new Set())(collection.original)(
206
+ input,
207
+ );
208
+ const union: OpenApiV3.IJsonSchema[] = [];
209
+ const attribute: OpenApiV3.IJsonSchema.__IAttribute = {
210
+ title: input.title,
211
+ description: input.description,
212
+ deprecated: input.deprecated,
213
+ readOnly: input.readOnly,
214
+ writeOnly: input.writeOnly,
215
+ example: input.example,
216
+ examples: input.examples,
217
+ ...Object.fromEntries(
218
+ Object.entries(input).filter(
219
+ ([key, value]) => key.startsWith("x-") && value !== undefined,
220
+ ),
221
+ ),
222
+ };
223
+ const visit = (schema: OpenApi.IJsonSchema): void => {
224
+ if (OpenApiTypeChecker.isBoolean(schema))
225
+ union.push({ type: "boolean" });
226
+ else if (
227
+ OpenApiTypeChecker.isBoolean(schema) ||
228
+ OpenApiTypeChecker.isInteger(schema) ||
229
+ OpenApiTypeChecker.isNumber(schema) ||
230
+ OpenApiTypeChecker.isString(schema) ||
231
+ OpenApiTypeChecker.isReference(schema)
232
+ )
233
+ union.push({ ...schema });
234
+ else if (OpenApiTypeChecker.isArray(schema))
235
+ union.push({
236
+ ...schema,
237
+ items: downgradeSchema(collection)(schema.items),
238
+ });
239
+ else if (OpenApiTypeChecker.isTuple(schema))
240
+ union.push({
241
+ ...schema,
242
+ items: ((): OpenApiV3.IJsonSchema => {
243
+ if (schema.additionalItems === true) return {};
244
+ const elements = [
245
+ ...schema.prefixItems,
246
+ ...(typeof schema.additionalItems === "object"
247
+ ? [downgradeSchema(collection)(schema.additionalItems)]
248
+ : []),
249
+ ];
250
+ if (elements.length === 0) return {};
251
+ return {
252
+ oneOf: elements.map(downgradeSchema(collection) as any),
253
+ };
254
+ })(),
255
+ minItems: schema.prefixItems.length,
256
+ maxItems:
257
+ !!schema.additionalItems === true
258
+ ? undefined
259
+ : schema.prefixItems.length,
260
+ ...{
261
+ prefixItems: undefined,
262
+ additionalItems: undefined,
263
+ },
264
+ });
265
+ else if (OpenApiTypeChecker.isObject(schema))
266
+ union.push({
267
+ ...schema,
268
+ properties: schema.properties
269
+ ? Object.fromEntries(
270
+ Object.entries(schema.properties)
271
+ .filter(([_, v]) => v !== undefined)
272
+ .map(([key, value]) => [
273
+ key,
274
+ downgradeSchema(collection)(value),
275
+ ]),
276
+ )
277
+ : undefined,
278
+ additionalProperties:
279
+ typeof schema.additionalProperties === "object"
280
+ ? downgradeSchema(collection)(schema.additionalProperties)
281
+ : schema.additionalProperties,
282
+ required: schema.required,
283
+ });
284
+ else if (OpenApiTypeChecker.isOneOf(schema))
285
+ schema.oneOf.forEach(visit);
286
+ };
287
+ const visitConstant = (schema: OpenApi.IJsonSchema): void => {
288
+ const insert = (value: any): void => {
289
+ const matched: OpenApiV3.IJsonSchema.INumber | undefined = union.find(
290
+ (u) => (u as OpenApiV3.IJsonSchema.INumber).type === typeof value,
291
+ ) as OpenApiV3.IJsonSchema.INumber | undefined;
292
+ if (matched !== undefined) {
293
+ matched.enum ??= [];
294
+ matched.enum.push(value);
295
+ } else union.push({ type: typeof value as "number", enum: [value] });
296
+ };
297
+ if (OpenApiTypeChecker.isConstant(schema)) insert(schema.const);
298
+ else if (OpenApiTypeChecker.isOneOf(schema))
299
+ for (const u of schema.oneOf)
300
+ if (OpenApiTypeChecker.isConstant(u)) insert(u.const);
301
+ };
302
+
303
+ visit(input);
304
+ visitConstant(input);
305
+ if (nullable === true)
306
+ for (const u of union)
307
+ if (OpenApiTypeChecker.isReference(u as any))
308
+ downgradeNullableReference(new Set())(collection)(u as any);
309
+ else (u as OpenApiV3.IJsonSchema.IArray).nullable = true;
310
+ if (nullable === true && union.length === 0)
311
+ return { type: "null", ...attribute };
312
+ return {
313
+ ...(union.length === 0
314
+ ? { type: undefined }
315
+ : union.length === 1
316
+ ? { ...union[0] }
317
+ : { oneOf: union }),
318
+ ...attribute,
319
+ };
320
+ };
321
+
322
+ const downgradeNullableReference =
323
+ (visited: Set<string>) =>
324
+ (collection: IComponentsCollection) =>
325
+ (schema: OpenApiV3.IJsonSchema.IReference): void => {
326
+ const key: string = schema.$ref.split("/").pop()!;
327
+ if (key.endsWith(".Nullable")) return;
328
+
329
+ const found: OpenApi.IJsonSchema | undefined =
330
+ collection.original.schemas?.[key];
331
+ if (found === undefined) return;
332
+ else if (isNullable(visited)(collection.original)(found) === true) return;
333
+ else if (
334
+ collection.downgraded.schemas?.[`${key}.Nullable`] === undefined
335
+ ) {
336
+ collection.downgraded.schemas ??= {};
337
+ collection.downgraded.schemas[`${key}.Nullable`] = {};
338
+ collection.downgraded.schemas[`${key}.Nullable`] = downgradeSchema(
339
+ collection,
340
+ )(
341
+ OpenApiTypeChecker.isOneOf(found)
342
+ ? {
343
+ ...found,
344
+ oneOf: [...found.oneOf, { type: "null" }],
345
+ }
346
+ : {
347
+ oneOf: [found, { type: "null" }],
348
+ title: found.title,
349
+ description: found.description,
350
+ example: found.example,
351
+ examples: found.examples,
352
+ ...Object.fromEntries(
353
+ Object.entries(found).filter(
354
+ ([key, value]) =>
355
+ key.startsWith("x-") && value !== undefined,
356
+ ),
357
+ ),
358
+ },
359
+ );
360
+ }
361
+ schema.$ref += ".Nullable";
362
+ };
363
+
364
+ const isNullable =
365
+ (visited: Set<string>) =>
366
+ (components: OpenApi.IComponents) =>
367
+ (schema: OpenApi.IJsonSchema): boolean => {
368
+ if (OpenApiTypeChecker.isNull(schema)) return true;
369
+ else if (OpenApiTypeChecker.isReference(schema)) {
370
+ if (visited.has(schema.$ref)) return false;
371
+ visited.add(schema.$ref);
372
+ const key: string = schema.$ref.split("/").pop()!;
373
+ const next: OpenApi.IJsonSchema | undefined = components.schemas?.[key];
374
+ return next ? isNullable(visited)(components)(next) : false;
375
+ }
376
+ return (
377
+ OpenApiTypeChecker.isOneOf(schema) &&
378
+ schema.oneOf.some(isNullable(visited)(components))
379
+ );
380
+ };
381
+ }