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

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.
@@ -8,6 +8,7 @@ import {
8
8
 
9
9
  import { OpenApiV3Downgrader } from "./internal/OpenApiV3Downgrader";
10
10
  import { OpenApiV3Upgrader } from "./internal/OpenApiV3Upgrader";
11
+ import { OpenApiV3_1Downgrader } from "./internal/OpenApiV3_1Downgrader";
11
12
  import { OpenApiV3_1Upgrader } from "./internal/OpenApiV3_1Upgrader";
12
13
  import { OpenApiV3_2Upgrader } from "./internal/OpenApiV3_2Upgrader";
13
14
  import { SwaggerV2Downgrader } from "./internal/SwaggerV2Downgrader";
@@ -32,6 +33,7 @@ import { SwaggerV2Upgrader } from "./internal/SwaggerV2Upgrader";
32
33
  *
33
34
  * - Emended v3.2 → Swagger v2.0
34
35
  * - Emended v3.2 → OpenAPI v3.0
36
+ * - Emended v3.2 → OpenAPI v3.1
35
37
  *
36
38
  * The emended format normalizes ambiguous expressions: dereferences `$ref`,
37
39
  * merges `allOf`, converts `nullable` to union types, etc.
@@ -89,13 +91,21 @@ export namespace OpenApiConverter {
89
91
  version: "3.0",
90
92
  ): OpenApiV3.IDocument;
91
93
 
94
+ export function downgradeDocument(
95
+ document: OpenApi.IDocument,
96
+ version: "3.1",
97
+ ): OpenApiV3_1.IDocument;
98
+
92
99
  /** @internal */
93
100
  export function downgradeDocument(
94
101
  document: OpenApi.IDocument,
95
- version: "2.0" | "3.0",
96
- ): SwaggerV2.IDocument | OpenApiV3.IDocument {
102
+ version: "2.0" | "3.0" | "3.1",
103
+ ): SwaggerV2.IDocument | OpenApiV3.IDocument | OpenApiV3_1.IDocument {
97
104
  if (version === "2.0") return SwaggerV2Downgrader.downgrade(document);
98
105
  else if (version === "3.0") return OpenApiV3Downgrader.downgrade(document);
106
+ else if (version === "3.1")
107
+ return OpenApiV3_1Downgrader.downgrade(document);
108
+
99
109
  version satisfies never;
100
110
  throw new Error("Invalid OpenAPI version");
101
111
  }
@@ -144,13 +154,30 @@ export namespace OpenApiConverter {
144
154
  version: "3.0",
145
155
  ): OpenApiV3.IComponents;
146
156
 
157
+ /**
158
+ * Downgrade components to OpenAPI v3.1 format.
159
+ *
160
+ * @param input Source emended components
161
+ * @param version Target version "3.1"
162
+ * @returns OpenAPI v3.1 components
163
+ */
164
+ export function downgradeComponents(
165
+ input: OpenApi.IComponents,
166
+ version: "3.1",
167
+ ): OpenApiV3_1.IComponents;
168
+
147
169
  /** @internal */
148
170
  export function downgradeComponents(
149
171
  input: OpenApi.IComponents,
150
- version: "2.0" | "3.0",
151
- ): Record<string, SwaggerV2.IJsonSchema> | OpenApiV3.IComponents {
172
+ version: "2.0" | "3.0" | "3.1",
173
+ ):
174
+ | Record<string, SwaggerV2.IJsonSchema>
175
+ | OpenApiV3.IComponents
176
+ | OpenApiV3_1.IComponents {
152
177
  if (version === "2.0")
153
178
  return SwaggerV2Downgrader.downgradeComponents(input).downgraded;
179
+ if (version === "3.1")
180
+ return OpenApiV3_1Downgrader.downgradeComponents(input).downgraded;
154
181
  return OpenApiV3Downgrader.downgradeComponents(input).downgraded;
155
182
  }
156
183
 
@@ -264,23 +291,49 @@ export namespace OpenApiConverter {
264
291
  downgraded: OpenApiV3.IComponents;
265
292
  }): OpenApiV3.IJsonSchema;
266
293
 
294
+ /**
295
+ * Downgrade schema to OpenAPI v3.1 format.
296
+ *
297
+ * @param props.components Source emended components
298
+ * @param props.schema Schema to downgrade
299
+ * @param props.version Target version "3.1"
300
+ * @param props.downgraded Target components (mutated)
301
+ * @returns OpenAPI v3.1 schema
302
+ */
303
+ export function downgradeSchema(props: {
304
+ components: OpenApi.IComponents;
305
+ schema: OpenApi.IJsonSchema;
306
+ version: "3.1";
307
+ downgraded: OpenApiV3_1.IComponents;
308
+ }): OpenApiV3_1.IJsonSchema;
309
+
267
310
  /** @internal */
