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