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