268
- export function downgradeSchema<Version extends "2.0" | "3.0">(props: {
311
+ export function downgradeSchema<Version extends "2.0" | "3.0" | "3.1">(props: {
269
312
  components: OpenApi.IComponents;
270
313
  schema: OpenApi.IJsonSchema;
271
314
  version: Version;
272
315
  downgraded: Version extends "2.0"
273
316
  ? Record<string, SwaggerV2.IJsonSchema>
274
- : OpenApiV3.IComponents;
275
- }): OpenApiV3.IJsonSchema | SwaggerV2.IJsonSchema {
317
+ : Version extends "3.1"
318
+ ? OpenApiV3_1.IComponents
319
+ : OpenApiV3.IComponents;
320
+ }):
321
+ | OpenApiV3.IJsonSchema
322
+ | OpenApiV3_1.IJsonSchema
323
+ | SwaggerV2.IJsonSchema {
276
324
  if (props.version === "2.0")
277
325
  return SwaggerV2Downgrader.downgradeSchema({
278
326
  original: props.components,
279
327
  downgraded: props.downgraded as Record<string, SwaggerV2.IJsonSchema>,
280
328
  })(props.schema);
329
+ if (props.version === "3.1")
330
+ return OpenApiV3_1Downgrader.downgradeSchema({
331
+ original: props.components,
332
+ downgraded: props.downgraded as OpenApiV3_1.IComponents,
333
+ })(props.schema);
281
334
  return OpenApiV3Downgrader.downgradeSchema({
282
335
  original: props.components,
283
- downgraded: props.downgraded,
336
+ downgraded: props.downgraded as OpenApiV3.IComponents,
284
337
  })(props.schema);
285
338
  }
286
339
  }
