@kubb/adapter-oas 5.0.0-alpha.2 → 5.0.0-alpha.20
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.
- package/dist/index.cjs +989 -1070
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +83 -128
- package/dist/index.js +988 -1069
- package/dist/index.js.map +1 -1
- package/package.json +5 -5
- package/src/adapter.ts +79 -63
- package/src/constants.ts +63 -63
- package/src/discriminator.ts +99 -0
- package/src/factory.ts +151 -0
- package/src/guards.ts +68 -0
- package/src/index.ts +1 -0
- package/src/parser.ts +352 -667
- package/src/refs.ts +57 -0
- package/src/resolvers.ts +490 -0
- package/src/types.ts +178 -56
- package/src/oas/Oas.ts +0 -616
- package/src/oas/resolveServerUrl.ts +0 -47
- package/src/oas/types.ts +0 -52
- package/src/oas/utils.ts +0 -402
package/dist/index.js
CHANGED
|
@@ -1,37 +1,202 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
+
import { childName, collectImports, createDiscriminantNode, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, enumPropName, extractRefName, findDiscriminator, mediaTypes, mergeAdjacentObjects, narrowSchema, schemaTypes, setDiscriminatorEnum, setEnumName, simplifyUnion, transform } from "@kubb/ast";
|
|
3
|
+
import { createAdapter } from "@kubb/core";
|
|
2
4
|
import path from "node:path";
|
|
3
|
-
import { collect, createOperation, createParameter, createProperty, createResponse, createRoot, createSchema, narrowSchema, schemaTypes, transform } from "@kubb/ast";
|
|
4
|
-
import { defineAdapter } from "@kubb/core";
|
|
5
5
|
import { bundle, loadConfig } from "@redocly/openapi-core";
|
|
6
6
|
import yaml from "@stoplight/yaml";
|
|
7
|
-
import { isRef } from "oas/types";
|
|
8
7
|
import OASNormalize from "oas-normalize";
|
|
9
8
|
import { isPlainObject, mergeDeep } from "remeda";
|
|
10
9
|
import swagger2openapi from "swagger2openapi";
|
|
11
|
-
import jsonpointer from "jsonpointer";
|
|
12
10
|
import BaseOas from "oas";
|
|
11
|
+
import jsonpointer from "jsonpointer";
|
|
12
|
+
import { isRef } from "oas/types";
|
|
13
13
|
import { matchesMimeType } from "oas/utils";
|
|
14
|
-
//#region src/
|
|
14
|
+
//#region src/constants.ts
|
|
15
15
|
/**
|
|
16
|
-
*
|
|
16
|
+
* Default parser options applied when no explicit options are provided.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* 3. Variable is left unreplaced when neither is available.
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
22
21
|
*
|
|
23
|
-
*
|
|
22
|
+
* const parser = createOasParser(oas)
|
|
23
|
+
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
24
|
+
* ```
|
|
24
25
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
26
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
27
|
+
dateType: "string",
|
|
28
|
+
integerType: "number",
|
|
29
|
+
unknownType: "any",
|
|
30
|
+
emptySchemaType: "any",
|
|
31
|
+
enumSuffix: "enum"
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
35
|
+
*/
|
|
36
|
+
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
37
|
+
/**
|
|
38
|
+
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
39
|
+
*/
|
|
40
|
+
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
41
|
+
/**
|
|
42
|
+
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
43
|
+
*/
|
|
44
|
+
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
45
|
+
/**
|
|
46
|
+
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
47
|
+
*
|
|
48
|
+
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
49
|
+
* intersection member rather than being merged into the parent.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```ts
|
|
53
|
+
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
54
|
+
*
|
|
55
|
+
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
56
|
+
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
57
|
+
* ```
|
|
58
|
+
*/
|
|
59
|
+
const structuralKeys = new Set([
|
|
60
|
+
"properties",
|
|
61
|
+
"items",
|
|
62
|
+
"additionalProperties",
|
|
63
|
+
"oneOf",
|
|
64
|
+
"anyOf",
|
|
65
|
+
"allOf",
|
|
66
|
+
"not"
|
|
67
|
+
]);
|
|
68
|
+
/**
|
|
69
|
+
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
70
|
+
*
|
|
71
|
+
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
72
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
73
|
+
* in the parser. `ipv4`, `ipv6`, and `hostname` map to `'url'` as the closest supported scalar.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* ```ts
|
|
77
|
+
* import { formatMap } from '@kubb/adapter-oas'
|
|
78
|
+
*
|
|
79
|
+
* formatMap['uuid'] // 'uuid'
|
|
80
|
+
* formatMap['binary'] // 'blob'
|
|
81
|
+
* formatMap['float'] // 'number'
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
const formatMap = {
|
|
85
|
+
uuid: "uuid",
|
|
86
|
+
email: "email",
|
|
87
|
+
"idn-email": "email",
|
|
88
|
+
uri: "url",
|
|
89
|
+
"uri-reference": "url",
|
|
90
|
+
url: "url",
|
|
91
|
+
ipv4: "url",
|
|
92
|
+
ipv6: "url",
|
|
93
|
+
hostname: "url",
|
|
94
|
+
"idn-hostname": "url",
|
|
95
|
+
binary: "blob",
|
|
96
|
+
byte: "blob",
|
|
97
|
+
int32: "integer",
|
|
98
|
+
float: "number",
|
|
99
|
+
double: "number"
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
103
|
+
*
|
|
104
|
+
* @example
|
|
105
|
+
* ```ts
|
|
106
|
+
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
107
|
+
*
|
|
108
|
+
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
109
|
+
* ```
|
|
110
|
+
*/
|
|
111
|
+
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
112
|
+
/**
|
|
113
|
+
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
114
|
+
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
115
|
+
*/
|
|
116
|
+
const typeOptionMap = new Map([
|
|
117
|
+
["any", schemaTypes.any],
|
|
118
|
+
["unknown", schemaTypes.unknown],
|
|
119
|
+
["void", schemaTypes.void]
|
|
120
|
+
]);
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/discriminator.ts
|
|
123
|
+
/**
|
|
124
|
+
* Injects discriminator enum values into child schemas so they know which value identifies them.
|
|
125
|
+
*
|
|
126
|
+
* Finds every union schema in `root.schemas` that has a `discriminatorPropertyName`, collects the
|
|
127
|
+
* enum value each union member is mapped to, then adds (or replaces) that property on the matching
|
|
128
|
+
* child object schema.
|
|
129
|
+
*
|
|
130
|
+
* Returns a new `RootNode` — the original is never mutated.
|
|
131
|
+
*
|
|
132
|
+
* @example
|
|
133
|
+
* ```ts
|
|
134
|
+
* const { root } = parseOas(document, options)
|
|
135
|
+
* const next = applyDiscriminatorInheritance(root)
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
function applyDiscriminatorInheritance(root) {
|
|
139
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const schema of root.schemas) {
|
|
141
|
+
let unionNode = narrowSchema(schema, "union");
|
|
142
|
+
if (!unionNode) {
|
|
143
|
+
const intersectionMembers = narrowSchema(schema, "intersection")?.members;
|
|
144
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
145
|
+
const u = narrowSchema(m, "union");
|
|
146
|
+
if (u) {
|
|
147
|
+
unionNode = u;
|
|
148
|
+
break;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
153
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
154
|
+
for (const member of members) {
|
|
155
|
+
const intersectionNode = narrowSchema(member, "intersection");
|
|
156
|
+
if (!intersectionNode?.members) continue;
|
|
157
|
+
let refNode;
|
|
158
|
+
let objNode;
|
|
159
|
+
for (const m of intersectionNode.members) {
|
|
160
|
+
refNode ??= narrowSchema(m, "ref");
|
|
161
|
+
objNode ??= narrowSchema(m, "object");
|
|
162
|
+
}
|
|
163
|
+
if (!refNode?.name || !objNode) continue;
|
|
164
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
165
|
+
const enumNode = prop ? narrowSchema(prop.schema, "enum") : void 0;
|
|
166
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
167
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
168
|
+
if (!enumValues.length) continue;
|
|
169
|
+
const existing = childMap.get(refNode.name);
|
|
170
|
+
if (existing) existing.enumValues.push(...enumValues);
|
|
171
|
+
else childMap.set(refNode.name, {
|
|
172
|
+
propertyName: discriminatorPropertyName,
|
|
173
|
+
enumValues: [...enumValues]
|
|
174
|
+
});
|
|
175
|
+
}
|
|
33
176
|
}
|
|
34
|
-
return
|
|
177
|
+
if (childMap.size === 0) return root;
|
|
178
|
+
return transform(root, { schema(node, { parent }) {
|
|
179
|
+
if (parent?.kind !== "Root" || !node.name) return;
|
|
180
|
+
const entry = childMap.get(node.name);
|
|
181
|
+
if (!entry) return;
|
|
182
|
+
const objectNode = narrowSchema(node, "object");
|
|
183
|
+
if (!objectNode) return;
|
|
184
|
+
const { propertyName, enumValues } = entry;
|
|
185
|
+
const newProp = createProperty({
|
|
186
|
+
name: propertyName,
|
|
187
|
+
required: true,
|
|
188
|
+
schema: createSchema({
|
|
189
|
+
type: "enum",
|
|
190
|
+
enumValues
|
|
191
|
+
})
|
|
192
|
+
});
|
|
193
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
194
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
195
|
+
return {
|
|
196
|
+
...objectNode,
|
|
197
|
+
properties: newProperties
|
|
198
|
+
};
|
|
199
|
+
} });
|
|
35
200
|
}
|
|
36
201
|
//#endregion
|
|
37
202
|
//#region ../../internals/utils/src/casing.ts
|
|
@@ -53,9 +218,12 @@ function toCamelOrPascal(text, pascal) {
|
|
|
53
218
|
* Splits `text` on `.` and applies `transformPart` to each segment.
|
|
54
219
|
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
55
220
|
* Segments are joined with `/` to form a file path.
|
|
221
|
+
*
|
|
222
|
+
* Only splits on dots followed by a letter so that version numbers
|
|
223
|
+
* embedded in operationIds (e.g. `v2025.0`) are kept intact.
|
|
56
224
|
*/
|
|
57
225
|
function applyToFileParts(text, transformPart) {
|
|
58
|
-
const parts = text.split(
|
|
226
|
+
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
59
227
|
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).join("/");
|
|
60
228
|
}
|
|
61
229
|
/**
|
|
@@ -92,6 +260,13 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
92
260
|
//#region ../../internals/utils/src/reserved.ts
|
|
93
261
|
/**
|
|
94
262
|
* Returns `true` when `name` is a syntactically valid JavaScript variable name.
|
|
263
|
+
*
|
|
264
|
+
* @example
|
|
265
|
+
* ```ts
|
|
266
|
+
* isValidVarName('status') // true
|
|
267
|
+
* isValidVarName('class') // false (reserved word)
|
|
268
|
+
* isValidVarName('42foo') // false (starts with digit)
|
|
269
|
+
* ```
|
|
95
270
|
*/
|
|
96
271
|
function isValidVarName(name) {
|
|
97
272
|
try {
|
|
@@ -112,18 +287,33 @@ function isValidVarName(name) {
|
|
|
112
287
|
* p.template // '`/pet/${petId}`'
|
|
113
288
|
*/
|
|
114
289
|
var URLPath = class {
|
|
115
|
-
/**
|
|
290
|
+
/**
|
|
291
|
+
* The raw OpenAPI/Swagger path string, e.g. `/pet/{petId}`.
|
|
292
|
+
*/
|
|
116
293
|
path;
|
|
117
294
|
#options;
|
|
118
295
|
constructor(path, options = {}) {
|
|
119
296
|
this.path = path;
|
|
120
297
|
this.#options = options;
|
|
121
298
|
}
|
|
122
|
-
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
299
|
+
/** Converts the OpenAPI path to Express-style colon syntax, e.g. `/pet/{petId}` → `/pet/:petId`.
|
|
300
|
+
*
|
|
301
|
+
* @example
|
|
302
|
+
* ```ts
|
|
303
|
+
* new URLPath('/pet/{petId}').URL // '/pet/:petId'
|
|
304
|
+
* ```
|
|
305
|
+
*/
|
|
123
306
|
get URL() {
|
|
124
307
|
return this.toURLPath();
|
|
125
308
|
}
|
|
126
|
-
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
309
|
+
/** Returns `true` when `path` is a fully-qualified URL (e.g. starts with `https://`).
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* ```ts
|
|
313
|
+
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
314
|
+
* new URLPath('/pet/{petId}').isURL // false
|
|
315
|
+
* ```
|
|
316
|
+
*/
|
|
127
317
|
get isURL() {
|
|
128
318
|
try {
|
|
129
319
|
return !!new URL(this.path).href;
|
|
@@ -141,11 +331,25 @@ var URLPath = class {
|
|
|
141
331
|
get template() {
|
|
142
332
|
return this.toTemplateString();
|
|
143
333
|
}
|
|
144
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
334
|
+
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
335
|
+
*
|
|
336
|
+
* @example
|
|
337
|
+
* ```ts
|
|
338
|
+
* new URLPath('/pet/{petId}').object
|
|
339
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
340
|
+
* ```
|
|
341
|
+
*/
|
|
145
342
|
get object() {
|
|
146
343
|
return this.toObject();
|
|
147
344
|
}
|
|
148
|
-
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
345
|
+
/** Returns a map of path parameter names, or `undefined` when the path has no parameters.
|
|
346
|
+
*
|
|
347
|
+
* @example
|
|
348
|
+
* ```ts
|
|
349
|
+
* new URLPath('/pet/{petId}').params // { petId: 'petId' }
|
|
350
|
+
* new URLPath('/pet').params // undefined
|
|
351
|
+
* ```
|
|
352
|
+
*/
|
|
149
353
|
get params() {
|
|
150
354
|
return this.getParams();
|
|
151
355
|
}
|
|
@@ -153,7 +357,9 @@ var URLPath = class {
|
|
|
153
357
|
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
154
358
|
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
155
359
|
}
|
|
156
|
-
/**
|
|
360
|
+
/**
|
|
361
|
+
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
362
|
+
*/
|
|
157
363
|
#eachParam(fn) {
|
|
158
364
|
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
159
365
|
const raw = match[1];
|
|
@@ -190,6 +396,12 @@ var URLPath = class {
|
|
|
190
396
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
191
397
|
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
192
398
|
* Returns `undefined` when no path parameters are found.
|
|
399
|
+
*
|
|
400
|
+
* @example
|
|
401
|
+
* ```ts
|
|
402
|
+
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
403
|
+
* // { petId: 'petId', tagId: 'tagId' }
|
|
404
|
+
* ```
|
|
193
405
|
*/
|
|
194
406
|
getParams(replacer) {
|
|
195
407
|
const params = {};
|
|
@@ -199,439 +411,28 @@ var URLPath = class {
|
|
|
199
411
|
});
|
|
200
412
|
return Object.keys(params).length > 0 ? params : void 0;
|
|
201
413
|
}
|
|
202
|
-
/** Converts the OpenAPI path to Express-style colon syntax
|
|
414
|
+
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* ```ts
|
|
418
|
+
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
203
421
|
toURLPath() {
|
|
204
422
|
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
205
423
|
}
|
|
206
424
|
};
|
|
207
425
|
//#endregion
|
|
208
|
-
//#region src/
|
|
209
|
-
/**
|
|
210
|
-
* OpenAPI version string written into merged document stubs.
|
|
211
|
-
*/
|
|
212
|
-
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
213
|
-
/**
|
|
214
|
-
* Fallback `info.title` used when merging multiple API documents.
|
|
215
|
-
*/
|
|
216
|
-
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
217
|
-
/**
|
|
218
|
-
* Fallback `info.version` used when merging multiple API documents.
|
|
219
|
-
*/
|
|
220
|
-
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
426
|
+
//#region src/guards.ts
|
|
221
427
|
/**
|
|
222
|
-
*
|
|
223
|
-
* A schema fragment containing any of these keys must not be inlined into its
|
|
224
|
-
* parent during `allOf` flattening — it carries semantic meaning of its own.
|
|
225
|
-
*/
|
|
226
|
-
const structuralKeys = new Set([
|
|
227
|
-
"properties",
|
|
228
|
-
"items",
|
|
229
|
-
"additionalProperties",
|
|
230
|
-
"oneOf",
|
|
231
|
-
"anyOf",
|
|
232
|
-
"allOf",
|
|
233
|
-
"not"
|
|
234
|
-
]);
|
|
235
|
-
/**
|
|
236
|
-
* Maps OAS/JSON Schema `format` strings to their Kubb `SchemaType` equivalents.
|
|
237
|
-
*
|
|
238
|
-
* Only formats that need a type different from the raw OAS `type` are listed.
|
|
239
|
-
* `int64`, `date-time`, `date`, and `time` are handled separately because their
|
|
240
|
-
* mapping depends on runtime parser options.
|
|
428
|
+
* Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
|
|
241
429
|
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
"idn-email": "email",
|
|
249
|
-
uri: "url",
|
|
250
|
-
"uri-reference": "url",
|
|
251
|
-
url: "url",
|
|
252
|
-
ipv4: "url",
|
|
253
|
-
ipv6: "url",
|
|
254
|
-
hostname: "url",
|
|
255
|
-
"idn-hostname": "url",
|
|
256
|
-
binary: "blob",
|
|
257
|
-
byte: "blob",
|
|
258
|
-
int32: "integer",
|
|
259
|
-
float: "number",
|
|
260
|
-
double: "number"
|
|
261
|
-
};
|
|
262
|
-
/**
|
|
263
|
-
* Exhaustive list of media types that Kubb recognizes.
|
|
264
|
-
* Kept as a module-level constant to avoid re-allocating the array on every call.
|
|
265
|
-
*/
|
|
266
|
-
const knownMediaTypes = [
|
|
267
|
-
"application/json",
|
|
268
|
-
"application/xml",
|
|
269
|
-
"application/x-www-form-urlencoded",
|
|
270
|
-
"application/octet-stream",
|
|
271
|
-
"application/pdf",
|
|
272
|
-
"application/zip",
|
|
273
|
-
"application/graphql",
|
|
274
|
-
"multipart/form-data",
|
|
275
|
-
"text/plain",
|
|
276
|
-
"text/html",
|
|
277
|
-
"text/csv",
|
|
278
|
-
"text/xml",
|
|
279
|
-
"image/png",
|
|
280
|
-
"image/jpeg",
|
|
281
|
-
"image/gif",
|
|
282
|
-
"image/webp",
|
|
283
|
-
"image/svg+xml",
|
|
284
|
-
"audio/mpeg",
|
|
285
|
-
"video/mp4"
|
|
286
|
-
];
|
|
287
|
-
/**
|
|
288
|
-
* Vendor extension keys used to attach human-readable labels to enum values.
|
|
289
|
-
* Checked in priority order: the first key found wins.
|
|
290
|
-
*/
|
|
291
|
-
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
292
|
-
//#endregion
|
|
293
|
-
//#region src/oas/Oas.ts
|
|
294
|
-
/**
|
|
295
|
-
* Prefix used to create synthetic `$ref` values for anonymous (inline) discriminator schemas.
|
|
296
|
-
* The suffix is the schema index within the discriminator's `oneOf`/`anyOf` array.
|
|
297
|
-
* @example `#kubb-inline-0`
|
|
298
|
-
*/
|
|
299
|
-
const KUBB_INLINE_REF_PREFIX = "#kubb-inline-";
|
|
300
|
-
var Oas = class extends BaseOas {
|
|
301
|
-
#options = { discriminator: "strict" };
|
|
302
|
-
document;
|
|
303
|
-
constructor(document) {
|
|
304
|
-
super(document, void 0);
|
|
305
|
-
this.document = document;
|
|
306
|
-
}
|
|
307
|
-
setOptions(options) {
|
|
308
|
-
this.#options = {
|
|
309
|
-
...this.#options,
|
|
310
|
-
...options
|
|
311
|
-
};
|
|
312
|
-
if (this.#options.discriminator === "inherit") this.#applyDiscriminatorInheritance();
|
|
313
|
-
}
|
|
314
|
-
get options() {
|
|
315
|
-
return this.#options;
|
|
316
|
-
}
|
|
317
|
-
get($ref) {
|
|
318
|
-
const origRef = $ref;
|
|
319
|
-
$ref = $ref.trim();
|
|
320
|
-
if ($ref === "") return null;
|
|
321
|
-
if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
322
|
-
else return null;
|
|
323
|
-
const current = jsonpointer.get(this.api, $ref);
|
|
324
|
-
if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
|
|
325
|
-
return current;
|
|
326
|
-
}
|
|
327
|
-
getKey($ref) {
|
|
328
|
-
const key = $ref.split("/").pop();
|
|
329
|
-
return key === "" ? void 0 : key;
|
|
330
|
-
}
|
|
331
|
-
set($ref, value) {
|
|
332
|
-
$ref = $ref.trim();
|
|
333
|
-
if ($ref === "") return false;
|
|
334
|
-
if ($ref.startsWith("#")) {
|
|
335
|
-
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
336
|
-
jsonpointer.set(this.api, $ref, value);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
#setDiscriminator(schema) {
|
|
340
|
-
const { mapping = {}, propertyName } = schema.discriminator;
|
|
341
|
-
if (this.#options.discriminator === "inherit") Object.entries(mapping).forEach(([mappingKey, mappingValue]) => {
|
|
342
|
-
if (mappingValue) {
|
|
343
|
-
const childSchema = this.get(mappingValue);
|
|
344
|
-
if (!childSchema) return;
|
|
345
|
-
if (!childSchema.properties) childSchema.properties = {};
|
|
346
|
-
const property = childSchema.properties[propertyName];
|
|
347
|
-
if (childSchema.properties) {
|
|
348
|
-
childSchema.properties[propertyName] = {
|
|
349
|
-
...childSchema.properties ? childSchema.properties[propertyName] : {},
|
|
350
|
-
enum: [...property?.enum?.filter((value) => value !== mappingKey) ?? [], mappingKey]
|
|
351
|
-
};
|
|
352
|
-
childSchema.required = typeof childSchema.required === "boolean" ? childSchema.required : [...new Set([...childSchema.required ?? [], propertyName])];
|
|
353
|
-
this.set(mappingValue, childSchema);
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
}
|
|
358
|
-
getDiscriminator(schema) {
|
|
359
|
-
if (!isDiscriminator(schema) || !schema) return null;
|
|
360
|
-
const { mapping = {}, propertyName } = schema.discriminator;
|
|
361
|
-
/**
|
|
362
|
-
* Helper to extract discriminator value from a schema.
|
|
363
|
-
* Checks in order:
|
|
364
|
-
* 1. Extension property matching propertyName (e.g., x-linode-ref-name)
|
|
365
|
-
* 2. Property with const value
|
|
366
|
-
* 3. Property with single enum value
|
|
367
|
-
* 4. Title as fallback
|
|
368
|
-
*/
|
|
369
|
-
const getDiscriminatorValue = (schema) => {
|
|
370
|
-
if (!schema) return null;
|
|
371
|
-
if (propertyName.startsWith("x-")) {
|
|
372
|
-
const extensionValue = schema[propertyName];
|
|
373
|
-
if (extensionValue && typeof extensionValue === "string") return extensionValue;
|
|
374
|
-
}
|
|
375
|
-
const propertySchema = schema.properties?.[propertyName];
|
|
376
|
-
if (propertySchema && "const" in propertySchema && propertySchema.const !== void 0) return String(propertySchema.const);
|
|
377
|
-
if (propertySchema && propertySchema.enum?.length === 1) return String(propertySchema.enum[0]);
|
|
378
|
-
return schema.title || null;
|
|
379
|
-
};
|
|
380
|
-
/**
|
|
381
|
-
* Process oneOf/anyOf items to build mapping.
|
|
382
|
-
* Handles both $ref and inline schemas.
|
|
383
|
-
*/
|
|
384
|
-
const processSchemas = (schemas, existingMapping) => {
|
|
385
|
-
schemas.forEach((schemaItem, index) => {
|
|
386
|
-
if (isReference(schemaItem)) {
|
|
387
|
-
const key = this.getKey(schemaItem.$ref);
|
|
388
|
-
try {
|
|
389
|
-
const discriminatorValue = getDiscriminatorValue(this.get(schemaItem.$ref));
|
|
390
|
-
const canAdd = key && !Object.values(existingMapping).includes(schemaItem.$ref);
|
|
391
|
-
if (canAdd && discriminatorValue) existingMapping[discriminatorValue] = schemaItem.$ref;
|
|
392
|
-
else if (canAdd) existingMapping[key] = schemaItem.$ref;
|
|
393
|
-
} catch (_error) {
|
|
394
|
-
if (key && !Object.values(existingMapping).includes(schemaItem.$ref)) existingMapping[key] = schemaItem.$ref;
|
|
395
|
-
}
|
|
396
|
-
} else {
|
|
397
|
-
const discriminatorValue = getDiscriminatorValue(schemaItem);
|
|
398
|
-
if (discriminatorValue) existingMapping[discriminatorValue] = `${KUBB_INLINE_REF_PREFIX}${index}`;
|
|
399
|
-
}
|
|
400
|
-
});
|
|
401
|
-
};
|
|
402
|
-
if (schema.oneOf) processSchemas(schema.oneOf, mapping);
|
|
403
|
-
if (schema.anyOf) processSchemas(schema.anyOf, mapping);
|
|
404
|
-
return {
|
|
405
|
-
...schema.discriminator,
|
|
406
|
-
mapping
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
dereferenceWithRef(schema) {
|
|
410
|
-
if (isReference(schema)) return {
|
|
411
|
-
...schema,
|
|
412
|
-
...this.get(schema.$ref),
|
|
413
|
-
$ref: schema.$ref
|
|
414
|
-
};
|
|
415
|
-
return schema;
|
|
416
|
-
}
|
|
417
|
-
#applyDiscriminatorInheritance() {
|
|
418
|
-
const components = this.api.components;
|
|
419
|
-
if (!components?.schemas) return;
|
|
420
|
-
const visited = /* @__PURE__ */ new WeakSet();
|
|
421
|
-
const enqueue = (value) => {
|
|
422
|
-
if (!value) return;
|
|
423
|
-
if (Array.isArray(value)) {
|
|
424
|
-
for (const item of value) enqueue(item);
|
|
425
|
-
return;
|
|
426
|
-
}
|
|
427
|
-
if (typeof value === "object") visit(value);
|
|
428
|
-
};
|
|
429
|
-
const visit = (schema) => {
|
|
430
|
-
if (!schema || typeof schema !== "object") return;
|
|
431
|
-
if (isReference(schema)) {
|
|
432
|
-
visit(this.get(schema.$ref));
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
const schemaObject = schema;
|
|
436
|
-
if (visited.has(schemaObject)) return;
|
|
437
|
-
visited.add(schemaObject);
|
|
438
|
-
if (isDiscriminator(schemaObject)) this.#setDiscriminator(schemaObject);
|
|
439
|
-
if ("allOf" in schemaObject) enqueue(schemaObject.allOf);
|
|
440
|
-
if ("oneOf" in schemaObject) enqueue(schemaObject.oneOf);
|
|
441
|
-
if ("anyOf" in schemaObject) enqueue(schemaObject.anyOf);
|
|
442
|
-
if ("not" in schemaObject) enqueue(schemaObject.not);
|
|
443
|
-
if ("items" in schemaObject) enqueue(schemaObject.items);
|
|
444
|
-
if ("prefixItems" in schemaObject) enqueue(schemaObject.prefixItems);
|
|
445
|
-
if (schemaObject.properties) enqueue(Object.values(schemaObject.properties));
|
|
446
|
-
if (schemaObject.additionalProperties && typeof schemaObject.additionalProperties === "object") enqueue(schemaObject.additionalProperties);
|
|
447
|
-
};
|
|
448
|
-
for (const schema of Object.values(components.schemas)) visit(schema);
|
|
449
|
-
}
|
|
450
|
-
/**
|
|
451
|
-
* Oas does not have a getResponseBody(contentType)
|
|
452
|
-
*/
|
|
453
|
-
#getResponseBodyFactory(responseBody) {
|
|
454
|
-
function hasResponseBody(res = responseBody) {
|
|
455
|
-
return !!res;
|
|
456
|
-
}
|
|
457
|
-
return (contentType) => {
|
|
458
|
-
if (!hasResponseBody(responseBody)) return false;
|
|
459
|
-
if (isReference(responseBody)) return false;
|
|
460
|
-
if (!responseBody.content) return false;
|
|
461
|
-
if (contentType) {
|
|
462
|
-
if (!(contentType in responseBody.content)) return false;
|
|
463
|
-
return responseBody.content[contentType];
|
|
464
|
-
}
|
|
465
|
-
let availableContentType;
|
|
466
|
-
const contentTypes = Object.keys(responseBody.content);
|
|
467
|
-
contentTypes.forEach((mt) => {
|
|
468
|
-
if (!availableContentType && matchesMimeType.json(mt)) availableContentType = mt;
|
|
469
|
-
});
|
|
470
|
-
if (!availableContentType) contentTypes.forEach((mt) => {
|
|
471
|
-
if (!availableContentType) availableContentType = mt;
|
|
472
|
-
});
|
|
473
|
-
if (availableContentType) return [
|
|
474
|
-
availableContentType,
|
|
475
|
-
responseBody.content[availableContentType],
|
|
476
|
-
...responseBody.description ? [responseBody.description] : []
|
|
477
|
-
];
|
|
478
|
-
return false;
|
|
479
|
-
};
|
|
480
|
-
}
|
|
481
|
-
getResponseSchema(operation, statusCode) {
|
|
482
|
-
if (operation.schema.responses) Object.keys(operation.schema.responses).forEach((key) => {
|
|
483
|
-
const schema = operation.schema.responses[key];
|
|
484
|
-
const $ref = isReference(schema) ? schema.$ref : void 0;
|
|
485
|
-
if (schema && $ref) operation.schema.responses[key] = this.get($ref);
|
|
486
|
-
});
|
|
487
|
-
const getResponseBody = this.#getResponseBodyFactory(operation.getResponseByStatusCode(statusCode));
|
|
488
|
-
const { contentType } = this.#options;
|
|
489
|
-
const responseBody = getResponseBody(contentType);
|
|
490
|
-
if (responseBody === false) return {};
|
|
491
|
-
const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
|
|
492
|
-
if (!schema) return {};
|
|
493
|
-
return this.dereferenceWithRef(schema);
|
|
494
|
-
}
|
|
495
|
-
getRequestSchema(operation) {
|
|
496
|
-
const { contentType } = this.#options;
|
|
497
|
-
if (operation.schema.requestBody) operation.schema.requestBody = this.dereferenceWithRef(operation.schema.requestBody);
|
|
498
|
-
const requestBody = operation.getRequestBody(contentType);
|
|
499
|
-
if (requestBody === false) return;
|
|
500
|
-
const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
|
|
501
|
-
if (!schema) return;
|
|
502
|
-
return this.dereferenceWithRef(schema);
|
|
503
|
-
}
|
|
504
|
-
getParametersSchema(operation, inKey) {
|
|
505
|
-
const { contentType = operation.getContentType() } = this.#options;
|
|
506
|
-
const resolveParams = (params) => params.map((p) => this.dereferenceWithRef(p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
|
|
507
|
-
const operationParams = resolveParams(operation.schema?.parameters || []);
|
|
508
|
-
const pathItem = this.api?.paths?.[operation.path];
|
|
509
|
-
const pathLevelParams = resolveParams(pathItem && !isReference(pathItem) && pathItem.parameters ? pathItem.parameters : []);
|
|
510
|
-
const paramMap = /* @__PURE__ */ new Map();
|
|
511
|
-
for (const p of pathLevelParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
|
|
512
|
-
for (const p of operationParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
|
|
513
|
-
const params = Array.from(paramMap.values()).filter((v) => v.in === inKey);
|
|
514
|
-
if (!params.length) return null;
|
|
515
|
-
return params.reduce((schema, pathParameters) => {
|
|
516
|
-
const property = pathParameters.content?.[contentType]?.schema ?? pathParameters.schema;
|
|
517
|
-
const required = typeof schema.required === "boolean" ? schema.required : [...schema.required || [], pathParameters.required ? pathParameters.name : void 0].filter(Boolean);
|
|
518
|
-
const getDefaultStyle = (location) => {
|
|
519
|
-
if (location === "query") return "form";
|
|
520
|
-
if (location === "path") return "simple";
|
|
521
|
-
return "simple";
|
|
522
|
-
};
|
|
523
|
-
const style = pathParameters.style || getDefaultStyle(inKey);
|
|
524
|
-
const explode = pathParameters.explode !== void 0 ? pathParameters.explode : style === "form";
|
|
525
|
-
if (inKey === "query" && style === "form" && explode === true && property?.type === "object" && property?.additionalProperties && !property?.properties) return {
|
|
526
|
-
...schema,
|
|
527
|
-
description: pathParameters.description || schema.description,
|
|
528
|
-
deprecated: schema.deprecated,
|
|
529
|
-
example: property.example || schema.example,
|
|
530
|
-
additionalProperties: property.additionalProperties
|
|
531
|
-
};
|
|
532
|
-
return {
|
|
533
|
-
...schema,
|
|
534
|
-
description: schema.description,
|
|
535
|
-
deprecated: schema.deprecated,
|
|
536
|
-
example: schema.example,
|
|
537
|
-
required,
|
|
538
|
-
properties: {
|
|
539
|
-
...schema.properties,
|
|
540
|
-
[pathParameters.name]: {
|
|
541
|
-
description: pathParameters.description,
|
|
542
|
-
...property
|
|
543
|
-
}
|
|
544
|
-
}
|
|
545
|
-
};
|
|
546
|
-
}, {
|
|
547
|
-
type: "object",
|
|
548
|
-
required: [],
|
|
549
|
-
properties: {}
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
async validate() {
|
|
553
|
-
return validate(this.api);
|
|
554
|
-
}
|
|
555
|
-
flattenSchema(schema) {
|
|
556
|
-
return flattenSchema(schema);
|
|
557
|
-
}
|
|
558
|
-
/**
|
|
559
|
-
* Get schemas from OpenAPI components (schemas, responses, requestBodies).
|
|
560
|
-
* Returns schemas in dependency order along with name mapping for collision resolution.
|
|
561
|
-
*/
|
|
562
|
-
getSchemas(options = {}) {
|
|
563
|
-
const contentType = options.contentType ?? this.#options.contentType;
|
|
564
|
-
const includes = options.includes ?? [
|
|
565
|
-
"schemas",
|
|
566
|
-
"requestBodies",
|
|
567
|
-
"responses"
|
|
568
|
-
];
|
|
569
|
-
const shouldResolveCollisions = options.collisionDetection ?? this.#options.collisionDetection ?? false;
|
|
570
|
-
const components = this.getDefinition().components;
|
|
571
|
-
const schemasWithMeta = [];
|
|
572
|
-
if (includes.includes("schemas")) {
|
|
573
|
-
const componentSchemas = components?.schemas || {};
|
|
574
|
-
for (const [name, schemaObject] of Object.entries(componentSchemas)) {
|
|
575
|
-
let schema = schemaObject;
|
|
576
|
-
if (isReference(schemaObject)) {
|
|
577
|
-
const resolved = this.get(schemaObject.$ref);
|
|
578
|
-
if (resolved && !isReference(resolved)) schema = resolved;
|
|
579
|
-
}
|
|
580
|
-
schemasWithMeta.push({
|
|
581
|
-
schema,
|
|
582
|
-
source: "schemas",
|
|
583
|
-
originalName: name
|
|
584
|
-
});
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
if (includes.includes("responses")) {
|
|
588
|
-
const responses = components?.responses || {};
|
|
589
|
-
for (const [name, response] of Object.entries(responses)) {
|
|
590
|
-
const schema = extractSchemaFromContent(response.content, contentType);
|
|
591
|
-
if (schema) {
|
|
592
|
-
let resolvedSchema = schema;
|
|
593
|
-
if (isReference(schema)) {
|
|
594
|
-
const resolved = this.get(schema.$ref);
|
|
595
|
-
if (resolved && !isReference(resolved)) resolvedSchema = resolved;
|
|
596
|
-
}
|
|
597
|
-
schemasWithMeta.push({
|
|
598
|
-
schema: resolvedSchema,
|
|
599
|
-
source: "responses",
|
|
600
|
-
originalName: name
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
if (includes.includes("requestBodies")) {
|
|
606
|
-
const requestBodies = components?.requestBodies || {};
|
|
607
|
-
for (const [name, request] of Object.entries(requestBodies)) {
|
|
608
|
-
const schema = extractSchemaFromContent(request.content, contentType);
|
|
609
|
-
if (schema) {
|
|
610
|
-
let resolvedSchema = schema;
|
|
611
|
-
if (isReference(schema)) {
|
|
612
|
-
const resolved = this.get(schema.$ref);
|
|
613
|
-
if (resolved && !isReference(resolved)) resolvedSchema = resolved;
|
|
614
|
-
}
|
|
615
|
-
schemasWithMeta.push({
|
|
616
|
-
schema: resolvedSchema,
|
|
617
|
-
source: "requestBodies",
|
|
618
|
-
originalName: name
|
|
619
|
-
});
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
const { schemas, nameMapping } = shouldResolveCollisions ? resolveCollisions(schemasWithMeta) : legacyResolve(schemasWithMeta);
|
|
624
|
-
return {
|
|
625
|
-
schemas: sortSchemas(schemas),
|
|
626
|
-
nameMapping
|
|
627
|
-
};
|
|
628
|
-
}
|
|
629
|
-
};
|
|
630
|
-
//#endregion
|
|
631
|
-
//#region src/oas/utils.ts
|
|
632
|
-
/**
|
|
633
|
-
* Narrows `doc` to a Swagger 2.0 document.
|
|
634
|
-
* Swagger 2.0 documents do not have an `openapi` version key.
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* if (isOpenApiV2Document(doc)) {
|
|
433
|
+
* // doc is OpenAPIV2.Document
|
|
434
|
+
* }
|
|
435
|
+
* ```
|
|
635
436
|
*/
|
|
636
437
|
function isOpenApiV2Document(doc) {
|
|
637
438
|
return !!doc && isPlainObject(doc) && !("openapi" in doc);
|
|
@@ -639,9 +440,15 @@ function isOpenApiV2Document(doc) {
|
|
|
639
440
|
/**
|
|
640
441
|
* Returns `true` when a schema should be treated as nullable.
|
|
641
442
|
*
|
|
642
|
-
*
|
|
643
|
-
* -
|
|
644
|
-
*
|
|
443
|
+
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
444
|
+
* `x-nullable: true` (vendor extension), `type: 'null'`, and `type: ['null', ...]` (OAS 3.1).
|
|
445
|
+
*
|
|
446
|
+
* @example
|
|
447
|
+
* ```ts
|
|
448
|
+
* isNullable({ type: 'string', nullable: true }) // true
|
|
449
|
+
* isNullable({ type: ['string', 'null'] }) // true
|
|
450
|
+
* isNullable({ type: 'string' }) // false
|
|
451
|
+
* ```
|
|
645
452
|
*/
|
|
646
453
|
function isNullable(schema) {
|
|
647
454
|
if ((schema?.nullable ?? schema?.["x-nullable"]) === true) return true;
|
|
@@ -651,34 +458,51 @@ function isNullable(schema) {
|
|
|
651
458
|
return false;
|
|
652
459
|
}
|
|
653
460
|
/**
|
|
654
|
-
*
|
|
655
|
-
*
|
|
461
|
+
* Returns `true` when `obj` is an OpenAPI `$ref` pointer object.
|
|
462
|
+
*
|
|
463
|
+
* @example
|
|
464
|
+
* ```ts
|
|
465
|
+
* isReference({ $ref: '#/components/schemas/Pet' }) // true
|
|
466
|
+
* isReference({ type: 'string' }) // false
|
|
467
|
+
* ```
|
|
656
468
|
*/
|
|
657
469
|
function isReference(obj) {
|
|
658
|
-
return !!obj &&
|
|
470
|
+
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
659
471
|
}
|
|
660
472
|
/**
|
|
661
|
-
* Returns `true` when `obj` is a schema
|
|
662
|
-
*
|
|
473
|
+
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object.
|
|
474
|
+
*
|
|
475
|
+
* @example
|
|
476
|
+
* ```ts
|
|
477
|
+
* isDiscriminator({ discriminator: { propertyName: 'type', mapping: {} } }) // true
|
|
478
|
+
* isDiscriminator({ discriminator: 'type' }) // false (Swagger 2 string form)
|
|
479
|
+
* ```
|
|
663
480
|
*/
|
|
664
481
|
function isDiscriminator(obj) {
|
|
665
482
|
const record = obj;
|
|
666
483
|
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
667
484
|
}
|
|
485
|
+
//#endregion
|
|
486
|
+
//#region src/factory.ts
|
|
668
487
|
/**
|
|
669
|
-
* Loads
|
|
488
|
+
* Loads and dereferences an OpenAPI document, returning the raw `Document`.
|
|
489
|
+
*
|
|
490
|
+
* Accepts a file path string or an already-parsed document object. File paths are bundled via
|
|
491
|
+
* Redocly to resolve external `$ref`s. Swagger 2.0 documents are automatically up-converted
|
|
492
|
+
* to OpenAPI 3.0 via `swagger2openapi`.
|
|
670
493
|
*
|
|
671
|
-
*
|
|
672
|
-
*
|
|
673
|
-
*
|
|
494
|
+
* @example
|
|
495
|
+
* ```ts
|
|
496
|
+
* const document = await parseDocument('./openapi.yaml')
|
|
497
|
+
* const document = await parse(rawDocumentObject, { canBundle: false })
|
|
498
|
+
* ```
|
|
674
499
|
*/
|
|
675
|
-
async function
|
|
676
|
-
if (typeof pathOrApi === "string" && canBundle) return
|
|
500
|
+
async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true } = {}) {
|
|
501
|
+
if (typeof pathOrApi === "string" && canBundle) return parseDocument((await bundle({
|
|
677
502
|
ref: pathOrApi,
|
|
678
503
|
config: await loadConfig(),
|
|
679
504
|
base: pathOrApi
|
|
680
505
|
})).bundle.parsed, {
|
|
681
|
-
oasClass,
|
|
682
506
|
canBundle,
|
|
683
507
|
enablePaths
|
|
684
508
|
});
|
|
@@ -688,26 +512,27 @@ async function parse(pathOrApi, { oasClass = Oas, canBundle = true, enablePaths
|
|
|
688
512
|
}).load();
|
|
689
513
|
if (isOpenApiV2Document(document)) {
|
|
690
514
|
const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
|
|
691
|
-
return
|
|
515
|
+
return openapi;
|
|
692
516
|
}
|
|
693
|
-
return
|
|
517
|
+
return document;
|
|
694
518
|
}
|
|
695
519
|
/**
|
|
696
|
-
* Deep-merges multiple OpenAPI documents into a single `
|
|
520
|
+
* Deep-merges multiple OpenAPI documents into a single `Document`.
|
|
697
521
|
*
|
|
698
|
-
* Each document is parsed independently
|
|
699
|
-
*
|
|
700
|
-
* the returned `Oas` instance is fully initialized.
|
|
522
|
+
* Each document is parsed independently then recursively merged with `remeda`'s `mergeDeep`.
|
|
523
|
+
* Throws when the input array is empty.
|
|
701
524
|
*
|
|
702
|
-
*
|
|
525
|
+
* @example
|
|
526
|
+
* ```ts
|
|
527
|
+
* const document = await mergeDocuments(['./pets.yaml', './orders.yaml'])
|
|
528
|
+
* ```
|
|
703
529
|
*/
|
|
704
|
-
async function
|
|
705
|
-
const
|
|
706
|
-
oasClass,
|
|
530
|
+
async function mergeDocuments(pathOrApi) {
|
|
531
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
707
532
|
enablePaths: false,
|
|
708
533
|
canBundle: false
|
|
709
534
|
})));
|
|
710
|
-
if (
|
|
535
|
+
if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
|
|
711
536
|
const seed = {
|
|
712
537
|
openapi: MERGE_OPENAPI_VERSION,
|
|
713
538
|
info: {
|
|
@@ -717,38 +542,248 @@ async function merge(pathOrApi, { oasClass = Oas } = {}) {
|
|
|
717
542
|
paths: {},
|
|
718
543
|
components: { schemas: {} }
|
|
719
544
|
};
|
|
720
|
-
return
|
|
545
|
+
return parseDocument(documents.reduce((acc, current) => mergeDeep(acc, current), seed));
|
|
721
546
|
}
|
|
722
547
|
/**
|
|
723
|
-
*
|
|
548
|
+
* Creates a `Document` from an `AdapterSource`.
|
|
549
|
+
*
|
|
550
|
+
* Handles all three source types:
|
|
551
|
+
* - `{ type: 'path' }` — resolves and bundles a local file path or remote URL.
|
|
552
|
+
* - `{ type: 'paths' }` — merges multiple file paths into a single document.
|
|
553
|
+
* - `{ type: 'data' }` — parses an inline string (YAML/JSON) or raw object.
|
|
724
554
|
*
|
|
725
|
-
*
|
|
726
|
-
*
|
|
727
|
-
*
|
|
728
|
-
*
|
|
555
|
+
* @example
|
|
556
|
+
* ```ts
|
|
557
|
+
* const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
|
|
558
|
+
* const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
|
|
559
|
+
* ```
|
|
560
|
+
*/
|
|
561
|
+
function parseFromConfig(source) {
|
|
562
|
+
if (source.type === "data") {
|
|
563
|
+
if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
|
|
564
|
+
try {
|
|
565
|
+
return parseDocument(yaml.parse(source.data));
|
|
566
|
+
} catch {
|
|
567
|
+
return parseDocument(source.data);
|
|
568
|
+
}
|
|
569
|
+
}
|
|
570
|
+
if (source.type === "paths") return mergeDocuments(source.paths);
|
|
571
|
+
if (new URLPath(source.path).isURL) return parseDocument(source.path);
|
|
572
|
+
return parseDocument(path.resolve(path.dirname(source.path), source.path));
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Validates an OpenAPI document using `oas-normalize` with colorized error output.
|
|
576
|
+
*
|
|
577
|
+
* @example
|
|
578
|
+
* ```ts
|
|
579
|
+
* await validateDocument(document)
|
|
580
|
+
* ```
|
|
581
|
+
*/
|
|
582
|
+
async function validateDocument(document) {
|
|
583
|
+
try {
|
|
584
|
+
await new OASNormalize(document, {
|
|
585
|
+
enablePaths: true,
|
|
586
|
+
colorizeErrors: true
|
|
587
|
+
}).validate({ parser: { validate: { errors: { colorize: true } } } });
|
|
588
|
+
} catch (_err) {}
|
|
589
|
+
}
|
|
590
|
+
//#endregion
|
|
591
|
+
//#region src/refs.ts
|
|
592
|
+
/**
|
|
593
|
+
* Resolves a local JSON pointer reference from a document.
|
|
594
|
+
*
|
|
595
|
+
* Accepts `#/...` refs. Returns `null` for empty or non-local refs.
|
|
596
|
+
* Throws when the pointer cannot be resolved.
|
|
597
|
+
*
|
|
598
|
+
* @example
|
|
599
|
+
* ```ts
|
|
600
|
+
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet') // SchemaObject | null
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
function resolveRef(document, $ref) {
|
|
604
|
+
const origRef = $ref;
|
|
605
|
+
$ref = $ref.trim();
|
|
606
|
+
if ($ref === "") return null;
|
|
607
|
+
if ($ref.startsWith("#")) $ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
608
|
+
else return null;
|
|
609
|
+
const current = jsonpointer.get(document, $ref);
|
|
610
|
+
if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
|
|
611
|
+
return current;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Resolves a `$ref` object while preserving the original `$ref` field on the result.
|
|
615
|
+
*
|
|
616
|
+
* Useful for parser flows that need both dereferenced fields and pointer
|
|
617
|
+
* identity (for naming/import purposes). Non-reference values are returned as-is.
|
|
618
|
+
*
|
|
619
|
+
* @example
|
|
620
|
+
* ```ts
|
|
621
|
+
* dereferenceWithRef(document, { $ref: '#/components/schemas/Pet' })
|
|
622
|
+
* // { $ref: '#/components/schemas/Pet', type: 'object', properties: { ... } }
|
|
623
|
+
* ```
|
|
624
|
+
*/
|
|
625
|
+
function dereferenceWithRef(document, schema) {
|
|
626
|
+
if (isReference(schema)) return {
|
|
627
|
+
...schema,
|
|
628
|
+
...resolveRef(document, schema.$ref),
|
|
629
|
+
$ref: schema.$ref
|
|
630
|
+
};
|
|
631
|
+
return schema;
|
|
632
|
+
}
|
|
633
|
+
//#endregion
|
|
634
|
+
//#region src/resolvers.ts
|
|
635
|
+
/**
|
|
636
|
+
* Resolves `{variable}` placeholders in an OpenAPI server URL.
|
|
637
|
+
*
|
|
638
|
+
* Resolution order per variable: `overrides[key]` → `variable.default` → left unreplaced.
|
|
639
|
+
* Throws when an override value is not in the variable's allowed `enum` list.
|
|
640
|
+
*
|
|
641
|
+
* @example
|
|
642
|
+
* ```ts
|
|
643
|
+
* resolveServerUrl(
|
|
644
|
+
* { url: 'https://{env}.api.example.com', variables: { env: { default: 'dev', enum: ['dev', 'prod'] } } },
|
|
645
|
+
* { env: 'prod' },
|
|
646
|
+
* )
|
|
647
|
+
* // 'https://prod.api.example.com'
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
650
|
+
function resolveServerUrl(server, overrides) {
|
|
651
|
+
if (!server.variables) return server.url;
|
|
652
|
+
let url = server.url;
|
|
653
|
+
for (const [key, variable] of Object.entries(server.variables)) {
|
|
654
|
+
const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
|
|
655
|
+
if (value === void 0) continue;
|
|
656
|
+
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(`Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`);
|
|
657
|
+
url = url.replaceAll(`{${key}}`, value);
|
|
658
|
+
}
|
|
659
|
+
return url;
|
|
660
|
+
}
|
|
661
|
+
/**
|
|
662
|
+
* Looks up the Kubb `SchemaType` for a given OAS `format` string.
|
|
663
|
+
* Returns `undefined` for formats not in `formatMap` (e.g. `int64`, `date-time`),
|
|
664
|
+
* which are handled separately because their output depends on parser options.
|
|
665
|
+
*/
|
|
666
|
+
function getSchemaType(format) {
|
|
667
|
+
return formatMap[format] ?? null;
|
|
668
|
+
}
|
|
669
|
+
/**
|
|
670
|
+
* Maps an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
671
|
+
* Numeric types (`number`, `integer`, `bigint`) are returned unchanged;
|
|
672
|
+
* `boolean` maps to `'boolean'`; everything else defaults to `'string'`.
|
|
673
|
+
*/
|
|
674
|
+
function getPrimitiveType(type) {
|
|
675
|
+
if (type === "number" || type === "integer" || type === "bigint") return type;
|
|
676
|
+
if (type === "boolean") return "boolean";
|
|
677
|
+
return "string";
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Narrows a raw content-type string to the `MediaType` union recognized by Kubb.
|
|
681
|
+
* Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
|
|
682
|
+
*/
|
|
683
|
+
function getMediaType(contentType) {
|
|
684
|
+
return Object.values(mediaTypes).includes(contentType) ? contentType : null;
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Returns all resolved parameters for an operation, merging path-level and operation-level entries.
|
|
688
|
+
*
|
|
689
|
+
* Operation-level parameters take precedence over path-level ones with the same `in:name` key.
|
|
690
|
+
* `$ref` parameters are resolved via `dereferenceWithRef` to restore backward compatibility
|
|
691
|
+
* with `oas` v31+ which otherwise filters them out.
|
|
692
|
+
*
|
|
693
|
+
* @example
|
|
694
|
+
* ```ts
|
|
695
|
+
* getParameters(document, operation)
|
|
696
|
+
* // [{ name: 'petId', in: 'path', required: true, schema: { type: 'integer' } }]
|
|
697
|
+
* ```
|
|
698
|
+
*/
|
|
699
|
+
function getParameters(document, operation) {
|
|
700
|
+
const resolveParams = (params) => params.map((p) => dereferenceWithRef(document, p)).filter((p) => !!p && typeof p === "object" && "in" in p && "name" in p);
|
|
701
|
+
const operationParams = resolveParams(operation.schema?.parameters || []);
|
|
702
|
+
const pathItem = document.paths?.[operation.path];
|
|
703
|
+
const pathLevelParams = resolveParams(pathItem && !isReference(pathItem) && pathItem.parameters ? pathItem.parameters : []);
|
|
704
|
+
const paramMap = /* @__PURE__ */ new Map();
|
|
705
|
+
for (const p of pathLevelParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
|
|
706
|
+
for (const p of operationParams) if (p.name && p.in) paramMap.set(`${p.in}:${p.name}`, p);
|
|
707
|
+
return Array.from(paramMap.values());
|
|
708
|
+
}
|
|
709
|
+
function getResponseBody(responseBody, contentType) {
|
|
710
|
+
if (!responseBody) return false;
|
|
711
|
+
if (isReference(responseBody)) return false;
|
|
712
|
+
const body = responseBody;
|
|
713
|
+
if (!body.content) return false;
|
|
714
|
+
if (contentType) {
|
|
715
|
+
if (!(contentType in body.content)) return false;
|
|
716
|
+
return body.content[contentType];
|
|
717
|
+
}
|
|
718
|
+
let availableContentType;
|
|
719
|
+
const contentTypes = Object.keys(body.content);
|
|
720
|
+
contentTypes.forEach((mt) => {
|
|
721
|
+
if (!availableContentType && matchesMimeType.json(mt)) availableContentType = mt;
|
|
722
|
+
});
|
|
723
|
+
if (!availableContentType) contentTypes.forEach((mt) => {
|
|
724
|
+
if (!availableContentType) availableContentType = mt;
|
|
725
|
+
});
|
|
726
|
+
if (availableContentType) return [
|
|
727
|
+
availableContentType,
|
|
728
|
+
body.content[availableContentType],
|
|
729
|
+
...body.description ? [body.description] : []
|
|
730
|
+
];
|
|
731
|
+
return false;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Returns the response schema for a given operation and HTTP status code.
|
|
735
|
+
*
|
|
736
|
+
* Returns an empty object `{}` when no response body schema is available.
|
|
737
|
+
*
|
|
738
|
+
* @example
|
|
739
|
+
* ```ts
|
|
740
|
+
* getResponseSchema(document, operation, 200) // SchemaObject
|
|
741
|
+
* getResponseSchema(document, operation, '4XX') // {}
|
|
742
|
+
* ```
|
|
743
|
+
*/
|
|
744
|
+
function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
745
|
+
if (operation.schema.responses) Object.keys(operation.schema.responses).forEach((key) => {
|
|
746
|
+
const schema = operation.schema.responses[key];
|
|
747
|
+
const $ref = isReference(schema) ? schema.$ref : void 0;
|
|
748
|
+
if (schema && $ref) operation.schema.responses[key] = resolveRef(document, $ref);
|
|
749
|
+
});
|
|
750
|
+
const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
|
|
751
|
+
if (responseBody === false) return {};
|
|
752
|
+
const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
|
|
753
|
+
if (!schema) return {};
|
|
754
|
+
return dereferenceWithRef(document, schema);
|
|
755
|
+
}
|
|
756
|
+
/**
|
|
757
|
+
* Returns the request body schema for an operation, or `undefined` when absent.
|
|
758
|
+
*
|
|
759
|
+
* @example
|
|
760
|
+
* ```ts
|
|
761
|
+
* getRequestSchema(document, operation) // SchemaObject | null
|
|
762
|
+
* ```
|
|
729
763
|
*/
|
|
730
|
-
function
|
|
731
|
-
if (
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
if (Array.isArray(config.input)) return merge(config.input.map((input) => path.resolve(config.root, input.path)), { oasClass });
|
|
740
|
-
if (new URLPath(config.input.path).isURL) return parse(config.input.path, { oasClass });
|
|
741
|
-
return parse(path.resolve(config.root, config.input.path), { oasClass });
|
|
764
|
+
function getRequestSchema(document, operation, options = {}) {
|
|
765
|
+
if (operation.schema.requestBody) operation.schema.requestBody = dereferenceWithRef(document, operation.schema.requestBody);
|
|
766
|
+
const requestBody = operation.getRequestBody(options.contentType);
|
|
767
|
+
if (requestBody === false) return null;
|
|
768
|
+
const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
|
|
769
|
+
if (!schema) return null;
|
|
770
|
+
return dereferenceWithRef(document, schema);
|
|
742
771
|
}
|
|
743
772
|
/**
|
|
744
|
-
* Flattens a
|
|
773
|
+
* Flattens a keyword-only `allOf` into its parent schema.
|
|
745
774
|
*
|
|
746
|
-
*
|
|
747
|
-
*
|
|
748
|
-
*
|
|
775
|
+
* Only flattens when every member is a plain fragment — no `$ref` and no structural keywords
|
|
776
|
+
* (see `structuralKeys`). Outer schema values take precedence over fragment values.
|
|
777
|
+
* Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
|
|
778
|
+
*
|
|
779
|
+
* @example
|
|
780
|
+
* ```ts
|
|
781
|
+
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
782
|
+
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
749
783
|
*
|
|
750
|
-
*
|
|
751
|
-
*
|
|
784
|
+
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
785
|
+
* // returned unchanged — contains a $ref
|
|
786
|
+
* ```
|
|
752
787
|
*/
|
|
753
788
|
function flattenSchema(schema) {
|
|
754
789
|
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
|
|
@@ -761,18 +796,26 @@ function flattenSchema(schema) {
|
|
|
761
796
|
return merged;
|
|
762
797
|
}
|
|
763
798
|
/**
|
|
764
|
-
*
|
|
765
|
-
*
|
|
799
|
+
* Extracts the inline schema from a media-type `content` map.
|
|
800
|
+
*
|
|
801
|
+
* Prefers `preferredContentType` when given; otherwise uses the first key in the map.
|
|
802
|
+
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
803
|
+
*
|
|
804
|
+
* @example
|
|
805
|
+
* ```ts
|
|
806
|
+
* extractSchemaFromContent(operation.content, 'application/json')
|
|
807
|
+
* // SchemaObject | null
|
|
808
|
+
* ```
|
|
766
809
|
*/
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
810
|
+
function extractSchemaFromContent(content, preferredContentType) {
|
|
811
|
+
if (!content) return null;
|
|
812
|
+
const firstContentType = Object.keys(content)[0] ?? "application/json";
|
|
813
|
+
const schema = content[preferredContentType ?? firstContentType]?.schema;
|
|
814
|
+
if (schema && "$ref" in schema) return null;
|
|
815
|
+
return schema ?? null;
|
|
772
816
|
}
|
|
773
817
|
/**
|
|
774
|
-
* Walks a schema tree and collects the names of all `#/components/schemas/<name>`
|
|
775
|
-
* Used by `sortSchemas` to build the dependency graph.
|
|
818
|
+
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
776
819
|
*/
|
|
777
820
|
function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
778
821
|
if (Array.isArray(schema)) {
|
|
@@ -788,11 +831,14 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
788
831
|
/**
|
|
789
832
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
790
833
|
*
|
|
791
|
-
*
|
|
792
|
-
*
|
|
793
|
-
* referenced types before the types that depend on them.
|
|
834
|
+
* Referenced schemas appear before the schemas that depend on them, so code generators
|
|
835
|
+
* can emit types in the correct order. Cycles are silently skipped.
|
|
794
836
|
*
|
|
795
|
-
*
|
|
837
|
+
* @example
|
|
838
|
+
* ```ts
|
|
839
|
+
* const sorted = sortSchemas({ Order: orderSchema, Pet: petSchema })
|
|
840
|
+
* // Pet appears before Order when Order.$ref points at Pet
|
|
841
|
+
* ```
|
|
796
842
|
*/
|
|
797
843
|
function sortSchemas(schemas) {
|
|
798
844
|
const deps = /* @__PURE__ */ new Map();
|
|
@@ -812,283 +858,172 @@ function sortSchemas(schemas) {
|
|
|
812
858
|
for (const name of sorted) result[name] = schemas[name];
|
|
813
859
|
return result;
|
|
814
860
|
}
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
* - `content` is absent.
|
|
821
|
-
* - The target content-type has no `schema` entry.
|
|
822
|
-
* - The schema is a `$ref` (callers resolve refs separately).
|
|
823
|
-
*/
|
|
824
|
-
function extractSchemaFromContent(content, preferredContentType) {
|
|
825
|
-
if (!content) return null;
|
|
826
|
-
const firstContentType = Object.keys(content)[0] ?? "application/json";
|
|
827
|
-
const schema = content[preferredContentType ?? firstContentType]?.schema;
|
|
828
|
-
if (schema && "$ref" in schema) return null;
|
|
829
|
-
return schema ?? null;
|
|
830
|
-
}
|
|
831
|
-
/**
|
|
832
|
-
* Returns the PascalCase suffix appended to a component name when resolving
|
|
833
|
-
* cross-source name collisions (schemas vs. responses vs. requestBodies).
|
|
834
|
-
*/
|
|
861
|
+
const semanticSuffixes = {
|
|
862
|
+
schemas: "Schema",
|
|
863
|
+
responses: "Response",
|
|
864
|
+
requestBodies: "Request"
|
|
865
|
+
};
|
|
835
866
|
function getSemanticSuffix(source) {
|
|
836
|
-
|
|
837
|
-
case "schemas": return "Schema";
|
|
838
|
-
case "responses": return "Response";
|
|
839
|
-
case "requestBodies": return "Request";
|
|
840
|
-
}
|
|
867
|
+
return semanticSuffixes[source];
|
|
841
868
|
}
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
*/
|
|
847
|
-
function legacyResolve(schemasWithMeta) {
|
|
848
|
-
const schemas = {};
|
|
849
|
-
const nameMapping = /* @__PURE__ */ new Map();
|
|
850
|
-
for (const item of schemasWithMeta) {
|
|
851
|
-
schemas[item.originalName] = item.schema;
|
|
852
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName);
|
|
853
|
-
}
|
|
854
|
-
return {
|
|
855
|
-
schemas,
|
|
856
|
-
nameMapping
|
|
857
|
-
};
|
|
869
|
+
function resolveSchemaRef(document, schema) {
|
|
870
|
+
if (!isReference(schema)) return schema;
|
|
871
|
+
const resolved = resolveRef(document, schema.$ref);
|
|
872
|
+
return resolved && !isReference(resolved) ? resolved : schema;
|
|
858
873
|
}
|
|
859
874
|
/**
|
|
860
|
-
*
|
|
875
|
+
* Collects component schemas from one or more sources and resolves name collisions.
|
|
876
|
+
*
|
|
877
|
+
* Sources default to `['schemas', 'requestBodies', 'responses']`. Returned schemas are
|
|
878
|
+
* topologically sorted by `$ref` dependency so generators emit types in the correct order.
|
|
861
879
|
*
|
|
862
880
|
* When two or more schemas normalize to the same PascalCase name:
|
|
863
|
-
* -
|
|
864
|
-
* -
|
|
865
|
-
* a semantic suffix (`Schema`, `Response`, `Request`) is appended.
|
|
881
|
+
* - Same source → numeric suffix (`2`, `3`, …).
|
|
882
|
+
* - Different sources → semantic suffix (`Schema`, `Response`, `Request`).
|
|
866
883
|
*
|
|
867
|
-
*
|
|
884
|
+
* @example
|
|
885
|
+
* ```ts
|
|
886
|
+
* const { schemas, nameMapping } = getSchemas(document, { contentType: 'application/json' })
|
|
887
|
+
* ```
|
|
868
888
|
*/
|
|
869
|
-
function
|
|
870
|
-
const
|
|
871
|
-
const
|
|
889
|
+
function getSchemas(document, { contentType }) {
|
|
890
|
+
const components = document.components;
|
|
891
|
+
const candidates = [...Object.entries(components?.schemas ?? {}).map(([name, schema]) => ({
|
|
892
|
+
schema: resolveSchemaRef(document, schema),
|
|
893
|
+
source: "schemas",
|
|
894
|
+
originalName: name
|
|
895
|
+
})), ...["responses", "requestBodies"].flatMap((source) => Object.entries(components?.[source] ?? {}).flatMap(([name, item]) => {
|
|
896
|
+
const schema = extractSchemaFromContent(item.content, contentType);
|
|
897
|
+
return schema ? [{
|
|
898
|
+
schema: resolveSchemaRef(document, schema),
|
|
899
|
+
source,
|
|
900
|
+
originalName: name
|
|
901
|
+
}] : [];
|
|
902
|
+
}))];
|
|
872
903
|
const normalizedNames = /* @__PURE__ */ new Map();
|
|
873
|
-
for (const item of
|
|
874
|
-
const
|
|
875
|
-
const bucket = normalizedNames.get(
|
|
904
|
+
for (const item of candidates) {
|
|
905
|
+
const key = pascalCase(item.originalName);
|
|
906
|
+
const bucket = normalizedNames.get(key) ?? [];
|
|
876
907
|
bucket.push(item);
|
|
877
|
-
normalizedNames.set(
|
|
878
|
-
}
|
|
879
|
-
for (const [, items] of normalizedNames) {
|
|
880
|
-
if (items.length === 1) {
|
|
881
|
-
const item = items[0];
|
|
882
|
-
schemas[item.originalName] = item.schema;
|
|
883
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, item.originalName);
|
|
884
|
-
continue;
|
|
885
|
-
}
|
|
886
|
-
if (new Set(items.map((item) => item.source)).size === 1) items.forEach((item, index) => {
|
|
887
|
-
const uniqueName = item.originalName + (index === 0 ? "" : (index + 1).toString());
|
|
888
|
-
schemas[uniqueName] = item.schema;
|
|
889
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
|
|
890
|
-
});
|
|
891
|
-
else items.forEach((item) => {
|
|
892
|
-
const uniqueName = item.originalName + getSemanticSuffix(item.source);
|
|
893
|
-
schemas[uniqueName] = item.schema;
|
|
894
|
-
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
|
|
895
|
-
});
|
|
908
|
+
normalizedNames.set(key, bucket);
|
|
896
909
|
}
|
|
910
|
+
const schemas = {};
|
|
911
|
+
const nameMapping = /* @__PURE__ */ new Map();
|
|
912
|
+
const multipleSources = (items) => new Set(items.map((i) => i.source)).size > 1;
|
|
913
|
+
for (const [, items] of normalizedNames) items.forEach((item, index) => {
|
|
914
|
+
const suffix = items.length === 1 ? "" : multipleSources(items) ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
|
|
915
|
+
const uniqueName = item.originalName + suffix;
|
|
916
|
+
schemas[uniqueName] = item.schema;
|
|
917
|
+
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
|
|
918
|
+
});
|
|
897
919
|
return {
|
|
898
|
-
schemas,
|
|
920
|
+
schemas: sortSchemas(schemas),
|
|
899
921
|
nameMapping
|
|
900
922
|
};
|
|
901
923
|
}
|
|
902
|
-
//#endregion
|
|
903
|
-
//#region src/parser.ts
|
|
904
|
-
/**
|
|
905
|
-
* Default values for all `Options` fields.
|
|
906
|
-
*/
|
|
907
|
-
const DEFAULT_OPTIONS = {
|
|
908
|
-
dateType: "string",
|
|
909
|
-
integerType: "number",
|
|
910
|
-
unknownType: "any",
|
|
911
|
-
emptySchemaType: "any",
|
|
912
|
-
enumSuffix: "enum"
|
|
913
|
-
};
|
|
914
|
-
/**
|
|
915
|
-
* Looks up the Kubb `SchemaType` for a given OAS `format` string.
|
|
916
|
-
* Returns `undefined` for formats not in `formatMap` (e.g. `int64`, `date-time`),
|
|
917
|
-
* which are handled separately because their output depends on parser options.
|
|
918
|
-
*/
|
|
919
|
-
function formatToSchemaType(format) {
|
|
920
|
-
return formatMap[format];
|
|
921
|
-
}
|
|
922
|
-
/**
|
|
923
|
-
* Extracts the final path segment of a JSON Pointer `$ref` string.
|
|
924
|
-
* For `#/components/schemas/Order` this returns `'Order'`.
|
|
925
|
-
* Falls back to the full ref string when no slash is present.
|
|
926
|
-
*/
|
|
927
|
-
function extractRefName($ref) {
|
|
928
|
-
return $ref.split("/").at(-1) ?? $ref;
|
|
929
|
-
}
|
|
930
924
|
/**
|
|
931
|
-
*
|
|
932
|
-
*
|
|
933
|
-
* `boolean` maps to `'boolean'`; everything else defaults to `'string'`.
|
|
925
|
+
* Resolves the AST type descriptor for a date/time format, honoring the `dateType` option.
|
|
926
|
+
* Returns `null` when `dateType: false`, signalling the format should fall through to `string`.
|
|
934
927
|
*/
|
|
935
|
-
function
|
|
936
|
-
if (
|
|
937
|
-
if (
|
|
938
|
-
|
|
928
|
+
function getDateType(options, format) {
|
|
929
|
+
if (!options.dateType) return null;
|
|
930
|
+
if (format === "date-time") {
|
|
931
|
+
if (options.dateType === "date") return {
|
|
932
|
+
type: "date",
|
|
933
|
+
representation: "date"
|
|
934
|
+
};
|
|
935
|
+
if (options.dateType === "stringOffset") return {
|
|
936
|
+
type: "datetime",
|
|
937
|
+
offset: true
|
|
938
|
+
};
|
|
939
|
+
if (options.dateType === "stringLocal") return {
|
|
940
|
+
type: "datetime",
|
|
941
|
+
local: true
|
|
942
|
+
};
|
|
943
|
+
return {
|
|
944
|
+
type: "datetime",
|
|
945
|
+
offset: false
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
if (format === "date") return {
|
|
949
|
+
type: "date",
|
|
950
|
+
representation: options.dateType === "date" ? "date" : "string"
|
|
951
|
+
};
|
|
952
|
+
return {
|
|
953
|
+
type: "time",
|
|
954
|
+
representation: options.dateType === "date" ? "date" : "string"
|
|
955
|
+
};
|
|
939
956
|
}
|
|
940
957
|
/**
|
|
941
|
-
*
|
|
942
|
-
* Returns `undefined` for content types not present in `KNOWN_MEDIA_TYPES`.
|
|
958
|
+
* Collects the shared metadata fields passed to every `createSchema` call.
|
|
943
959
|
*/
|
|
944
|
-
function
|
|
945
|
-
return
|
|
960
|
+
function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
961
|
+
return {
|
|
962
|
+
name,
|
|
963
|
+
nullable,
|
|
964
|
+
title: schema.title,
|
|
965
|
+
description: schema.description,
|
|
966
|
+
deprecated: schema.deprecated,
|
|
967
|
+
readOnly: schema.readOnly,
|
|
968
|
+
writeOnly: schema.writeOnly,
|
|
969
|
+
default: defaultValue,
|
|
970
|
+
example: schema.example
|
|
971
|
+
};
|
|
946
972
|
}
|
|
973
|
+
//#endregion
|
|
974
|
+
//#region src/parser.ts
|
|
947
975
|
/**
|
|
948
|
-
*
|
|
949
|
-
*
|
|
950
|
-
*
|
|
976
|
+
* Normalize a malformed `{ type: 'array', enum: [...] }` schema by moving the
|
|
977
|
+
* enum values into the items sub-schema. This pattern is technically invalid OAS
|
|
978
|
+
* but appears in the wild and must be handled gracefully.
|
|
951
979
|
*/
|
|
952
|
-
function
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
980
|
+
function normalizeArrayEnum(schema) {
|
|
981
|
+
const normalizedItems = {
|
|
982
|
+
...typeof schema.items === "object" && !Array.isArray(schema.items) ? schema.items : {},
|
|
983
|
+
enum: schema.enum
|
|
984
|
+
};
|
|
985
|
+
const { enum: _enum, ...schemaWithoutEnum } = schema;
|
|
956
986
|
return {
|
|
957
|
-
...
|
|
958
|
-
|
|
959
|
-
...schema.properties,
|
|
960
|
-
[propName]: {
|
|
961
|
-
...schema.properties[propName],
|
|
962
|
-
enum: schema.discriminator.mapping ? Object.keys(schema.discriminator.mapping) : void 0
|
|
963
|
-
}
|
|
964
|
-
}
|
|
987
|
+
...schemaWithoutEnum,
|
|
988
|
+
items: normalizedItems
|
|
965
989
|
};
|
|
966
990
|
}
|
|
967
991
|
/**
|
|
968
|
-
*
|
|
969
|
-
* the `@kubb/ast` tree.
|
|
992
|
+
* Builds the internal converter functions for a given `OasParserContext`.
|
|
970
993
|
*
|
|
971
|
-
*
|
|
972
|
-
*
|
|
973
|
-
*
|
|
974
|
-
* This is the **kubb-parser** stage of the compilation lifecycle:
|
|
975
|
-
* OpenAPI / Swagger → Kubb AST
|
|
976
|
-
*
|
|
977
|
-
* No code is generated here; the resulting tree is spec-agnostic and can
|
|
978
|
-
* be consumed by any downstream plugin (plugin-ts, plugin-zod, …).
|
|
979
|
-
*
|
|
980
|
-
* @example
|
|
981
|
-
* ```ts
|
|
982
|
-
* const parser = createOasParser(oas)
|
|
983
|
-
* const root = parser.parse({ emptySchemaType: 'unknown' })
|
|
984
|
-
* ```
|
|
994
|
+
* All `convert*` functions are defined as function declarations so they can freely
|
|
995
|
+
* reference each other and `parseSchema` via JS hoisting (mutual recursion).
|
|
985
996
|
*/
|
|
986
|
-
function
|
|
987
|
-
const
|
|
988
|
-
contentType,
|
|
989
|
-
collisionDetection
|
|
990
|
-
});
|
|
991
|
-
/**
|
|
992
|
-
* Maps an `'any' | 'unknown' | 'void'` option string to the corresponding `SchemaType` constant.
|
|
993
|
-
* Used for both `unknownType` (unannotated schemas) and `emptySchemaType` (empty `{}` schemas).
|
|
994
|
-
*/
|
|
995
|
-
function resolveTypeOption(value) {
|
|
996
|
-
if (value === "any") return schemaTypes.any;
|
|
997
|
-
if (value === "void") return schemaTypes.void;
|
|
998
|
-
return schemaTypes.unknown;
|
|
999
|
-
}
|
|
1000
|
-
/**
|
|
1001
|
-
* Resolves the AST type and datetime modifiers for a date/time format, honoring the `dateType` option.
|
|
1002
|
-
* Returns `undefined` when `dateType` is `false`, meaning the format should fall through to `string`.
|
|
1003
|
-
*/
|
|
1004
|
-
function getDateType(options, format) {
|
|
1005
|
-
if (!options.dateType) return;
|
|
1006
|
-
if (format === "date-time") {
|
|
1007
|
-
if (options.dateType === "date") return {
|
|
1008
|
-
type: "date",
|
|
1009
|
-
representation: "date"
|
|
1010
|
-
};
|
|
1011
|
-
if (options.dateType === "stringOffset") return {
|
|
1012
|
-
type: "datetime",
|
|
1013
|
-
offset: true
|
|
1014
|
-
};
|
|
1015
|
-
if (options.dateType === "stringLocal") return {
|
|
1016
|
-
type: "datetime",
|
|
1017
|
-
local: true
|
|
1018
|
-
};
|
|
1019
|
-
return {
|
|
1020
|
-
type: "datetime",
|
|
1021
|
-
offset: false
|
|
1022
|
-
};
|
|
1023
|
-
}
|
|
1024
|
-
if (format === "date") return {
|
|
1025
|
-
type: "date",
|
|
1026
|
-
representation: options.dateType === "date" ? "date" : "string"
|
|
1027
|
-
};
|
|
1028
|
-
return {
|
|
1029
|
-
type: "time",
|
|
1030
|
-
representation: options.dateType === "date" ? "date" : "string"
|
|
1031
|
-
};
|
|
1032
|
-
}
|
|
997
|
+
function createSchemaParser(ctx) {
|
|
998
|
+
const document = ctx.document;
|
|
1033
999
|
/**
|
|
1034
|
-
*
|
|
1035
|
-
* Centralizes the common properties so sub-handlers don't repeat them.
|
|
1000
|
+
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1036
1001
|
*/
|
|
1037
|
-
function
|
|
1038
|
-
return {
|
|
1039
|
-
|
|
1002
|
+
function convertRef({ schema, nullable, defaultValue }) {
|
|
1003
|
+
return createSchema({
|
|
1004
|
+
type: "ref",
|
|
1005
|
+
name: extractRefName(schema.$ref),
|
|
1006
|
+
ref: schema.$ref,
|
|
1040
1007
|
nullable,
|
|
1041
|
-
title: schema.title,
|
|
1042
1008
|
description: schema.description,
|
|
1043
1009
|
deprecated: schema.deprecated,
|
|
1044
1010
|
readOnly: schema.readOnly,
|
|
1045
1011
|
writeOnly: schema.writeOnly,
|
|
1046
|
-
|
|
1047
|
-
example: schema.example
|
|
1048
|
-
};
|
|
1049
|
-
}
|
|
1050
|
-
/**
|
|
1051
|
-
* Converts a `$ref` schema pointer into a `RefSchemaNode`.
|
|
1052
|
-
*
|
|
1053
|
-
* In OAS 3.0 siblings of `$ref` are technically ignored by the spec, but Kubb intentionally
|
|
1054
|
-
* preserves them so that annotations like `pattern`, `description`, and `nullable` are
|
|
1055
|
-
* reflected in generated JSDoc and type modifiers.
|
|
1056
|
-
*/
|
|
1057
|
-
function convertRef({ schema, nullable, defaultValue }) {
|
|
1058
|
-
const schemaObject = schema;
|
|
1059
|
-
return createSchema({
|
|
1060
|
-
type: "ref",
|
|
1061
|
-
name: extractRefName(schemaObject.$ref),
|
|
1062
|
-
ref: schemaObject.$ref,
|
|
1063
|
-
nullable,
|
|
1064
|
-
description: schemaObject.description,
|
|
1065
|
-
deprecated: schemaObject.deprecated,
|
|
1066
|
-
readOnly: schemaObject.readOnly,
|
|
1067
|
-
writeOnly: schemaObject.writeOnly,
|
|
1068
|
-
pattern: schemaObject.type === "string" ? schemaObject.pattern : void 0,
|
|
1069
|
-
example: schemaObject.example,
|
|
1012
|
+
pattern: schema.type === "string" ? schema.pattern : void 0,
|
|
1013
|
+
example: schema.example,
|
|
1070
1014
|
default: defaultValue
|
|
1071
1015
|
});
|
|
1072
1016
|
}
|
|
1073
1017
|
/**
|
|
1074
|
-
* Converts
|
|
1075
|
-
* or an `IntersectionSchemaNode` (multi-member `allOf`).
|
|
1076
|
-
*
|
|
1077
|
-
* Single-member `allOf` without sibling structural keys is the common OAS 3.0 pattern for
|
|
1078
|
-
* annotating a `$ref` or primitive with extra constraints; it is flattened to avoid
|
|
1079
|
-
* producing needless intersection wrappers.
|
|
1080
|
-
*
|
|
1081
|
-
* The flatten path is skipped when the outer schema carries structural keys that cannot be
|
|
1082
|
-
* merged into annotation fields: `properties`, `required`, or `additionalProperties`.
|
|
1083
|
-
* Those cases must become an intersection so the constraints are preserved.
|
|
1084
|
-
*
|
|
1085
|
-
* Circular references through discriminator parents are detected and skipped to prevent
|
|
1086
|
-
* infinite recursion during code generation.
|
|
1018
|
+
* Converts an `allOf` schema into a flattened node or an `IntersectionSchemaNode`.
|
|
1087
1019
|
*/
|
|
1088
|
-
function convertAllOf({ schema, name, nullable, defaultValue,
|
|
1020
|
+
function convertAllOf({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1089
1021
|
if (schema.allOf.length === 1 && !schema.properties && !(Array.isArray(schema.required) && schema.required.length) && schema.additionalProperties === void 0) {
|
|
1090
1022
|
const [memberSchema] = schema.allOf;
|
|
1091
|
-
const memberNode =
|
|
1023
|
+
const memberNode = parseSchema({
|
|
1024
|
+
schema: memberSchema,
|
|
1025
|
+
name: null
|
|
1026
|
+
}, rawOptions);
|
|
1092
1027
|
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1093
1028
|
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
1094
1029
|
const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
|
|
@@ -1106,81 +1041,123 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1106
1041
|
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1107
1042
|
});
|
|
1108
1043
|
}
|
|
1044
|
+
const filteredDiscriminantValues = [];
|
|
1109
1045
|
const allOfMembers = schema.allOf.filter((item) => {
|
|
1110
1046
|
if (!isReference(item) || !name) return true;
|
|
1111
|
-
const deref =
|
|
1047
|
+
const deref = resolveRef(document, item.$ref);
|
|
1112
1048
|
if (!deref || !isDiscriminator(deref)) return true;
|
|
1113
1049
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1114
1050
|
if (!parentUnion) return true;
|
|
1115
1051
|
const childRef = `#/components/schemas/${name}`;
|
|
1116
1052
|
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1117
1053
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1118
|
-
|
|
1119
|
-
|
|
1054
|
+
if (inOneOf || inMapping) {
|
|
1055
|
+
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
|
|
1056
|
+
if (discriminatorValue) filteredDiscriminantValues.push({
|
|
1057
|
+
propertyName: deref.discriminator.propertyName,
|
|
1058
|
+
value: discriminatorValue
|
|
1059
|
+
});
|
|
1060
|
+
return false;
|
|
1061
|
+
}
|
|
1062
|
+
return true;
|
|
1063
|
+
}).map((s) => parseSchema({ schema: s }, rawOptions));
|
|
1064
|
+
const syntheticStart = allOfMembers.length;
|
|
1120
1065
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1121
1066
|
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1122
1067
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1123
1068
|
if (missingRequired.length) {
|
|
1124
1069
|
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1125
1070
|
if (!isReference(item)) return [item];
|
|
1126
|
-
const deref =
|
|
1071
|
+
const deref = resolveRef(document, item.$ref);
|
|
1127
1072
|
return deref && !isReference(deref) ? [deref] : [];
|
|
1128
1073
|
});
|
|
1129
1074
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1130
|
-
allOfMembers.push(
|
|
1075
|
+
allOfMembers.push(parseSchema({ schema: {
|
|
1131
1076
|
properties: { [key]: resolved.properties[key] },
|
|
1132
1077
|
required: [key]
|
|
1133
|
-
} },
|
|
1078
|
+
} }, rawOptions));
|
|
1134
1079
|
break;
|
|
1135
1080
|
}
|
|
1136
1081
|
}
|
|
1137
1082
|
}
|
|
1138
1083
|
if (schema.properties) {
|
|
1139
1084
|
const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
|
|
1140
|
-
allOfMembers.push(
|
|
1085
|
+
allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
|
|
1141
1086
|
}
|
|
1087
|
+
for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
|
|
1088
|
+
propertyName,
|
|
1089
|
+
value
|
|
1090
|
+
}));
|
|
1142
1091
|
return createSchema({
|
|
1143
1092
|
type: "intersection",
|
|
1144
|
-
members: allOfMembers,
|
|
1145
|
-
...
|
|
1093
|
+
members: [...mergeAdjacentObjects(allOfMembers.slice(0, syntheticStart)), ...mergeAdjacentObjects(allOfMembers.slice(syntheticStart))],
|
|
1094
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1146
1095
|
});
|
|
1147
1096
|
}
|
|
1148
1097
|
/**
|
|
1149
1098
|
* Converts a `oneOf` / `anyOf` schema into a `UnionSchemaNode`.
|
|
1150
|
-
*
|
|
1151
|
-
* Both keywords are treated identically — their members are concatenated into a single union.
|
|
1152
|
-
* When sibling `properties` are present alongside `oneOf`/`anyOf`, each union member is
|
|
1153
|
-
* individually intersected with the shared properties node to match the OAS pattern of
|
|
1154
|
-
* adding common fields next to a discriminated union.
|
|
1155
1099
|
*/
|
|
1156
|
-
function convertUnion({ schema, name, nullable, defaultValue,
|
|
1100
|
+
function convertUnion({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1101
|
+
function pickDiscriminatorPropertyNode(node, propertyName) {
|
|
1102
|
+
const discriminatorProperty = narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
|
|
1103
|
+
if (!discriminatorProperty) return null;
|
|
1104
|
+
return createSchema({
|
|
1105
|
+
type: "object",
|
|
1106
|
+
primitive: "object",
|
|
1107
|
+
properties: [discriminatorProperty]
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1157
1110
|
const unionMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
1158
1111
|
const unionBase = {
|
|
1159
|
-
...
|
|
1112
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1160
1113
|
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0
|
|
1161
1114
|
};
|
|
1162
|
-
|
|
1115
|
+
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1116
|
+
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1163
1117
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1164
|
-
|
|
1165
|
-
|
|
1118
|
+
return parseSchema({
|
|
1119
|
+
schema: discriminator ? Object.fromEntries(Object.entries(schemaWithoutUnion).filter(([key]) => key !== "discriminator")) : schemaWithoutUnion,
|
|
1120
|
+
name
|
|
1121
|
+
}, rawOptions);
|
|
1122
|
+
})() : void 0;
|
|
1123
|
+
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1124
|
+
const members = unionMembers.map((s) => {
|
|
1125
|
+
const ref = isReference(s) ? s.$ref : void 0;
|
|
1126
|
+
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref);
|
|
1127
|
+
const memberNode = parseSchema({ schema: s }, rawOptions);
|
|
1128
|
+
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1129
|
+
return createSchema({
|
|
1130
|
+
type: "intersection",
|
|
1131
|
+
members: [memberNode, (sharedPropertiesNode ? pickDiscriminatorPropertyNode(setDiscriminatorEnum({
|
|
1132
|
+
node: sharedPropertiesNode,
|
|
1133
|
+
propertyName: discriminator.propertyName,
|
|
1134
|
+
values: [discriminatorValue]
|
|
1135
|
+
}), discriminator.propertyName) : void 0) ?? createDiscriminantNode({
|
|
1136
|
+
propertyName: discriminator.propertyName,
|
|
1137
|
+
value: discriminatorValue
|
|
1138
|
+
})]
|
|
1139
|
+
});
|
|
1140
|
+
});
|
|
1141
|
+
const unionNode = createSchema({
|
|
1166
1142
|
type: "union",
|
|
1167
1143
|
...unionBase,
|
|
1168
|
-
members
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1144
|
+
members
|
|
1145
|
+
});
|
|
1146
|
+
if (!sharedPropertiesNode) return unionNode;
|
|
1147
|
+
return createSchema({
|
|
1148
|
+
type: "intersection",
|
|
1149
|
+
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1150
|
+
members: [unionNode, sharedPropertiesNode]
|
|
1172
1151
|
});
|
|
1173
1152
|
}
|
|
1174
1153
|
return createSchema({
|
|
1175
1154
|
type: "union",
|
|
1176
1155
|
...unionBase,
|
|
1177
|
-
members: unionMembers.map((s) =>
|
|
1156
|
+
members: simplifyUnion(unionMembers.map((s) => parseSchema({ schema: s }, rawOptions)))
|
|
1178
1157
|
});
|
|
1179
1158
|
}
|
|
1180
1159
|
/**
|
|
1181
|
-
* Converts an OAS 3.1 `const` schema into
|
|
1182
|
-
* `const: null` maps to a null scalar; any other value becomes a one-item enum so that generators
|
|
1183
|
-
* can produce a precise literal type.
|
|
1160
|
+
* Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
|
|
1184
1161
|
*/
|
|
1185
1162
|
function convertConst({ schema, name, nullable, defaultValue }) {
|
|
1186
1163
|
const constValue = schema.const;
|
|
@@ -1190,25 +1167,23 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1190
1167
|
name,
|
|
1191
1168
|
title: schema.title,
|
|
1192
1169
|
description: schema.description,
|
|
1193
|
-
deprecated: schema.deprecated
|
|
1194
|
-
nullable
|
|
1170
|
+
deprecated: schema.deprecated
|
|
1195
1171
|
});
|
|
1196
1172
|
return createSchema({
|
|
1197
1173
|
type: "enum",
|
|
1198
1174
|
primitive: getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string"),
|
|
1199
1175
|
enumValues: [constValue],
|
|
1200
|
-
...
|
|
1176
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1201
1177
|
});
|
|
1202
1178
|
}
|
|
1203
1179
|
/**
|
|
1204
|
-
*
|
|
1205
|
-
* Returns `
|
|
1206
|
-
* (i.e. `format: 'date-time'` with `dateType: false`).
|
|
1180
|
+
* Converts a format-annotated schema into a special-type `SchemaNode`.
|
|
1181
|
+
* Returns `null` when the format should fall through to string handling (`dateType: false`).
|
|
1207
1182
|
*/
|
|
1208
|
-
function convertFormat({ schema, name, nullable, defaultValue,
|
|
1209
|
-
const base =
|
|
1183
|
+
function convertFormat({ schema, name, nullable, defaultValue, options }) {
|
|
1184
|
+
const base = buildSchemaNode(schema, name, nullable, defaultValue);
|
|
1210
1185
|
if (schema.format === "int64") return createSchema({
|
|
1211
|
-
type:
|
|
1186
|
+
type: options.integerType === "bigint" ? "bigint" : "integer",
|
|
1212
1187
|
primitive: "integer",
|
|
1213
1188
|
...base,
|
|
1214
1189
|
min: schema.minimum,
|
|
@@ -1217,8 +1192,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1217
1192
|
exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0
|
|
1218
1193
|
});
|
|
1219
1194
|
if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
|
|
1220
|
-
const dateType = getDateType(
|
|
1221
|
-
if (!dateType) return
|
|
1195
|
+
const dateType = getDateType(options, schema.format);
|
|
1196
|
+
if (!dateType) return null;
|
|
1222
1197
|
if (dateType.type === "datetime") return createSchema({
|
|
1223
1198
|
...base,
|
|
1224
1199
|
primitive: "string",
|
|
@@ -1233,14 +1208,19 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1233
1208
|
representation: dateType.representation
|
|
1234
1209
|
});
|
|
1235
1210
|
}
|
|
1236
|
-
const specialType =
|
|
1237
|
-
if (!specialType) return
|
|
1211
|
+
const specialType = getSchemaType(schema.format);
|
|
1212
|
+
if (!specialType) return null;
|
|
1238
1213
|
const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
|
|
1239
1214
|
if (specialType === "number" || specialType === "integer" || specialType === "bigint") return createSchema({
|
|
1240
1215
|
...base,
|
|
1241
1216
|
primitive: specialPrimitive,
|
|
1242
1217
|
type: specialType
|
|
1243
1218
|
});
|
|
1219
|
+
if (specialType === "url") return createSchema({
|
|
1220
|
+
...base,
|
|
1221
|
+
primitive: "string",
|
|
1222
|
+
type: "url"
|
|
1223
|
+
});
|
|
1244
1224
|
return createSchema({
|
|
1245
1225
|
...base,
|
|
1246
1226
|
primitive: specialPrimitive,
|
|
@@ -1249,37 +1229,20 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1249
1229
|
}
|
|
1250
1230
|
/**
|
|
1251
1231
|
* Converts an `enum` schema into an `EnumSchemaNode`.
|
|
1252
|
-
*
|
|
1253
|
-
* Handles several edge cases:
|
|
1254
|
-
* - `{ type: 'array', enum }` (technically invalid OAS) — the enum is normalized into `items`.
|
|
1255
|
-
* - `null` in enum values (OAS 3.0 nullable enum convention) — stripped and reflected as `nullable`.
|
|
1256
|
-
* - `x-enumNames` / `x-enum-varnames` vendor extensions — produce named enum variants with explicit labels.
|
|
1257
|
-
* - Numeric and boolean enums require a const-map representation because most generators cannot
|
|
1258
|
-
* use string-enum syntax for non-string values.
|
|
1259
1232
|
*/
|
|
1260
|
-
function convertEnum({ schema, name, nullable, type,
|
|
1261
|
-
if (type === "array") {
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
enum: schema.enum
|
|
1266
|
-
};
|
|
1267
|
-
const { enum: _enum, ...schemaWithoutEnum } = schema;
|
|
1268
|
-
return convertSchema({
|
|
1269
|
-
schema: {
|
|
1270
|
-
...schemaWithoutEnum,
|
|
1271
|
-
items: normalizedItems
|
|
1272
|
-
},
|
|
1273
|
-
name
|
|
1274
|
-
}, options);
|
|
1275
|
-
}
|
|
1233
|
+
function convertEnum({ schema, name, nullable, type, rawOptions }) {
|
|
1234
|
+
if (type === "array") return parseSchema({
|
|
1235
|
+
schema: normalizeArrayEnum(schema),
|
|
1236
|
+
name
|
|
1237
|
+
}, rawOptions);
|
|
1276
1238
|
const nullInEnum = schema.enum.includes(null);
|
|
1277
1239
|
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1278
1240
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
1279
1241
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1242
|
+
const enumPrimitive = getPrimitiveType(type);
|
|
1280
1243
|
const enumBase = {
|
|
1281
1244
|
type: "enum",
|
|
1282
|
-
primitive:
|
|
1245
|
+
primitive: enumPrimitive,
|
|
1283
1246
|
name,
|
|
1284
1247
|
title: schema.title,
|
|
1285
1248
|
description: schema.description,
|
|
@@ -1291,138 +1254,115 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1291
1254
|
example: schema.example
|
|
1292
1255
|
};
|
|
1293
1256
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1294
|
-
if (extensionKey) {
|
|
1295
|
-
const
|
|
1296
|
-
const
|
|
1297
|
-
const enumType = getPrimitiveType(type) === "number" || getPrimitiveType(type) === "integer" ? "number" : getPrimitiveType(type) === "boolean" ? "boolean" : "string";
|
|
1257
|
+
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
1258
|
+
const enumPrimitiveType = enumPrimitive === "number" || enumPrimitive === "integer" ? "number" : enumPrimitive === "boolean" ? "boolean" : "string";
|
|
1259
|
+
const sourceValues = extensionKey ? [...new Set(schema[extensionKey])] : [...new Set(filteredValues)];
|
|
1298
1260
|
return createSchema({
|
|
1299
1261
|
...enumBase,
|
|
1300
|
-
|
|
1301
|
-
namedEnumValues:
|
|
1262
|
+
primitive: enumPrimitiveType,
|
|
1263
|
+
namedEnumValues: sourceValues.map((label, index) => ({
|
|
1302
1264
|
name: String(label),
|
|
1303
|
-
value: filteredValues[index] ?? label,
|
|
1304
|
-
|
|
1265
|
+
value: extensionKey ? filteredValues[index] ?? label : label,
|
|
1266
|
+
primitive: enumPrimitiveType
|
|
1305
1267
|
}))
|
|
1306
1268
|
});
|
|
1307
1269
|
}
|
|
1308
|
-
if (type === "number" || type === "integer") return createSchema({
|
|
1309
|
-
...enumBase,
|
|
1310
|
-
enumType: "number",
|
|
1311
|
-
namedEnumValues: [...new Set(filteredValues)].map((value) => ({
|
|
1312
|
-
name: String(value),
|
|
1313
|
-
value,
|
|
1314
|
-
format: "number"
|
|
1315
|
-
}))
|
|
1316
|
-
});
|
|
1317
|
-
if (type === "boolean") return createSchema({
|
|
1318
|
-
...enumBase,
|
|
1319
|
-
enumType: "boolean",
|
|
1320
|
-
namedEnumValues: [...new Set(filteredValues)].map((value) => ({
|
|
1321
|
-
name: String(value),
|
|
1322
|
-
value,
|
|
1323
|
-
format: "boolean"
|
|
1324
|
-
}))
|
|
1325
|
-
});
|
|
1326
1270
|
return createSchema({
|
|
1327
1271
|
...enumBase,
|
|
1328
1272
|
enumValues: [...new Set(filteredValues)]
|
|
1329
1273
|
});
|
|
1330
1274
|
}
|
|
1331
1275
|
/**
|
|
1332
|
-
* Converts an object-like schema
|
|
1333
|
-
* or `patternProperties`) into an `ObjectSchemaNode`.
|
|
1334
|
-
*
|
|
1335
|
-
* When a `discriminator` is present, the discriminator property's schema is replaced with an
|
|
1336
|
-
* enum of the mapping keys so generators can produce a precise literal-union type for it.
|
|
1337
|
-
*
|
|
1338
|
-
* Property optionality follows OAS semantics:
|
|
1339
|
-
* - required + not nullable → `required: true`
|
|
1340
|
-
* - not required + not nullable → `optional: true`
|
|
1341
|
-
* - not required + nullable → `nullish: true`
|
|
1276
|
+
* Converts an object-like schema into an `ObjectSchemaNode`.
|
|
1342
1277
|
*/
|
|
1343
|
-
function convertObject({ schema, name, nullable, defaultValue,
|
|
1344
|
-
const
|
|
1345
|
-
|
|
1346
|
-
const required = Array.isArray(resolvedSchema.required) ? resolvedSchema.required.includes(propName) : !!resolvedSchema.required;
|
|
1278
|
+
function convertObject({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1279
|
+
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1280
|
+
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1347
1281
|
const resolvedPropSchema = propSchema;
|
|
1348
1282
|
const propNullable = isNullable(resolvedPropSchema);
|
|
1283
|
+
let schemaNode = setEnumName(parseSchema({
|
|
1284
|
+
schema: resolvedPropSchema,
|
|
1285
|
+
name: childName(name, propName)
|
|
1286
|
+
}, rawOptions), name, propName, options.enumSuffix);
|
|
1287
|
+
const tupleNode = narrowSchema(schemaNode, "tuple");
|
|
1288
|
+
if (tupleNode?.items) {
|
|
1289
|
+
const namedItems = tupleNode.items.map((item) => setEnumName(item, name, propName, options.enumSuffix));
|
|
1290
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) schemaNode = {
|
|
1291
|
+
...tupleNode,
|
|
1292
|
+
items: namedItems
|
|
1293
|
+
};
|
|
1294
|
+
}
|
|
1349
1295
|
return createProperty({
|
|
1350
1296
|
name: propName,
|
|
1351
1297
|
schema: {
|
|
1352
|
-
...
|
|
1353
|
-
|
|
1354
|
-
name: name ? pascalCase([
|
|
1355
|
-
name,
|
|
1356
|
-
propName,
|
|
1357
|
-
mergedOptions.enumSuffix
|
|
1358
|
-
].filter(Boolean).join(" ")) : void 0
|
|
1359
|
-
}, options),
|
|
1360
|
-
nullable: propNullable || void 0,
|
|
1361
|
-
optional: !required && !propNullable ? true : void 0,
|
|
1362
|
-
nullish: !required && propNullable ? true : void 0
|
|
1298
|
+
...schemaNode,
|
|
1299
|
+
nullable: schemaNode.type === "null" ? void 0 : propNullable || void 0
|
|
1363
1300
|
},
|
|
1364
1301
|
required
|
|
1365
1302
|
});
|
|
1366
1303
|
}) : [];
|
|
1367
|
-
const additionalProperties =
|
|
1304
|
+
const additionalProperties = schema.additionalProperties;
|
|
1368
1305
|
let additionalPropertiesNode;
|
|
1369
1306
|
if (additionalProperties === true) additionalPropertiesNode = true;
|
|
1370
|
-
else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode =
|
|
1307
|
+
else if (additionalProperties && Object.keys(additionalProperties).length > 0) additionalPropertiesNode = parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1371
1308
|
else if (additionalProperties === false) additionalPropertiesNode = void 0;
|
|
1372
|
-
else if (additionalProperties) additionalPropertiesNode = createSchema({ type:
|
|
1373
|
-
const rawPatternProperties = "patternProperties" in
|
|
1374
|
-
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || Object.keys(patternSchema).length === 0 ? createSchema({ type:
|
|
1375
|
-
|
|
1309
|
+
else if (additionalProperties) additionalPropertiesNode = createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1310
|
+
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1311
|
+
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1312
|
+
const objectNode = createSchema({
|
|
1376
1313
|
type: "object",
|
|
1377
1314
|
primitive: "object",
|
|
1378
1315
|
properties,
|
|
1379
1316
|
additionalProperties: additionalPropertiesNode,
|
|
1380
1317
|
patternProperties,
|
|
1381
|
-
...
|
|
1318
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1382
1319
|
});
|
|
1320
|
+
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1321
|
+
const discPropName = schema.discriminator.propertyName;
|
|
1322
|
+
return setDiscriminatorEnum({
|
|
1323
|
+
node: objectNode,
|
|
1324
|
+
propertyName: discPropName,
|
|
1325
|
+
values: Object.keys(schema.discriminator.mapping),
|
|
1326
|
+
enumName: name ? enumPropName(name, discPropName, options.enumSuffix) : void 0
|
|
1327
|
+
});
|
|
1328
|
+
}
|
|
1329
|
+
return objectNode;
|
|
1383
1330
|
}
|
|
1384
1331
|
/**
|
|
1385
1332
|
* Converts an OAS 3.1 `prefixItems` tuple into a `TupleSchemaNode`.
|
|
1386
|
-
*
|
|
1387
|
-
* Each `prefixItems` element maps to a positional tuple slot. An optional `items` schema
|
|
1388
|
-
* after the prefix items is mapped to the rest parameter of the tuple.
|
|
1389
1333
|
*/
|
|
1390
|
-
function convertTuple({ schema, name, nullable, defaultValue,
|
|
1391
|
-
const rawSchema = schema;
|
|
1334
|
+
function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1392
1335
|
return createSchema({
|
|
1393
1336
|
type: "tuple",
|
|
1394
1337
|
primitive: "array",
|
|
1395
|
-
items:
|
|
1396
|
-
rest:
|
|
1338
|
+
items: (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions)),
|
|
1339
|
+
rest: schema.items ? parseSchema({ schema: schema.items }, rawOptions) : createSchema({ type: "any" }),
|
|
1397
1340
|
min: schema.minItems,
|
|
1398
1341
|
max: schema.maxItems,
|
|
1399
|
-
...
|
|
1342
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1400
1343
|
});
|
|
1401
1344
|
}
|
|
1402
1345
|
/**
|
|
1403
1346
|
* Converts a `type: 'array'` schema into an `ArraySchemaNode`.
|
|
1404
|
-
*
|
|
1405
|
-
* When the items schema is an inline enum, a name derived from the parent array's name and
|
|
1406
|
-
* `enumSuffix` is forwarded so generators can emit a named enum declaration.
|
|
1407
1347
|
*/
|
|
1408
|
-
function convertArray({ schema, name, nullable, defaultValue,
|
|
1409
|
-
const
|
|
1410
|
-
const itemName =
|
|
1348
|
+
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1349
|
+
const rawItems = schema.items;
|
|
1350
|
+
const itemName = rawItems?.enum?.length && name ? enumPropName(void 0, name, options.enumSuffix) : void 0;
|
|
1411
1351
|
return createSchema({
|
|
1412
1352
|
type: "array",
|
|
1413
1353
|
primitive: "array",
|
|
1414
|
-
items:
|
|
1415
|
-
schema:
|
|
1354
|
+
items: rawItems ? [parseSchema({
|
|
1355
|
+
schema: rawItems,
|
|
1416
1356
|
name: itemName
|
|
1417
|
-
},
|
|
1357
|
+
}, rawOptions)] : [],
|
|
1418
1358
|
min: schema.minItems,
|
|
1419
1359
|
max: schema.maxItems,
|
|
1420
1360
|
unique: schema.uniqueItems ?? void 0,
|
|
1421
|
-
...
|
|
1361
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1422
1362
|
});
|
|
1423
1363
|
}
|
|
1424
1364
|
/**
|
|
1425
|
-
* Converts a `type: 'string'` schema
|
|
1365
|
+
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
1426
1366
|
*/
|
|
1427
1367
|
function convertString({ schema, name, nullable, defaultValue }) {
|
|
1428
1368
|
return createSchema({
|
|
@@ -1431,11 +1371,11 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1431
1371
|
min: schema.minLength,
|
|
1432
1372
|
max: schema.maxLength,
|
|
1433
1373
|
pattern: schema.pattern,
|
|
1434
|
-
...
|
|
1374
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1435
1375
|
});
|
|
1436
1376
|
}
|
|
1437
1377
|
/**
|
|
1438
|
-
* Converts a `type: 'number'` or `type: 'integer'` schema
|
|
1378
|
+
* Converts a `type: 'number'` or `type: 'integer'` schema.
|
|
1439
1379
|
*/
|
|
1440
1380
|
function convertNumeric({ schema, name, nullable, defaultValue }, type) {
|
|
1441
1381
|
return createSchema({
|
|
@@ -1445,21 +1385,21 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1445
1385
|
max: schema.maximum,
|
|
1446
1386
|
exclusiveMinimum: typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0,
|
|
1447
1387
|
exclusiveMaximum: typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0,
|
|
1448
|
-
...
|
|
1388
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1449
1389
|
});
|
|
1450
1390
|
}
|
|
1451
1391
|
/**
|
|
1452
|
-
* Converts a `type: 'boolean'` schema
|
|
1392
|
+
* Converts a `type: 'boolean'` schema.
|
|
1453
1393
|
*/
|
|
1454
1394
|
function convertBoolean({ schema, name, nullable, defaultValue }) {
|
|
1455
1395
|
return createSchema({
|
|
1456
1396
|
type: "boolean",
|
|
1457
1397
|
primitive: "boolean",
|
|
1458
|
-
...
|
|
1398
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1459
1399
|
});
|
|
1460
1400
|
}
|
|
1461
1401
|
/**
|
|
1462
|
-
* Converts an explicit `type: 'null'`
|
|
1402
|
+
* Converts an explicit `type: 'null'` schema.
|
|
1463
1403
|
*/
|
|
1464
1404
|
function convertNull({ schema, name, nullable }) {
|
|
1465
1405
|
return createSchema({
|
|
@@ -1473,31 +1413,22 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1473
1413
|
});
|
|
1474
1414
|
}
|
|
1475
1415
|
/**
|
|
1476
|
-
* Central dispatcher
|
|
1416
|
+
* Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1477
1417
|
*
|
|
1478
|
-
* Dispatch order (first match wins):
|
|
1479
|
-
*
|
|
1480
|
-
*
|
|
1481
|
-
* 3. `oneOf` / `anyOf` union
|
|
1482
|
-
* 4. `const` literal (OAS 3.1)
|
|
1483
|
-
* 5. `format`-based special type (date/time, uuid, blob, …)
|
|
1484
|
-
* 6. OAS 3.1 `contentMediaType: 'application/octet-stream'` blob
|
|
1485
|
-
* 7. OAS 3.1 multi-type array → union or fallthrough
|
|
1486
|
-
* 8. Constraint-inferred type (minLength/maxLength → string; minimum/maximum → number)
|
|
1487
|
-
* 9. `enum` values
|
|
1488
|
-
* 10. Object / array / tuple / scalar by `type`
|
|
1489
|
-
* 11. Empty schema fallback (`emptySchemaType` option)
|
|
1418
|
+
* Dispatch order (first match wins): `$ref` → `allOf` → `oneOf`/`anyOf` → `const` → `format`
|
|
1419
|
+
* → octet-stream blob → multi-type array → constraint-inferred type → `enum` → object/array/tuple/scalar
|
|
1420
|
+
* → empty-schema fallback (`emptySchemaType` option).
|
|
1490
1421
|
*/
|
|
1491
|
-
function
|
|
1492
|
-
const
|
|
1493
|
-
...
|
|
1494
|
-
...
|
|
1422
|
+
function parseSchema({ schema, name }, rawOptions) {
|
|
1423
|
+
const options = {
|
|
1424
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
1425
|
+
...rawOptions
|
|
1495
1426
|
};
|
|
1496
1427
|
const flattenedSchema = flattenSchema(schema);
|
|
1497
|
-
if (flattenedSchema && flattenedSchema !== schema) return
|
|
1428
|
+
if (flattenedSchema && flattenedSchema !== schema) return parseSchema({
|
|
1498
1429
|
schema: flattenedSchema,
|
|
1499
1430
|
name
|
|
1500
|
-
},
|
|
1431
|
+
}, rawOptions);
|
|
1501
1432
|
const nullable = isNullable(schema) || void 0;
|
|
1502
1433
|
const defaultValue = schema.default === null && nullable ? void 0 : schema.default;
|
|
1503
1434
|
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
@@ -1507,8 +1438,8 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1507
1438
|
nullable,
|
|
1508
1439
|
defaultValue,
|
|
1509
1440
|
type,
|
|
1510
|
-
|
|
1511
|
-
|
|
1441
|
+
rawOptions,
|
|
1442
|
+
options
|
|
1512
1443
|
};
|
|
1513
1444
|
if (isReference(schema)) return convertRef(ctx);
|
|
1514
1445
|
if (schema.allOf?.length) return convertAllOf(ctx);
|
|
@@ -1521,18 +1452,21 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1521
1452
|
if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return createSchema({
|
|
1522
1453
|
type: "blob",
|
|
1523
1454
|
primitive: "string",
|
|
1524
|
-
...
|
|
1455
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1525
1456
|
});
|
|
1526
1457
|
if (Array.isArray(schema.type) && schema.type.length > 1) {
|
|
1527
1458
|
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
1528
1459
|
const arrayNullable = schema.type.includes("null") || nullable || void 0;
|
|
1529
1460
|
if (nonNullTypes.length > 1) return createSchema({
|
|
1530
1461
|
type: "union",
|
|
1531
|
-
members: nonNullTypes.map((t) =>
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1462
|
+
members: nonNullTypes.map((t) => parseSchema({
|
|
1463
|
+
schema: {
|
|
1464
|
+
...schema,
|
|
1465
|
+
type: t
|
|
1466
|
+
},
|
|
1467
|
+
name
|
|
1468
|
+
}, rawOptions)),
|
|
1469
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1536
1470
|
});
|
|
1537
1471
|
}
|
|
1538
1472
|
if (!type) {
|
|
@@ -1549,52 +1483,62 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1549
1483
|
if (type === "boolean") return convertBoolean(ctx);
|
|
1550
1484
|
if (type === "null") return convertNull(ctx);
|
|
1551
1485
|
return createSchema({
|
|
1552
|
-
type:
|
|
1486
|
+
type: typeOptionMap.get(options.emptySchemaType),
|
|
1553
1487
|
name,
|
|
1554
1488
|
title: schema.title,
|
|
1555
1489
|
description: schema.description
|
|
1556
1490
|
});
|
|
1557
1491
|
}
|
|
1558
1492
|
/**
|
|
1559
|
-
* Converts a
|
|
1560
|
-
* When the parameter has no `schema` or its schema is a `$ref`, falls back to `unknownType`.
|
|
1493
|
+
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
1561
1494
|
*/
|
|
1562
1495
|
function parseParameter(options, param) {
|
|
1563
|
-
const
|
|
1496
|
+
const required = param["required"] ?? false;
|
|
1497
|
+
const schema = param["schema"] ? parseSchema({ schema: param["schema"] }, options) : createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1564
1498
|
return createParameter({
|
|
1565
1499
|
name: param["name"],
|
|
1566
1500
|
in: param["in"],
|
|
1567
|
-
schema
|
|
1568
|
-
|
|
1501
|
+
schema: {
|
|
1502
|
+
...schema,
|
|
1503
|
+
description: param["description"] ?? schema.description
|
|
1504
|
+
},
|
|
1505
|
+
required
|
|
1569
1506
|
});
|
|
1570
1507
|
}
|
|
1571
1508
|
/**
|
|
1572
|
-
* Converts an OAS `Operation` into an `OperationNode
|
|
1573
|
-
* request body, and all response codes into their AST node equivalents.
|
|
1509
|
+
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1574
1510
|
*/
|
|
1575
|
-
function parseOperation(options,
|
|
1576
|
-
const parameters =
|
|
1577
|
-
|
|
1578
|
-
});
|
|
1579
|
-
const
|
|
1580
|
-
const
|
|
1511
|
+
function parseOperation(options, operation) {
|
|
1512
|
+
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
|
|
1513
|
+
const requestBodySchema = getRequestSchema(document, operation, { contentType: ctx.contentType });
|
|
1514
|
+
const requestBodySchemaNode = requestBodySchema ? parseSchema({ schema: requestBodySchema }, options) : void 0;
|
|
1515
|
+
const requestBodyDescription = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.description : void 0;
|
|
1516
|
+
const requestBodyKeysToOmit = requestBodySchema?.properties ? Object.entries(requestBodySchema.properties).filter(([, prop]) => !isReference(prop) && prop.readOnly).map(([key]) => key) : void 0;
|
|
1517
|
+
const requestBody = requestBodySchemaNode ? {
|
|
1518
|
+
description: requestBodyDescription,
|
|
1519
|
+
schema: requestBodySchemaNode,
|
|
1520
|
+
keysToOmit: requestBodyKeysToOmit?.length ? requestBodyKeysToOmit : void 0
|
|
1521
|
+
} : void 0;
|
|
1581
1522
|
const responses = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1582
1523
|
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1583
|
-
const responseSchema =
|
|
1584
|
-
const schema = responseSchema && Object.keys(responseSchema).length > 0 ?
|
|
1524
|
+
const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
|
|
1525
|
+
const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : createSchema({ type: typeOptionMap.get(options.emptySchemaType) });
|
|
1585
1526
|
const description = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : void 0;
|
|
1586
1527
|
const rawContent = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.content : void 0;
|
|
1528
|
+
const mediaType = rawContent ? getMediaType(Object.keys(rawContent)[0] ?? "") : getMediaType(operation.contentType ?? "");
|
|
1529
|
+
const keysToOmit = responseSchema?.properties ? Object.entries(responseSchema.properties).filter(([, prop]) => !isReference(prop) && prop.writeOnly).map(([key]) => key) : void 0;
|
|
1587
1530
|
return createResponse({
|
|
1588
1531
|
statusCode,
|
|
1589
1532
|
description,
|
|
1590
1533
|
schema,
|
|
1591
|
-
mediaType
|
|
1534
|
+
mediaType,
|
|
1535
|
+
keysToOmit: keysToOmit?.length ? keysToOmit : void 0
|
|
1592
1536
|
});
|
|
1593
1537
|
});
|
|
1594
1538
|
return createOperation({
|
|
1595
1539
|
operationId: operation.getOperationId(),
|
|
1596
1540
|
method: operation.method.toUpperCase(),
|
|
1597
|
-
path: operation.path,
|
|
1541
|
+
path: new URLPath(operation.path).URL,
|
|
1598
1542
|
tags: operation.getTags().map((tag) => tag.name),
|
|
1599
1543
|
summary: operation.getSummary() || void 0,
|
|
1600
1544
|
description: operation.getDescription() || void 0,
|
|
@@ -1604,165 +1548,140 @@ function createOasParser(oas, { contentType, collisionDetection } = {}) {
|
|
|
1604
1548
|
responses
|
|
1605
1549
|
});
|
|
1606
1550
|
}
|
|
1607
|
-
/**
|
|
1608
|
-
* Converts an OpenAPI/Swagger spec (wrapped in a Kubb `Oas` instance) into
|
|
1609
|
-
* a `RootNode` — the top-level node of the `@kubb/ast` tree.
|
|
1610
|
-
*/
|
|
1611
|
-
function parse(options) {
|
|
1612
|
-
const mergedOptions = {
|
|
1613
|
-
...DEFAULT_OPTIONS,
|
|
1614
|
-
...options
|
|
1615
|
-
};
|
|
1616
|
-
const schemas = Object.entries(schemaObjects).map(([name, schemaObject]) => convertSchema({
|
|
1617
|
-
schema: schemaObject,
|
|
1618
|
-
name
|
|
1619
|
-
}, mergedOptions));
|
|
1620
|
-
const paths = oas.getPaths();
|
|
1621
|
-
return createRoot({
|
|
1622
|
-
schemas,
|
|
1623
|
-
operations: Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? parseOperation(mergedOptions, oas, operation) : null).filter((op) => op !== null))
|
|
1624
|
-
});
|
|
1625
|
-
}
|
|
1626
|
-
/**
|
|
1627
|
-
* Walks a `SchemaNode` tree and resolves all `ref` node names through the provided callbacks.
|
|
1628
|
-
*
|
|
1629
|
-
* `resolveName` handles all schema types; `resolveEnumName` (when provided) takes precedence
|
|
1630
|
-
* for `enum` nodes, enabling a separate naming strategy for enums (e.g. different suffix).
|
|
1631
|
-
*
|
|
1632
|
-
* Collision-resolved names (from `nameMapping`) are applied before user-supplied resolvers.
|
|
1633
|
-
*/
|
|
1634
|
-
function resolveRefs(node, resolveName, resolveEnumName) {
|
|
1635
|
-
return transform(node, { schema(schemaNode) {
|
|
1636
|
-
const schemaRef = narrowSchema(schemaNode, schemaTypes.ref);
|
|
1637
|
-
if (schemaRef && (schemaRef.ref || schemaRef.name)) {
|
|
1638
|
-
const rawRef = schemaRef.ref ?? schemaRef.name;
|
|
1639
|
-
const resolved = resolveName(nameMapping.get(rawRef) ?? rawRef);
|
|
1640
|
-
if (resolved) return {
|
|
1641
|
-
...schemaNode,
|
|
1642
|
-
name: resolved
|
|
1643
|
-
};
|
|
1644
|
-
}
|
|
1645
|
-
if (schemaNode.type === "enum" && schemaNode.name) {
|
|
1646
|
-
const resolved = (resolveEnumName ?? resolveName)(schemaNode.name);
|
|
1647
|
-
if (resolved) return {
|
|
1648
|
-
...schemaNode,
|
|
1649
|
-
name: resolved
|
|
1650
|
-
};
|
|
1651
|
-
}
|
|
1652
|
-
} });
|
|
1653
|
-
}
|
|
1654
|
-
/**
|
|
1655
|
-
* Collects all `KubbFile.Import` descriptors needed by a `SchemaNode` tree.
|
|
1656
|
-
*
|
|
1657
|
-
* Walks the tree looking for `ref` nodes, verifies each `$ref` is resolvable in the spec,
|
|
1658
|
-
* applies collision-resolved names from `nameMapping`, and calls `resolve` to obtain the
|
|
1659
|
-
* import path and name. Returns an empty array for refs that cannot be resolved.
|
|
1660
|
-
*/
|
|
1661
|
-
function getImports(node, resolve) {
|
|
1662
|
-
return collect(node, { schema(schemaNode) {
|
|
1663
|
-
if (schemaNode.type !== "ref" || !schemaNode.ref) return;
|
|
1664
|
-
if (!oas.get(schemaNode.ref)) return;
|
|
1665
|
-
const rawName = extractRefName(schemaNode.ref);
|
|
1666
|
-
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
1667
|
-
if (!result) return;
|
|
1668
|
-
return {
|
|
1669
|
-
name: [result.name],
|
|
1670
|
-
path: result.path
|
|
1671
|
-
};
|
|
1672
|
-
} });
|
|
1673
|
-
}
|
|
1674
1551
|
return {
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1552
|
+
parseSchema,
|
|
1553
|
+
parseOperation,
|
|
1554
|
+
parseParameter
|
|
1555
|
+
};
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Converts the entire OpenAPI spec into a `RootNode` (the top-level `@kubb/ast` tree).
|
|
1559
|
+
*
|
|
1560
|
+
* This is the main entry point: `OpenAPI / Swagger → Kubb AST`.
|
|
1561
|
+
* No code is generated here — the resulting tree is spec-agnostic and consumed by
|
|
1562
|
+
* downstream plugins (`plugin-ts`, `plugin-zod`, …).
|
|
1563
|
+
*
|
|
1564
|
+
* @example
|
|
1565
|
+
* ```ts
|
|
1566
|
+
* const document = await parseFromConfig(config)
|
|
1567
|
+
* const root = parseOas(document, { dateType: 'date', contentType: 'application/json' })
|
|
1568
|
+
* ```
|
|
1569
|
+
*/
|
|
1570
|
+
function parseOas(document, options = {}) {
|
|
1571
|
+
const { contentType, ...parserOptions } = options;
|
|
1572
|
+
const mergedOptions = {
|
|
1573
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
1574
|
+
...parserOptions
|
|
1575
|
+
};
|
|
1576
|
+
const { schemas: schemaObjects, nameMapping } = getSchemas(document, { contentType });
|
|
1577
|
+
const { parseSchema: _parseSchema, parseOperation: _parseOperation } = createSchemaParser({
|
|
1578
|
+
document,
|
|
1579
|
+
contentType
|
|
1580
|
+
});
|
|
1581
|
+
const schemas = Object.entries(schemaObjects).map(([name, schema]) => _parseSchema({
|
|
1582
|
+
schema,
|
|
1583
|
+
name
|
|
1584
|
+
}, mergedOptions));
|
|
1585
|
+
const paths = new BaseOas(document).getPaths();
|
|
1586
|
+
return {
|
|
1587
|
+
root: createRoot({
|
|
1588
|
+
schemas,
|
|
1589
|
+
operations: Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null))
|
|
1590
|
+
}),
|
|
1591
|
+
nameMapping
|
|
1679
1592
|
};
|
|
1680
1593
|
}
|
|
1681
1594
|
//#endregion
|
|
1682
1595
|
//#region src/adapter.ts
|
|
1596
|
+
/**
|
|
1597
|
+
* Stable string identifier for the OAS adapter used in Kubb's adapter registry.
|
|
1598
|
+
*/
|
|
1683
1599
|
const adapterOasName = "oas";
|
|
1684
1600
|
/**
|
|
1685
|
-
* Creates
|
|
1601
|
+
* Creates the default OpenAPI / Swagger adapter for Kubb.
|
|
1686
1602
|
*
|
|
1687
|
-
*
|
|
1688
|
-
*
|
|
1603
|
+
* Parses the spec, optionally validates it, resolves the base URL, and converts
|
|
1604
|
+
* everything into a `RootNode` that downstream plugins consume.
|
|
1689
1605
|
*
|
|
1690
1606
|
* @example
|
|
1691
1607
|
* ```ts
|
|
1692
1608
|
* import { defineConfig } from '@kubb/core'
|
|
1693
1609
|
* import { adapterOas } from '@kubb/adapter-oas'
|
|
1610
|
+
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1694
1611
|
*
|
|
1695
1612
|
* export default defineConfig({
|
|
1696
|
-
* adapter: adapterOas({
|
|
1697
|
-
* input:
|
|
1698
|
-
* plugins: [pluginTs()
|
|
1613
|
+
* adapter: adapterOas({ dateType: 'date', serverIndex: 0 }),
|
|
1614
|
+
* input: { path: './openapi.yaml' },
|
|
1615
|
+
* plugins: [pluginTs()],
|
|
1699
1616
|
* })
|
|
1700
1617
|
* ```
|
|
1701
1618
|
*/
|
|
1702
|
-
const adapterOas =
|
|
1703
|
-
const { validate = true,
|
|
1619
|
+
const adapterOas = createAdapter((options) => {
|
|
1620
|
+
const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", dateType = DEFAULT_PARSER_OPTIONS.dateType, integerType = DEFAULT_PARSER_OPTIONS.integerType, unknownType = DEFAULT_PARSER_OPTIONS.unknownType, enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix, emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType } = options;
|
|
1621
|
+
let nameMapping = /* @__PURE__ */ new Map();
|
|
1622
|
+
let parsedDocument;
|
|
1704
1623
|
return {
|
|
1705
1624
|
name: "oas",
|
|
1706
|
-
options
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
contentType,
|
|
1710
|
-
serverIndex,
|
|
1711
|
-
serverVariables,
|
|
1712
|
-
discriminator,
|
|
1713
|
-
collisionDetection,
|
|
1714
|
-
dateType,
|
|
1715
|
-
integerType,
|
|
1716
|
-
unknownType,
|
|
1717
|
-
emptySchemaType
|
|
1718
|
-
},
|
|
1719
|
-
async parse(source) {
|
|
1720
|
-
const oas = await parseFromConfig(sourceToFakeConfig(source), oasClass);
|
|
1721
|
-
oas.setOptions({
|
|
1625
|
+
get options() {
|
|
1626
|
+
return {
|
|
1627
|
+
validate,
|
|
1722
1628
|
contentType,
|
|
1629
|
+
serverIndex,
|
|
1630
|
+
serverVariables,
|
|
1723
1631
|
discriminator,
|
|
1724
|
-
|
|
1632
|
+
dateType,
|
|
1633
|
+
integerType,
|
|
1634
|
+
unknownType,
|
|
1635
|
+
emptySchemaType,
|
|
1636
|
+
enumSuffix,
|
|
1637
|
+
nameMapping
|
|
1638
|
+
};
|
|
1639
|
+
},
|
|
1640
|
+
get document() {
|
|
1641
|
+
return parsedDocument;
|
|
1642
|
+
},
|
|
1643
|
+
getImports(node, resolve) {
|
|
1644
|
+
return collectImports({
|
|
1645
|
+
node,
|
|
1646
|
+
nameMapping,
|
|
1647
|
+
resolve: (schemaName) => {
|
|
1648
|
+
const result = resolve(schemaName);
|
|
1649
|
+
if (!result) return;
|
|
1650
|
+
return {
|
|
1651
|
+
name: [result.name],
|
|
1652
|
+
path: result.path
|
|
1653
|
+
};
|
|
1654
|
+
}
|
|
1725
1655
|
});
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1656
|
+
},
|
|
1657
|
+
async parse(source) {
|
|
1658
|
+
const document = await parseFromConfig(source);
|
|
1659
|
+
if (validate) await validateDocument(document);
|
|
1660
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
1730
1661
|
const baseURL = server?.url ? resolveServerUrl(server, serverVariables) : void 0;
|
|
1662
|
+
const { root: parsedRoot, nameMapping: parsedNameMapping } = parseOas(document, {
|
|
1663
|
+
contentType,
|
|
1664
|
+
dateType,
|
|
1665
|
+
integerType,
|
|
1666
|
+
unknownType,
|
|
1667
|
+
emptySchemaType,
|
|
1668
|
+
enumSuffix
|
|
1669
|
+
});
|
|
1670
|
+
const root = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
|
|
1671
|
+
nameMapping = parsedNameMapping;
|
|
1672
|
+
parsedDocument = document;
|
|
1731
1673
|
return createRoot({
|
|
1732
|
-
...
|
|
1733
|
-
contentType,
|
|
1734
|
-
collisionDetection
|
|
1735
|
-
}).parse({
|
|
1736
|
-
dateType,
|
|
1737
|
-
integerType,
|
|
1738
|
-
unknownType,
|
|
1739
|
-
emptySchemaType
|
|
1740
|
-
}),
|
|
1674
|
+
...root,
|
|
1741
1675
|
meta: {
|
|
1742
|
-
title:
|
|
1743
|
-
|
|
1676
|
+
title: document.info?.title,
|
|
1677
|
+
description: document.info?.description,
|
|
1678
|
+
version: document.info?.version,
|
|
1744
1679
|
baseURL
|
|
1745
1680
|
}
|
|
1746
1681
|
});
|
|
1747
1682
|
}
|
|
1748
1683
|
};
|
|
1749
1684
|
});
|
|
1750
|
-
function sourceToFakeConfig(source) {
|
|
1751
|
-
switch (source.type) {
|
|
1752
|
-
case "path": return {
|
|
1753
|
-
root: path.dirname(source.path),
|
|
1754
|
-
input: { path: source.path }
|
|
1755
|
-
};
|
|
1756
|
-
case "data": return {
|
|
1757
|
-
root: process.cwd(),
|
|
1758
|
-
input: { data: source.data }
|
|
1759
|
-
};
|
|
1760
|
-
case "paths": return {
|
|
1761
|
-
root: source.paths[0] ? path.dirname(source.paths[0]) : process.cwd(),
|
|
1762
|
-
input: source.paths.map((p) => ({ path: p }))
|
|
1763
|
-
};
|
|
1764
|
-
}
|
|
1765
|
-
}
|
|
1766
1685
|
//#endregion
|
|
1767
1686
|
export { adapterOas, adapterOasName };
|
|
1768
1687
|
|