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