@@ -0,0 +1,318 @@
1
+ import { OpenApi, OpenApiV3_1 } from "@typia/interface";
2
+
3
+ import { OpenApiTypeChecker } from "../../validators/OpenApiTypeChecker";
4
+
5
+ export namespace OpenApiV3_1Downgrader {
6
+ export interface IComponentsCollection {
7
+ original: OpenApi.IComponents;
8
+ downgraded: OpenApiV3_1.IComponents;
9
+ }
10
+
11
+ export const downgrade = (input: OpenApi.IDocument): OpenApiV3_1.IDocument => {
12
+ const collection: IComponentsCollection = downgradeComponents(
13
+ input.components,
14
+ );
15
+ return {
16
+ openapi: "3.1.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
+ webhooks: input.webhooks
31
+ ? Object.fromEntries(
32
+ Object.entries(input.webhooks)
33
+ .filter(([_, v]) => v !== undefined)
34
+ .map(
35
+ ([key, value]) =>
36
+ [key, downgradePathItem(collection)(value)] as const,
37
+ ),
38
+ )
39
+ : undefined,
40
+ security: input.security,
41
+ tags: input.tags,
42
+ };
43
+ };
44
+
45
+ /* -----------------------------------------------------------
46
+ OPERATORS
47
+ ----------------------------------------------------------- */
48
+ const downgradePathItem =
49
+ (collection: IComponentsCollection) =>
50
+ (pathItem: OpenApi.IPath): OpenApiV3_1.IPath => {
51
+ // Collect non-standard operations for x-additionalOperations
52
+ const xAdditionalOperations: Record<string, OpenApiV3_1.IOperation> = {};
53
+
54
+ // query method goes to x-additionalOperations
55
+ if (pathItem.query) {
56
+ xAdditionalOperations["query"] = downgradeOperation(collection)(pathItem.query);
57
+ }
58
+
59
+ // additionalOperations also go to x-additionalOperations
60
+ if (pathItem.additionalOperations) {
61
+ for (const [key, value] of Object.entries(pathItem.additionalOperations)) {
62
+ if (value !== undefined) {
63
+ xAdditionalOperations[key] = downgradeOperation(collection)(value);
64
+ }
65
+ }
66
+ }
67
+
68
+ return {
69
+ ...(pathItem as any),
70
+ ...(pathItem.get
71
+ ? { get: downgradeOperation(collection)(pathItem.get) }
72
+ : undefined),
73
+ ...(pathItem.put
74
+ ? { put: downgradeOperation(collection)(pathItem.put) }
75
+ : undefined),
76
+ ...(pathItem.post
77
+ ? { post: downgradeOperation(collection)(pathItem.post) }
78
+ : undefined),
79
+ ...(pathItem.delete
80
+ ? { delete: downgradeOperation(collection)(pathItem.delete) }
81
+ : undefined),
82
+ ...(pathItem.options
83
+ ? { options: downgradeOperation(collection)(pathItem.options) }
84
+ : undefined),
85
+ ...(pathItem.head
86
+ ? { head: downgradeOperation(collection)(pathItem.head) }
87
+ : undefined),
88
+ ...(pathItem.patch
89
+ ? { patch: downgradeOperation(collection)(pathItem.patch) }
90
+ : undefined),
91
+ ...(pathItem.trace
92
+ ? { trace: downgradeOperation(collection)(pathItem.trace) }
93
+ : undefined),
94
+ ...(Object.keys(xAdditionalOperations).length > 0
95
+ ? { "x-additionalOperations": xAdditionalOperations }
96
+ : undefined),
97
+ // Remove v3.2-only properties from spread
98
+ query: undefined,
99
+ additionalOperations: undefined,
100
+ };
101
+ };
102
+
103
+ const downgradeOperation =
104
+ (collection: IComponentsCollection) =>
105
+ (input: OpenApi.IOperation): OpenApiV3_1.IOperation => ({
106
+ ...input,
107
+ parameters: input.parameters
108
+ ? input.parameters.map(downgradeParameter(collection))
109
+ : undefined,
110
+ requestBody: input.requestBody
111
+ ? downgradeRequestBody(collection)(input.requestBody)
112
+ : undefined,
113
+ responses: input.responses
114
+ ? Object.fromEntries(
115
+ Object.entries(input.responses)
116
+ .filter(([_, v]) => v !== undefined)
117
+ .map(([key, value]) => [
118
+ key,
119
+ downgradeResponse(collection)(value),
120
+ ]),
121
+ )
122
+ : undefined,
123
+ });
124
+
125
+ const downgradeParameter =
126
+ (collection: IComponentsCollection) =>
127
+ (
128
+ input: OpenApi.IOperation.IParameter,
129
+ ): OpenApiV3_1.IOperation.IParameter => ({
130
+ ...input,
131
+ in: input.in === "querystring" ? "query" : input.in,
132
+ schema: downgradeSchema(collection)(input.schema),
133
+ });
134
+
135
+ const downgradeRequestBody =
136
+ (collection: IComponentsCollection) =>
137
+ (
138
+ input: OpenApi.IOperation.IRequestBody,
139
+ ): OpenApiV3_1.IOperation.IRequestBody => ({
140
+ ...input,
141
+ content: input.content
142
+ ? downgradeContent(collection)(input.content)
143
+ : undefined,
144
+ });
145
+
146
+ const downgradeResponse =
147
+ (collection: IComponentsCollection) =>
148
+ (input: OpenApi.IOperation.IResponse): OpenApiV3_1.IOperation.IResponse => ({
149
+ ...input,
150
+ content: input.content
151
+ ? downgradeContent(collection)(input.content)
152
+ : undefined,
153
+ headers: input.headers
154
+ ? Object.fromEntries(
155
+ Object.entries(input.headers)
156
+ .filter(([_, v]) => v !== undefined)
157
+ .map(([key, value]) => [
158
+ key,
159
+ {
160
+ ...value,
161
+ schema: downgradeSchema(collection)(value.schema),
162
+ },
163
+ ]),
164
+ )
165
+ : undefined,
166
+ });
167
+
168
+ const downgradeContent =
169
+ (collection: IComponentsCollection) =>
170
+ (
171
+ record: OpenApi.IOperation.IContent,
172
+ ): Record<string, OpenApiV3_1.IOperation.IMediaType> =>
173
+ Object.fromEntries(
174
+ Object.entries(record)
175
+ .filter(([_, v]) => v !== undefined)
176
+ .map(
177
+ ([key, value]) =>
178
+ [
179
+ key,
180
+ {
181
+ ...value,
182
+ schema: value?.schema
183
+ ? downgradeSchema(collection)(value.schema)
184
+ : undefined,
185
+ // itemSchema is v3.2-only, remove it
186
+ itemSchema: undefined,
187
+ },
188
+ ] as const,
189
+ ),
190
+ );
191
+
192
+ /* -----------------------------------------------------------
193
+ DEFINITIONS
194
+ ----------------------------------------------------------- */
195
+ export const downgradeComponents = (
196
+ input: OpenApi.IComponents,
197
+ ): IComponentsCollection => {
198
+ const collection: IComponentsCollection = {
199
+ original: input,
200
+ downgraded: {
201
+ securitySchemes: input.securitySchemes
202
+ ? downgradeSecuritySchemes(input.securitySchemes)
203
+ : undefined,
204
+ },
205
+ };
206
+ if (input.schemas) {
207
+ collection.downgraded.schemas = {};
208
+ for (const [key, value] of Object.entries(input.schemas))
209
+ if (value !== undefined)
210
+ collection.downgraded.schemas[key] =
211
+ downgradeSchema(collection)(value);
212
+ }
213
+ return collection;
214
+ };
215
+
216
+ export const downgradeSchema =
217
+ (collection: IComponentsCollection) =>
218
+ (input: OpenApi.IJsonSchema): OpenApiV3_1.IJsonSchema => {
219
+ const union: OpenApiV3_1.IJsonSchema[] = [];
220
+ const attribute: OpenApiV3_1.IJsonSchema.__IAttribute = {
221
+ title: input.title,
222
+ description: input.description,
223
+ deprecated: input.deprecated,
224
+ readOnly: input.readOnly,
225
+ writeOnly: input.writeOnly,
226
+ example: input.example,
227
+ examples: input.examples,
228
+ ...Object.fromEntries(
229
+ Object.entries(input).filter(
230
+ ([key, value]) => key.startsWith("x-") && value !== undefined,
231
+ ),
232
+ ),
233
+ };
234
+ const visit = (schema: OpenApi.IJsonSchema): void => {
235
+ if (OpenApiTypeChecker.isNull(schema))
236
+ union.push({ type: "null" });
237
+ else if (OpenApiTypeChecker.isConstant(schema))
238
+ union.push({ const: schema.const });
239
+ else if (
240
+ OpenApiTypeChecker.isBoolean(schema) ||
241
+ OpenApiTypeChecker.isInteger(schema) ||
242
+ OpenApiTypeChecker.isNumber(schema) ||
243
+ OpenApiTypeChecker.isString(schema) ||
244
+ OpenApiTypeChecker.isReference(schema)
245
+ )
246
+ union.push({ ...schema });
247
+ else if (OpenApiTypeChecker.isArray(schema))
248
+ union.push({
249
+ ...schema,
250
+ items: downgradeSchema(collection)(schema.items),
251
+ });
252
+ else if (OpenApiTypeChecker.isTuple(schema))
253
+ union.push({
254
+ ...schema,
255
+ prefixItems: schema.prefixItems.map(
256
+ downgradeSchema(collection),
257
+ ),
258
+ additionalItems:
259
+ typeof schema.additionalItems === "object"
260
+ ? downgradeSchema(collection)(schema.additionalItems)
261
+ : schema.additionalItems,
262
+ });
263
+ else if (OpenApiTypeChecker.isObject(schema))
264
+ union.push({
265
+ ...schema,
266
+ properties: schema.properties
267
+ ? Object.fromEntries(
268
+ Object.entries(schema.properties)
269
+ .filter(([_, v]) => v !== undefined)
270
+ .map(([key, value]) => [
271
+ key,
272
+ downgradeSchema(collection)(value),
273
+ ]),
274
+ )
275
+ : undefined,
276
+ additionalProperties:
277
+ typeof schema.additionalProperties === "object"
278
+ ? downgradeSchema(collection)(schema.additionalProperties)
279
+ : schema.additionalProperties,
280
+ required: schema.required,
281
+ });
282
+ else if (OpenApiTypeChecker.isOneOf(schema))
283
+ schema.oneOf.forEach(visit);
284
+ };
285
+ visit(input);
286
+ return {
287
+ ...(union.length === 0
288
+ ? { type: undefined }
289
+ : union.length === 1
290
+ ? { ...union[0] }
291
+ : { oneOf: union }),
292
+ ...attribute,
293
+ };
294
+ };
295
+
296
+ const downgradeSecuritySchemes = (
297
+ input: Record<string, OpenApi.ISecurityScheme>,
298
+ ): Record<string, OpenApiV3_1.ISecurityScheme> =>
299
+ Object.fromEntries(
300
+ Object.entries(input)
301
+ .filter(([_, v]) => v !== undefined)
302
+ .map(([key, value]) => [key, downgradeSecurityScheme(value)]),
303
+ );
304
+
305
+ const downgradeSecurityScheme = (
306
+ input: OpenApi.ISecurityScheme,
307
+ ): OpenApiV3_1.ISecurityScheme => {
308
+ if (input.type === "oauth2") {
309
+ const { deviceAuthorization: _, ...flows } = input.flows;
310
+ return {
311
+ type: "oauth2",
312
+ flows,
313
+ description: input.description,
314
+ };
315
+ }
316
+ return { ...input } as OpenApiV3_1.ISecurityScheme;
317
+ };
318
+ }