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