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