@kubb/adapter-oas 5.0.0-beta.7 → 5.0.0-beta.71
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/LICENSE +17 -10
- package/README.md +26 -24
- package/dist/index.cjs +1461 -716
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +840 -77
- package/dist/index.js +1464 -714
- package/dist/index.js.map +1 -1
- package/package.json +13 -10
- package/extension.yaml +0 -344
- package/src/adapter.ts +0 -130
- package/src/constants.ts +0 -122
- package/src/discriminator.ts +0 -108
- package/src/factory.ts +0 -165
- package/src/guards.ts +0 -68
- package/src/index.ts +0 -18
- package/src/parser.ts +0 -1002
- package/src/refs.ts +0 -59
- package/src/resolvers.ts +0 -544
- package/src/types.ts +0 -206
- /package/dist/{chunk--u3MIqq1.js → rolldown-runtime-C0LytTxp.js} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -24,25 +24,23 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
let _kubb_core = require("@kubb/core");
|
|
25
25
|
let node_path = require("node:path");
|
|
26
26
|
node_path = __toESM(node_path, 1);
|
|
27
|
-
let
|
|
28
|
-
let
|
|
29
|
-
|
|
30
|
-
let
|
|
31
|
-
|
|
32
|
-
let
|
|
33
|
-
|
|
34
|
-
let
|
|
35
|
-
let oas_utils = require("oas/utils");
|
|
27
|
+
let node_fs_promises = require("node:fs/promises");
|
|
28
|
+
let _readme_openapi_parser = require("@readme/openapi-parser");
|
|
29
|
+
let _scalar_openapi_upgrader = require("@scalar/openapi-upgrader");
|
|
30
|
+
let yaml = require("yaml");
|
|
31
|
+
let api_ref_bundler = require("api-ref-bundler");
|
|
32
|
+
let _kubb_ast_macros = require("@kubb/ast/macros");
|
|
33
|
+
let _kubb_ast_utils = require("@kubb/ast/utils");
|
|
34
|
+
let _kubb_ast = require("@kubb/ast");
|
|
36
35
|
//#region src/constants.ts
|
|
37
36
|
/**
|
|
38
37
|
* Default parser options applied when no explicit options are provided.
|
|
39
38
|
*
|
|
40
39
|
* @example
|
|
41
40
|
* ```ts
|
|
42
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
41
|
+
* import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
|
|
43
42
|
*
|
|
44
|
-
* const
|
|
45
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
43
|
+
* const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
46
44
|
* ```
|
|
47
45
|
*/
|
|
48
46
|
const DEFAULT_PARSER_OPTIONS = {
|
|
@@ -64,17 +62,19 @@ const DEFAULT_PARSER_OPTIONS = {
|
|
|
64
62
|
*/
|
|
65
63
|
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
66
64
|
/**
|
|
67
|
-
*
|
|
65
|
+
* HTTP methods that count as operations on an OpenAPI path item. Other keys
|
|
66
|
+
* (`parameters`, `summary`, `$ref`, vendor extensions) are skipped when iterating operations.
|
|
68
67
|
*/
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
68
|
+
const SUPPORTED_METHODS = new Set([
|
|
69
|
+
"get",
|
|
70
|
+
"put",
|
|
71
|
+
"post",
|
|
72
|
+
"delete",
|
|
73
|
+
"options",
|
|
74
|
+
"head",
|
|
75
|
+
"patch",
|
|
76
|
+
"trace"
|
|
77
|
+
]);
|
|
78
78
|
/**
|
|
79
79
|
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
80
80
|
*
|
|
@@ -99,12 +99,24 @@ const structuralKeys = new Set([
|
|
|
99
99
|
"not"
|
|
100
100
|
]);
|
|
101
101
|
/**
|
|
102
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
103
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
104
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
105
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
106
|
+
*/
|
|
107
|
+
const specialCasedFormats = new Set([
|
|
108
|
+
"int64",
|
|
109
|
+
"date-time",
|
|
110
|
+
"date",
|
|
111
|
+
"time"
|
|
112
|
+
]);
|
|
113
|
+
/**
|
|
102
114
|
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
103
115
|
*
|
|
104
116
|
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
105
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
106
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types
|
|
107
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
117
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
118
|
+
* separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
119
|
+
* and `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
108
120
|
*
|
|
109
121
|
* @example
|
|
110
122
|
* ```ts
|
|
@@ -143,96 +155,6 @@ const formatMap = {
|
|
|
143
155
|
* ```
|
|
144
156
|
*/
|
|
145
157
|
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
146
|
-
/**
|
|
147
|
-
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
148
|
-
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
149
|
-
*/
|
|
150
|
-
const typeOptionMap = new Map([
|
|
151
|
-
["any", _kubb_core.ast.schemaTypes.any],
|
|
152
|
-
["unknown", _kubb_core.ast.schemaTypes.unknown],
|
|
153
|
-
["void", _kubb_core.ast.schemaTypes.void]
|
|
154
|
-
]);
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region src/discriminator.ts
|
|
157
|
-
/**
|
|
158
|
-
* Injects discriminator enum values into child schemas so they know which value identifies them.
|
|
159
|
-
*
|
|
160
|
-
* Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
|
|
161
|
-
* enum value each union member is mapped to, then adds (or replaces) that property on the matching
|
|
162
|
-
* child object schema.
|
|
163
|
-
*
|
|
164
|
-
* Returns a new `InputNode` — the original is never mutated.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* const { root } = parseOas(document, options)
|
|
169
|
-
* const next = applyDiscriminatorInheritance(root)
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
function applyDiscriminatorInheritance(root) {
|
|
173
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
174
|
-
for (const schema of root.schemas) {
|
|
175
|
-
let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
|
|
176
|
-
if (!unionNode) {
|
|
177
|
-
const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
|
|
178
|
-
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
179
|
-
const u = _kubb_core.ast.narrowSchema(m, "union");
|
|
180
|
-
if (u) {
|
|
181
|
-
unionNode = u;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
187
|
-
const { discriminatorPropertyName, members } = unionNode;
|
|
188
|
-
for (const member of members) {
|
|
189
|
-
const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
|
|
190
|
-
if (!intersectionNode?.members) continue;
|
|
191
|
-
let refNode;
|
|
192
|
-
let objNode;
|
|
193
|
-
for (const m of intersectionNode.members) {
|
|
194
|
-
refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
|
|
195
|
-
objNode ??= _kubb_core.ast.narrowSchema(m, "object");
|
|
196
|
-
}
|
|
197
|
-
if (!refNode?.name || !objNode) continue;
|
|
198
|
-
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
199
|
-
const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : void 0;
|
|
200
|
-
if (!enumNode?.enumValues?.length) continue;
|
|
201
|
-
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
202
|
-
if (!enumValues.length) continue;
|
|
203
|
-
const existing = childMap.get(refNode.name);
|
|
204
|
-
if (existing) existing.enumValues.push(...enumValues);
|
|
205
|
-
else childMap.set(refNode.name, {
|
|
206
|
-
propertyName: discriminatorPropertyName,
|
|
207
|
-
enumValues: [...enumValues]
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (childMap.size === 0) return root;
|
|
212
|
-
return _kubb_core.ast.transform(root, { schema(node, { parent }) {
|
|
213
|
-
if (parent?.kind !== "Input" || !node.name) return;
|
|
214
|
-
const entry = childMap.get(node.name);
|
|
215
|
-
if (!entry) return;
|
|
216
|
-
const objectNode = _kubb_core.ast.narrowSchema(node, "object");
|
|
217
|
-
if (!objectNode) return;
|
|
218
|
-
const { propertyName, enumValues } = entry;
|
|
219
|
-
const enumSchema = _kubb_core.ast.createSchema({
|
|
220
|
-
type: "enum",
|
|
221
|
-
enumValues
|
|
222
|
-
});
|
|
223
|
-
const newProp = _kubb_core.ast.createProperty({
|
|
224
|
-
name: propertyName,
|
|
225
|
-
required: true,
|
|
226
|
-
schema: enumSchema
|
|
227
|
-
});
|
|
228
|
-
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
229
|
-
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
230
|
-
return {
|
|
231
|
-
...objectNode,
|
|
232
|
-
properties: newProperties
|
|
233
|
-
};
|
|
234
|
-
} });
|
|
235
|
-
}
|
|
236
158
|
//#endregion
|
|
237
159
|
//#region ../../internals/utils/src/casing.ts
|
|
238
160
|
/**
|
|
@@ -245,91 +167,132 @@ function applyDiscriminatorInheritance(root) {
|
|
|
245
167
|
function toCamelOrPascal(text, pascal) {
|
|
246
168
|
return text.trim().replace(/([a-z\d])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/(\d)([a-z])/g, "$1 $2").split(/[\s\-_./\\:]+/).filter(Boolean).map((word, i) => {
|
|
247
169
|
if (word.length > 1 && word === word.toUpperCase()) return word;
|
|
248
|
-
|
|
249
|
-
return word.charAt(0).toUpperCase() + word.slice(1);
|
|
170
|
+
return (i === 0 && !pascal ? word.charAt(0).toLowerCase() : word.charAt(0).toUpperCase()) + word.slice(1);
|
|
250
171
|
}).join("").replace(/[^a-zA-Z0-9]/g, "");
|
|
251
172
|
}
|
|
252
173
|
/**
|
|
253
|
-
*
|
|
254
|
-
* The last segment receives `isLast = true`, all earlier segments receive `false`.
|
|
255
|
-
* Segments are joined with `/` to form a file path.
|
|
174
|
+
* Converts `text` to camelCase.
|
|
256
175
|
*
|
|
257
|
-
*
|
|
258
|
-
*
|
|
176
|
+
* @example Word boundaries
|
|
177
|
+
* `camelCase('hello-world') // 'helloWorld'`
|
|
259
178
|
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
* and `'..'` transforms to an empty string). Without this filter the join would produce
|
|
263
|
-
* a leading `/`, which `path.resolve` would interpret as an absolute path, allowing
|
|
264
|
-
* generated files to escape the configured output directory.
|
|
179
|
+
* @example With a prefix
|
|
180
|
+
* `camelCase('tag', { prefix: 'create' }) // 'createTag'`
|
|
265
181
|
*/
|
|
266
|
-
function
|
|
267
|
-
const parts = text.split(/\.(?=[a-zA-Z])/);
|
|
268
|
-
return parts.map((part, i) => transformPart(part, i === parts.length - 1)).filter(Boolean).join("/");
|
|
269
|
-
}
|
|
270
|
-
/**
|
|
271
|
-
* Converts `text` to camelCase.
|
|
272
|
-
* When `isFile` is `true`, dot-separated segments are each cased independently and joined with `/`.
|
|
273
|
-
*
|
|
274
|
-
* @example
|
|
275
|
-
* camelCase('hello-world') // 'helloWorld'
|
|
276
|
-
* camelCase('pet.petId', { isFile: true }) // 'pet/petId'
|
|
277
|
-
*/
|
|
278
|
-
function camelCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
279
|
-
if (isFile) return applyToFileParts(text, (part, isLast) => camelCase(part, isLast ? {
|
|
280
|
-
prefix,
|
|
281
|
-
suffix
|
|
282
|
-
} : {}));
|
|
182
|
+
function camelCase(text, { prefix = "", suffix = "" } = {}) {
|
|
283
183
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, false);
|
|
284
184
|
}
|
|
285
185
|
/**
|
|
286
186
|
* Converts `text` to PascalCase.
|
|
287
|
-
* When `isFile` is `true`, the last dot-separated segment is PascalCased and earlier segments are camelCased.
|
|
288
187
|
*
|
|
289
|
-
* @example
|
|
290
|
-
* pascalCase('hello-world')
|
|
291
|
-
*
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
suffix
|
|
297
|
-
}) : camelCase(part));
|
|
188
|
+
* @example Word boundaries
|
|
189
|
+
* `pascalCase('hello-world') // 'HelloWorld'`
|
|
190
|
+
*
|
|
191
|
+
* @example With a suffix
|
|
192
|
+
* `pascalCase('tag', { suffix: 'schema' }) // 'TagSchema'`
|
|
193
|
+
*/
|
|
194
|
+
function pascalCase(text, { prefix = "", suffix = "" } = {}) {
|
|
298
195
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
299
196
|
}
|
|
300
197
|
//#endregion
|
|
301
|
-
//#region ../../internals/utils/src/
|
|
198
|
+
//#region ../../internals/utils/src/runtime.ts
|
|
302
199
|
/**
|
|
303
|
-
*
|
|
200
|
+
* Detects the JavaScript runtime executing the current process and exposes its name and version.
|
|
201
|
+
*
|
|
202
|
+
* Prefer the shared {@link runtime} instance over constructing your own.
|
|
203
|
+
*/
|
|
204
|
+
var Runtime = class {
|
|
205
|
+
/**
|
|
206
|
+
* `true` when the current process is running under Bun.
|
|
207
|
+
*
|
|
208
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
209
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
210
|
+
* otherwise look like Node.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* if (runtime.isBun) {
|
|
215
|
+
* await Bun.write(path, data)
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
get isBun() {
|
|
220
|
+
return typeof Bun !== "undefined";
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* `true` when the current process is running under Deno.
|
|
224
|
+
*/
|
|
225
|
+
get isDeno() {
|
|
226
|
+
return typeof globalThis.Deno !== "undefined";
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* `true` when the current process is running under Node.
|
|
230
|
+
*
|
|
231
|
+
* Bun and Deno are excluded first so a polyfilled `process` does not register as Node.
|
|
232
|
+
*/
|
|
233
|
+
get isNode() {
|
|
234
|
+
return !this.isBun && !this.isDeno && typeof process !== "undefined" && process.versions?.node != null;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Name of the runtime executing the current process.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* runtime.name // 'bun' when run with `bun kubb`, 'node' otherwise
|
|
242
|
+
* ```
|
|
243
|
+
*/
|
|
244
|
+
get name() {
|
|
245
|
+
if (this.isBun) return "bun";
|
|
246
|
+
if (this.isDeno) return "deno";
|
|
247
|
+
return "node";
|
|
248
|
+
}
|
|
249
|
+
/**
|
|
250
|
+
* Version of the active runtime, or an empty string when it cannot be read.
|
|
251
|
+
*
|
|
252
|
+
* @example
|
|
253
|
+
* ```ts
|
|
254
|
+
* runtime.version // '1.3.11' under Bun, '22.22.2' under Node
|
|
255
|
+
* ```
|
|
256
|
+
*/
|
|
257
|
+
get version() {
|
|
258
|
+
if (this.isBun) return process.versions.bun ?? "";
|
|
259
|
+
if (this.isDeno) return globalThis.Deno?.version?.deno ?? "";
|
|
260
|
+
return process.versions?.node ?? "";
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* Shared {@link Runtime} instance describing the JavaScript runtime executing the current process.
|
|
265
|
+
*/
|
|
266
|
+
const runtime = new Runtime();
|
|
267
|
+
//#endregion
|
|
268
|
+
//#region ../../internals/utils/src/fs.ts
|
|
269
|
+
/**
|
|
270
|
+
* Resolves to `true` when the file or directory at `path` exists.
|
|
271
|
+
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
304
272
|
*
|
|
305
273
|
* @example
|
|
306
274
|
* ```ts
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
275
|
+
* if (await exists('./kubb.config.ts')) {
|
|
276
|
+
* const content = await read('./kubb.config.ts')
|
|
277
|
+
* }
|
|
310
278
|
* ```
|
|
311
279
|
*/
|
|
312
|
-
function
|
|
313
|
-
|
|
280
|
+
async function exists(path) {
|
|
281
|
+
if (runtime.isBun) return Bun.file(path).exists();
|
|
282
|
+
return (0, node_fs_promises.access)(path).then(() => true, () => false);
|
|
314
283
|
}
|
|
315
284
|
/**
|
|
316
|
-
*
|
|
317
|
-
*
|
|
285
|
+
* Reads the file at `path` as a UTF-8 string.
|
|
286
|
+
* Uses `Bun.file().text()` when running under Bun, `fs.readFile` otherwise.
|
|
318
287
|
*
|
|
319
288
|
* @example
|
|
320
289
|
* ```ts
|
|
321
|
-
*
|
|
322
|
-
* // { a: { x: 1, y: 2 } }
|
|
290
|
+
* const source = await read('./src/Pet.ts')
|
|
323
291
|
* ```
|
|
324
292
|
*/
|
|
325
|
-
function
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const sv = source[key];
|
|
329
|
-
const tv = result[key];
|
|
330
|
-
result[key] = sv !== null && typeof sv === "object" && !Array.isArray(sv) && tv !== null && typeof tv === "object" && !Array.isArray(tv) ? mergeDeep(tv, sv) : sv;
|
|
331
|
-
}
|
|
332
|
-
return result;
|
|
293
|
+
async function read(path) {
|
|
294
|
+
if (runtime.isBun) return Bun.file(path).text();
|
|
295
|
+
return (0, node_fs_promises.readFile)(path, { encoding: "utf8" });
|
|
333
296
|
}
|
|
334
297
|
//#endregion
|
|
335
298
|
//#region ../../internals/utils/src/reserved.ts
|
|
@@ -432,102 +395,99 @@ const reservedWords = new Set([
|
|
|
432
395
|
*/
|
|
433
396
|
function isValidVarName(name) {
|
|
434
397
|
if (!name || reservedWords.has(name)) return false;
|
|
435
|
-
return
|
|
398
|
+
return isIdentifier(name);
|
|
436
399
|
}
|
|
437
|
-
//#endregion
|
|
438
|
-
//#region ../../internals/utils/src/urlPath.ts
|
|
439
400
|
/**
|
|
440
|
-
*
|
|
401
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
402
|
+
*
|
|
403
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
404
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
405
|
+
* deciding whether an object key needs quoting.
|
|
441
406
|
*
|
|
442
407
|
* @example
|
|
443
|
-
*
|
|
444
|
-
*
|
|
445
|
-
*
|
|
408
|
+
* ```ts
|
|
409
|
+
* isIdentifier('name') // true
|
|
410
|
+
* isIdentifier('x-total')// false
|
|
411
|
+
* ```
|
|
446
412
|
*/
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
413
|
+
function isIdentifier(name) {
|
|
414
|
+
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
415
|
+
}
|
|
416
|
+
//#endregion
|
|
417
|
+
//#region ../../internals/utils/src/url.ts
|
|
418
|
+
function transformParam(raw, casing) {
|
|
419
|
+
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
420
|
+
return casing === "camelcase" ? camelCase(param) : param;
|
|
421
|
+
}
|
|
422
|
+
function toParamsObject(path, { replacer, casing } = {}) {
|
|
423
|
+
const params = {};
|
|
424
|
+
for (const match of path.matchAll(/\{([^}]+)\}/g)) {
|
|
425
|
+
const param = transformParam(match[1], casing);
|
|
426
|
+
const key = replacer ? replacer(param) : param;
|
|
427
|
+
params[key] = key;
|
|
456
428
|
}
|
|
457
|
-
|
|
429
|
+
return Object.keys(params).length > 0 ? params : null;
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Helpers for OpenAPI/Swagger paths, plus a thin wrapper over the native `URL`.
|
|
433
|
+
*/
|
|
434
|
+
var Url = class Url {
|
|
435
|
+
/**
|
|
436
|
+
* Reports whether `url` is a parseable absolute URL. Delegates to the native `URL.canParse`.
|
|
458
437
|
*
|
|
459
438
|
* @example
|
|
460
|
-
*
|
|
461
|
-
*
|
|
462
|
-
* ```
|
|
439
|
+
* Url.canParse('https://petstore.swagger.io/v2') // true
|
|
440
|
+
* Url.canParse('/pet/{petId}') // false
|
|
463
441
|
*/
|
|
464
|
-
|
|
465
|
-
return
|
|
442
|
+
static canParse(url, base) {
|
|
443
|
+
return URL.canParse(url, base);
|
|
466
444
|
}
|
|
467
|
-
/**
|
|
445
|
+
/**
|
|
446
|
+
* Converts an OpenAPI/Swagger path to Express-style colon syntax.
|
|
468
447
|
*
|
|
469
448
|
* @example
|
|
470
|
-
*
|
|
471
|
-
* new URLPath('https://petstore.swagger.io/v2/pet').isURL // true
|
|
472
|
-
* new URLPath('/pet/{petId}').isURL // false
|
|
473
|
-
* ```
|
|
449
|
+
* Url.toPath('/pet/{petId}') // '/pet/:petId'
|
|
474
450
|
*/
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return !!new URL(this.path).href;
|
|
478
|
-
} catch {
|
|
479
|
-
return false;
|
|
480
|
-
}
|
|
451
|
+
static toPath(path) {
|
|
452
|
+
return path.replace(/\{([^}]+)\}/g, ":$1");
|
|
481
453
|
}
|
|
482
454
|
/**
|
|
483
|
-
* Converts
|
|
455
|
+
* Converts an OpenAPI/Swagger path to a TypeScript template literal string.
|
|
456
|
+
* `prefix` is prepended inside the literal, `replacer` transforms each parameter name,
|
|
457
|
+
* and `casing` controls parameter identifier casing.
|
|
484
458
|
*
|
|
485
459
|
* @example
|
|
486
|
-
*
|
|
487
|
-
* new URLPath('/account/monetary-accountID').template // '`/account/${monetaryAccountId}`'
|
|
488
|
-
*/
|
|
489
|
-
get template() {
|
|
490
|
-
return this.toTemplateString();
|
|
491
|
-
}
|
|
492
|
-
/** Returns the path and its extracted params as a structured `URLObject`, or as a stringified expression when `stringify` is set.
|
|
460
|
+
* Url.toTemplateString('/pet/{petId}') // '`/pet/${petId}`'
|
|
493
461
|
*
|
|
494
462
|
* @example
|
|
495
|
-
*
|
|
496
|
-
* new URLPath('/pet/{petId}').object
|
|
497
|
-
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
498
|
-
* ```
|
|
463
|
+
* Url.toTemplateString('/pet/{petId}', { prefix: 'https://api' }) // '`https://api/pet/${petId}`'
|
|
499
464
|
*/
|
|
500
|
-
|
|
501
|
-
|
|
465
|
+
static toTemplateString(path, { prefix, replacer, casing } = {}) {
|
|
466
|
+
const result = path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
467
|
+
if (i % 2 === 0) return part;
|
|
468
|
+
const param = transformParam(part, casing);
|
|
469
|
+
return `\${${replacer ? replacer(param) : param}}`;
|
|
470
|
+
}).join("");
|
|
471
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
502
472
|
}
|
|
503
|
-
/**
|
|
473
|
+
/**
|
|
474
|
+
* Returns the path and its extracted params as a structured `URLObject`, or as a stringified
|
|
475
|
+
* expression when `stringify` is set.
|
|
504
476
|
*
|
|
505
477
|
* @example
|
|
506
|
-
*
|
|
507
|
-
*
|
|
508
|
-
* new URLPath('/pet').params // undefined
|
|
509
|
-
* ```
|
|
510
|
-
*/
|
|
511
|
-
get params() {
|
|
512
|
-
return this.getParams();
|
|
513
|
-
}
|
|
514
|
-
#transformParam(raw) {
|
|
515
|
-
const param = isValidVarName(raw) ? raw : camelCase(raw);
|
|
516
|
-
return this.#options.casing === "camelcase" ? camelCase(param) : param;
|
|
517
|
-
}
|
|
518
|
-
/**
|
|
519
|
-
* Iterates over every `{param}` token in `path`, calling `fn` with the raw token and transformed name.
|
|
478
|
+
* Url.toObject('/pet/{petId}')
|
|
479
|
+
* // { url: '/pet/:petId', params: { petId: 'petId' } }
|
|
520
480
|
*/
|
|
521
|
-
|
|
522
|
-
for (const match of this.path.matchAll(/\{([^}]+)\}/g)) {
|
|
523
|
-
const raw = match[1];
|
|
524
|
-
fn(raw, this.#transformParam(raw));
|
|
525
|
-
}
|
|
526
|
-
}
|
|
527
|
-
toObject({ type = "path", replacer, stringify } = {}) {
|
|
481
|
+
static toObject(path, { type = "path", replacer, stringify, casing } = {}) {
|
|
528
482
|
const object = {
|
|
529
|
-
url: type === "path" ?
|
|
530
|
-
|
|
483
|
+
url: type === "path" ? Url.toPath(path) : Url.toTemplateString(path, {
|
|
484
|
+
replacer,
|
|
485
|
+
casing
|
|
486
|
+
}),
|
|
487
|
+
params: toParamsObject(path, {
|
|
488
|
+
replacer,
|
|
489
|
+
casing
|
|
490
|
+
})
|
|
531
491
|
};
|
|
532
492
|
if (stringify) {
|
|
533
493
|
if (type === "template") return JSON.stringify(object).replaceAll("'", "").replaceAll(`"`, "");
|
|
@@ -536,66 +496,124 @@ var URLPath = class {
|
|
|
536
496
|
}
|
|
537
497
|
return object;
|
|
538
498
|
}
|
|
539
|
-
/**
|
|
540
|
-
* Converts the OpenAPI path to a TypeScript template literal string.
|
|
541
|
-
* An optional `replacer` can transform each extracted parameter name before interpolation.
|
|
542
|
-
*
|
|
543
|
-
* @example
|
|
544
|
-
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
545
|
-
*/
|
|
546
|
-
toTemplateString({ prefix = "", replacer } = {}) {
|
|
547
|
-
return `\`${prefix}${this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
548
|
-
if (i % 2 === 0) return part;
|
|
549
|
-
const param = this.#transformParam(part);
|
|
550
|
-
return `\${${replacer ? replacer(param) : param}}`;
|
|
551
|
-
}).join("")}\``;
|
|
552
|
-
}
|
|
553
|
-
/**
|
|
554
|
-
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
555
|
-
* An optional `replacer` transforms each parameter name in both key and value positions.
|
|
556
|
-
* Returns `undefined` when no path parameters are found.
|
|
557
|
-
*
|
|
558
|
-
* @example
|
|
559
|
-
* ```ts
|
|
560
|
-
* new URLPath('/pet/{petId}/tag/{tagId}').getParams()
|
|
561
|
-
* // { petId: 'petId', tagId: 'tagId' }
|
|
562
|
-
* ```
|
|
563
|
-
*/
|
|
564
|
-
getParams(replacer) {
|
|
565
|
-
const params = {};
|
|
566
|
-
this.#eachParam((_raw, param) => {
|
|
567
|
-
const key = replacer ? replacer(param) : param;
|
|
568
|
-
params[key] = key;
|
|
569
|
-
});
|
|
570
|
-
return Object.keys(params).length > 0 ? params : void 0;
|
|
571
|
-
}
|
|
572
|
-
/** Converts the OpenAPI path to Express-style colon syntax.
|
|
573
|
-
*
|
|
574
|
-
* @example
|
|
575
|
-
* ```ts
|
|
576
|
-
* new URLPath('/pet/{petId}').toURLPath() // '/pet/:petId'
|
|
577
|
-
* ```
|
|
578
|
-
*/
|
|
579
|
-
toURLPath() {
|
|
580
|
-
return this.path.replace(/\{([^}]+)\}/g, ":$1");
|
|
581
|
-
}
|
|
582
499
|
};
|
|
583
500
|
//#endregion
|
|
584
|
-
//#region src/
|
|
501
|
+
//#region src/bundler.ts
|
|
502
|
+
const urlRegExp = /^https?:\/+/i;
|
|
503
|
+
async function readSource(sourcePath) {
|
|
504
|
+
if (urlRegExp.test(sourcePath)) {
|
|
505
|
+
const url = new URL(sourcePath);
|
|
506
|
+
const response = await fetch(url);
|
|
507
|
+
if (!response.ok) throw new Error(`Cannot fetch the OAS document at ${url.href} (HTTP ${response.status})`);
|
|
508
|
+
return response.text();
|
|
509
|
+
}
|
|
510
|
+
return read(sourcePath);
|
|
511
|
+
}
|
|
512
|
+
async function resolveSource(sourcePath) {
|
|
513
|
+
const data = await readSource(sourcePath);
|
|
514
|
+
if (sourcePath.toLowerCase().endsWith(".md")) return data;
|
|
515
|
+
return (0, yaml.parse)(data);
|
|
516
|
+
}
|
|
585
517
|
/**
|
|
586
|
-
*
|
|
518
|
+
* Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
|
|
519
|
+
*
|
|
520
|
+
* External file schemas are hoisted into named `components.schemas` entries, so a property
|
|
521
|
+
* pointing at `./schemas/User.yaml` ends up referencing `#/components/schemas/User`. Generators
|
|
522
|
+
* can then emit a named type with an import instead of inlining the shape. Sources are read with
|
|
523
|
+
* the Bun-aware `read` util for local YAML and JSON files, and with `fetch` for HTTP(S) URLs.
|
|
524
|
+
*
|
|
525
|
+
* @example Local file
|
|
526
|
+
* `const document = await bundleDocument('./openapi.yaml')`
|
|
527
|
+
*
|
|
528
|
+
* @example Remote URL
|
|
529
|
+
* `const document = await bundleDocument('https://example.com/openapi.yaml')`
|
|
530
|
+
*/
|
|
531
|
+
async function bundleDocument(pathOrUrl) {
|
|
532
|
+
const cache = /* @__PURE__ */ new Map();
|
|
533
|
+
const resolver = (sourcePath) => {
|
|
534
|
+
const key = urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
|
|
535
|
+
const cached = cache.get(key);
|
|
536
|
+
if (cached) return cached;
|
|
537
|
+
const result = resolveSource(sourcePath);
|
|
538
|
+
cache.set(key, result);
|
|
539
|
+
return result;
|
|
540
|
+
};
|
|
541
|
+
await resolver(pathOrUrl);
|
|
542
|
+
return await (0, api_ref_bundler.bundle)(pathOrUrl, resolver);
|
|
543
|
+
}
|
|
544
|
+
//#endregion
|
|
545
|
+
//#region src/factory.ts
|
|
546
|
+
/**
|
|
547
|
+
* Loads and bundles an OpenAPI document, returning the raw `Document`.
|
|
548
|
+
*
|
|
549
|
+
* A string is a file path or URL: it is bundled via `api-ref-bundler`, hoisting external file
|
|
550
|
+
* schemas into named `components.schemas` entries so generators can emit named types and imports.
|
|
551
|
+
* An object is treated as an already-parsed document. Swagger 2.0 and OpenAPI 3.0 documents are
|
|
552
|
+
* up-converted to OpenAPI 3.1 via `@scalar/openapi-upgrader`.
|
|
587
553
|
*
|
|
588
554
|
* @example
|
|
589
555
|
* ```ts
|
|
590
|
-
*
|
|
591
|
-
*
|
|
592
|
-
*
|
|
556
|
+
* const document = await parseDocument('./openapi.yaml')
|
|
557
|
+
* const document = await parseDocument(rawDocumentObject)
|
|
558
|
+
* ```
|
|
559
|
+
*/
|
|
560
|
+
async function parseDocument(pathOrApi) {
|
|
561
|
+
if (typeof pathOrApi === "string") return parseDocument(await bundleDocument(pathOrApi));
|
|
562
|
+
return (0, _scalar_openapi_upgrader.upgrade)(pathOrApi, "3.1");
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Creates a `Document` from an `AdapterSource`.
|
|
566
|
+
*
|
|
567
|
+
* - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
|
|
568
|
+
* - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
|
|
569
|
+
*
|
|
570
|
+
* @example
|
|
571
|
+
* ```ts
|
|
572
|
+
* const document = await parseFromConfig({ type: 'path', path: './openapi.yaml' })
|
|
573
|
+
* const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
|
|
593
574
|
* ```
|
|
594
575
|
*/
|
|
595
|
-
function
|
|
596
|
-
|
|
576
|
+
async function parseFromConfig(source) {
|
|
577
|
+
if (source.type === "data") return parseDocument(typeof source.data === "string" ? (0, yaml.parse)(source.data) : structuredClone(source.data));
|
|
578
|
+
if (Url.canParse(source.path)) return parseDocument(source.path);
|
|
579
|
+
const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
|
|
580
|
+
await assertInputExists(resolved);
|
|
581
|
+
return parseDocument(resolved);
|
|
597
582
|
}
|
|
598
583
|
/**
|
|
584
|
+
* Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
|
|
585
|
+
* URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
|
|
586
|
+
* its parse error instead.
|
|
587
|
+
*/
|
|
588
|
+
async function assertInputExists(input) {
|
|
589
|
+
if (Url.canParse(input)) return;
|
|
590
|
+
if (!await exists(input)) throw new _kubb_core.Diagnostics.Error({
|
|
591
|
+
code: _kubb_core.Diagnostics.code.inputNotFound,
|
|
592
|
+
severity: "error",
|
|
593
|
+
message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
|
|
594
|
+
help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
|
|
595
|
+
location: { kind: "config" }
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* Validates an OpenAPI document using `@readme/openapi-parser` with colorized error output.
|
|
600
|
+
*
|
|
601
|
+
* @example
|
|
602
|
+
* ```ts
|
|
603
|
+
* await validateDocument(document)
|
|
604
|
+
* ```
|
|
605
|
+
*/
|
|
606
|
+
async function validateDocument(document, { throwOnError = false } = {}) {
|
|
607
|
+
try {
|
|
608
|
+
const result = await (0, _readme_openapi_parser.validate)(structuredClone(document), { validate: { errors: { colorize: true } } });
|
|
609
|
+
if (!result.valid) throw new Error((0, _readme_openapi_parser.compileErrors)(result));
|
|
610
|
+
} catch (error) {
|
|
611
|
+
if (throwOnError) throw error;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
//#endregion
|
|
615
|
+
//#region src/guards.ts
|
|
616
|
+
/**
|
|
599
617
|
* Returns `true` when a schema should be treated as nullable.
|
|
600
618
|
*
|
|
601
619
|
* Recognizes all nullable signals across OAS versions: `nullable: true` (OAS 3.0),
|
|
@@ -641,151 +659,369 @@ function isDiscriminator(obj) {
|
|
|
641
659
|
return !!obj && !!record["discriminator"] && typeof record["discriminator"] !== "string";
|
|
642
660
|
}
|
|
643
661
|
//#endregion
|
|
644
|
-
//#region src/
|
|
662
|
+
//#region src/refs.ts
|
|
663
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
645
664
|
/**
|
|
646
|
-
*
|
|
665
|
+
* Resolves a local JSON pointer reference from a document.
|
|
647
666
|
*
|
|
648
|
-
* Accepts
|
|
649
|
-
*
|
|
650
|
-
* to
|
|
667
|
+
* Accepts `#/...` refs. Returns `null` for an empty or non-local ref. When the pointer cannot be
|
|
668
|
+
* resolved, reports a `refNotFound` diagnostic into the active build and returns `null`. Outside a
|
|
669
|
+
* build there is no sink to collect it, so it throws instead.
|
|
651
670
|
*
|
|
652
671
|
* @example
|
|
653
672
|
* ```ts
|
|
654
|
-
*
|
|
655
|
-
* const document = await parse(rawDocumentObject, { canBundle: false })
|
|
673
|
+
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
656
674
|
* ```
|
|
657
675
|
*/
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
const
|
|
673
|
-
|
|
676
|
+
function resolveRef(document, $ref) {
|
|
677
|
+
const origRef = $ref;
|
|
678
|
+
$ref = $ref.trim();
|
|
679
|
+
if ($ref === "") return null;
|
|
680
|
+
if (!$ref.startsWith("#")) return null;
|
|
681
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
682
|
+
let docCache = _refCache.get(document);
|
|
683
|
+
if (!docCache) {
|
|
684
|
+
docCache = /* @__PURE__ */ new Map();
|
|
685
|
+
_refCache.set(document, docCache);
|
|
686
|
+
}
|
|
687
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
688
|
+
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
689
|
+
if (!current) {
|
|
690
|
+
const diagnostic = {
|
|
691
|
+
code: _kubb_core.Diagnostics.code.refNotFound,
|
|
692
|
+
severity: "error",
|
|
693
|
+
message: `Could not find a definition for ${origRef}.`,
|
|
694
|
+
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
695
|
+
location: {
|
|
696
|
+
kind: "schema",
|
|
697
|
+
pointer: origRef,
|
|
698
|
+
ref: origRef
|
|
699
|
+
}
|
|
700
|
+
};
|
|
701
|
+
if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.Diagnostics.Error(diagnostic);
|
|
702
|
+
return null;
|
|
674
703
|
}
|
|
675
|
-
|
|
704
|
+
docCache.set($ref, current);
|
|
705
|
+
return current;
|
|
676
706
|
}
|
|
677
707
|
/**
|
|
678
|
-
*
|
|
708
|
+
* Resolves a `$ref` object while preserving the original `$ref` field on the result.
|
|
679
709
|
*
|
|
680
|
-
*
|
|
681
|
-
*
|
|
710
|
+
* Useful for parser flows that need both dereferenced fields and pointer
|
|
711
|
+
* identity (for naming/import purposes). Non-reference values are returned as-is.
|
|
682
712
|
*
|
|
683
713
|
* @example
|
|
684
714
|
* ```ts
|
|
685
|
-
*
|
|
715
|
+
* dereferenceWithRef(document, { $ref: '#/components/schemas/Pet' })
|
|
716
|
+
* // { $ref: '#/components/schemas/Pet', type: 'object', properties: { ... } }
|
|
686
717
|
* ```
|
|
687
718
|
*/
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
}));
|
|
694
|
-
if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
|
|
695
|
-
const seed = {
|
|
696
|
-
openapi: MERGE_OPENAPI_VERSION,
|
|
697
|
-
info: {
|
|
698
|
-
title: MERGE_DEFAULT_TITLE,
|
|
699
|
-
version: MERGE_DEFAULT_VERSION
|
|
700
|
-
},
|
|
701
|
-
paths: {},
|
|
702
|
-
components: { schemas: {} }
|
|
719
|
+
function dereferenceWithRef(document, schema) {
|
|
720
|
+
if (isReference(schema)) return {
|
|
721
|
+
...schema,
|
|
722
|
+
...resolveRef(document, schema.$ref),
|
|
723
|
+
$ref: schema.$ref
|
|
703
724
|
};
|
|
704
|
-
return
|
|
725
|
+
return schema;
|
|
705
726
|
}
|
|
727
|
+
//#endregion
|
|
728
|
+
//#region src/dialect.ts
|
|
706
729
|
/**
|
|
707
|
-
*
|
|
730
|
+
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
708
731
|
*
|
|
709
|
-
*
|
|
710
|
-
*
|
|
711
|
-
*
|
|
712
|
-
*
|
|
732
|
+
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
733
|
+
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
734
|
+
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
735
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
736
|
+
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
737
|
+
* the rest unchanged.
|
|
738
|
+
*
|
|
739
|
+
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
740
|
+
* JSON Schema vocabulary, so the converters keep that common case.
|
|
713
741
|
*
|
|
714
742
|
* @example
|
|
715
743
|
* ```ts
|
|
716
|
-
* const
|
|
717
|
-
* const
|
|
744
|
+
* const parser = createSchemaParser(context) // uses oasDialect
|
|
745
|
+
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
718
746
|
* ```
|
|
719
747
|
*/
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
748
|
+
const oasDialect = _kubb_core.ast.defineDialect({
|
|
749
|
+
name: "oas",
|
|
750
|
+
schema: {
|
|
751
|
+
isNullable,
|
|
752
|
+
isReference,
|
|
753
|
+
isDiscriminator,
|
|
754
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
755
|
+
resolveRef
|
|
724
756
|
}
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
757
|
+
});
|
|
758
|
+
//#endregion
|
|
759
|
+
//#region src/discriminator.ts
|
|
760
|
+
/**
|
|
761
|
+
* Maps each child schema name to its discriminator patch data by scanning the given
|
|
762
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
763
|
+
*
|
|
764
|
+
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
765
|
+
* discriminator parents) rather than on all schemas at once.
|
|
766
|
+
*/
|
|
767
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
768
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
769
|
+
for (const schema of schemas) {
|
|
770
|
+
let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
|
|
771
|
+
if (!unionNode) {
|
|
772
|
+
const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
|
|
773
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
774
|
+
const u = _kubb_core.ast.narrowSchema(m, "union");
|
|
775
|
+
if (u) {
|
|
776
|
+
unionNode = u;
|
|
777
|
+
break;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
782
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
783
|
+
for (const member of members) {
|
|
784
|
+
const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
|
|
785
|
+
if (!intersectionNode?.members) continue;
|
|
786
|
+
let refNode = null;
|
|
787
|
+
let objNode = null;
|
|
788
|
+
for (const m of intersectionNode.members) {
|
|
789
|
+
refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
|
|
790
|
+
objNode ??= _kubb_core.ast.narrowSchema(m, "object");
|
|
791
|
+
}
|
|
792
|
+
if (!refNode?.name || !objNode) continue;
|
|
793
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
794
|
+
const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : null;
|
|
795
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
796
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
797
|
+
if (!enumValues.length) continue;
|
|
798
|
+
const existing = childMap.get(refNode.name);
|
|
799
|
+
if (!existing) {
|
|
800
|
+
childMap.set(refNode.name, {
|
|
801
|
+
propertyName: discriminatorPropertyName,
|
|
802
|
+
enumValues: [...enumValues]
|
|
803
|
+
});
|
|
804
|
+
continue;
|
|
805
|
+
}
|
|
806
|
+
existing.enumValues.push(...enumValues);
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
return childMap;
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
812
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
813
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
814
|
+
* without buffering all schemas.
|
|
815
|
+
*/
|
|
816
|
+
function patchDiscriminatorNode(node, entry) {
|
|
817
|
+
const objectNode = _kubb_core.ast.narrowSchema(node, "object");
|
|
818
|
+
if (!objectNode) return node;
|
|
819
|
+
const { propertyName, enumValues } = entry;
|
|
820
|
+
const enumSchema = _kubb_core.ast.factory.createSchema({
|
|
821
|
+
type: "enum",
|
|
822
|
+
enumValues
|
|
823
|
+
});
|
|
824
|
+
const newProp = _kubb_core.ast.factory.createProperty({
|
|
825
|
+
name: propertyName,
|
|
826
|
+
required: true,
|
|
827
|
+
schema: enumSchema
|
|
828
|
+
});
|
|
829
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
830
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
831
|
+
return {
|
|
832
|
+
...objectNode,
|
|
833
|
+
properties: newProperties
|
|
834
|
+
};
|
|
728
835
|
}
|
|
729
836
|
/**
|
|
730
|
-
*
|
|
837
|
+
* Creates a single-property object schema used as a discriminator literal.
|
|
731
838
|
*
|
|
732
839
|
* @example
|
|
733
840
|
* ```ts
|
|
734
|
-
*
|
|
841
|
+
* createDiscriminantNode({ propertyName: 'type', value: 'dog' })
|
|
842
|
+
* // -> { type: 'object', properties: [{ name: 'type', required: true, schema: enum('dog') }] }
|
|
735
843
|
* ```
|
|
736
844
|
*/
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
845
|
+
function createDiscriminantNode({ propertyName, value }) {
|
|
846
|
+
return _kubb_core.ast.factory.createSchema({
|
|
847
|
+
type: "object",
|
|
848
|
+
primitive: "object",
|
|
849
|
+
properties: [_kubb_core.ast.factory.createProperty({
|
|
850
|
+
name: propertyName,
|
|
851
|
+
schema: _kubb_core.ast.factory.createSchema({
|
|
852
|
+
type: "enum",
|
|
853
|
+
primitive: "string",
|
|
854
|
+
enumValues: [value]
|
|
855
|
+
}),
|
|
856
|
+
required: true
|
|
857
|
+
})]
|
|
858
|
+
});
|
|
859
|
+
}
|
|
860
|
+
/**
|
|
861
|
+
* Returns the discriminator key whose mapping value matches `ref`, or `null` when there is no match.
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```ts
|
|
865
|
+
* findDiscriminator({ dog: '#/components/schemas/Dog' }, '#/components/schemas/Dog') // 'dog'
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
868
|
+
function findDiscriminator(mapping, ref) {
|
|
869
|
+
if (!mapping || !ref) return null;
|
|
870
|
+
return Object.entries(mapping).find(([, value]) => value === ref)?.[0] ?? null;
|
|
746
871
|
}
|
|
747
872
|
//#endregion
|
|
748
|
-
//#region src/
|
|
873
|
+
//#region src/mime.ts
|
|
749
874
|
/**
|
|
750
|
-
*
|
|
875
|
+
* MIME type fragments that mark a media type as JSON-like.
|
|
751
876
|
*
|
|
752
|
-
*
|
|
753
|
-
*
|
|
877
|
+
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
878
|
+
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
879
|
+
*/
|
|
880
|
+
const jsonMimeFragments = [
|
|
881
|
+
"application/json",
|
|
882
|
+
"application/x-json",
|
|
883
|
+
"text/json",
|
|
884
|
+
"text/x-json",
|
|
885
|
+
"+json"
|
|
886
|
+
];
|
|
887
|
+
/**
|
|
888
|
+
* Returns `true` when a media type string is JSON-like.
|
|
754
889
|
*
|
|
755
890
|
* @example
|
|
756
891
|
* ```ts
|
|
757
|
-
*
|
|
892
|
+
* isJsonMimeType('application/json') // true
|
|
893
|
+
* isJsonMimeType('application/vnd.api+json') // true
|
|
894
|
+
* isJsonMimeType('multipart/form-data') // false
|
|
758
895
|
* ```
|
|
759
896
|
*/
|
|
760
|
-
function
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
897
|
+
function isJsonMimeType(mimeType) {
|
|
898
|
+
return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
|
|
899
|
+
}
|
|
900
|
+
//#endregion
|
|
901
|
+
//#region src/operation.ts
|
|
902
|
+
/**
|
|
903
|
+
* Slugifies a path for the `operationId` fallback: non-alphanumerics collapse to single dashes,
|
|
904
|
+
* with no leading or trailing dash.
|
|
905
|
+
*/
|
|
906
|
+
function slugify(value) {
|
|
907
|
+
return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
|
|
911
|
+
*/
|
|
912
|
+
function getOperationId({ path, method, schema }) {
|
|
913
|
+
const { operationId } = schema;
|
|
914
|
+
if (typeof operationId === "string" && operationId.length > 0) return operationId;
|
|
915
|
+
return `${method}_${slugify(path).toLowerCase()}`;
|
|
916
|
+
}
|
|
917
|
+
/**
|
|
918
|
+
* Returns the declared response status codes, skipping `x-` extensions and non-object entries.
|
|
919
|
+
*/
|
|
920
|
+
function getResponseStatusCodes({ schema }) {
|
|
921
|
+
const responses = schema.responses;
|
|
922
|
+
if (!responses || isReference(responses)) return [];
|
|
923
|
+
return Object.keys(responses).filter((key) => !key.startsWith("x-") && !!responses[key] && typeof responses[key] === "object");
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Returns the response object for a status code, resolving a `$ref` in place. `false` when absent.
|
|
927
|
+
*/
|
|
928
|
+
function getResponseByStatusCode({ document, operation, statusCode }) {
|
|
929
|
+
const responses = operation.schema.responses;
|
|
930
|
+
if (!responses || isReference(responses)) return false;
|
|
931
|
+
const response = responses[statusCode];
|
|
932
|
+
if (!response) return false;
|
|
933
|
+
if (isReference(response)) {
|
|
934
|
+
const resolved = resolveRef(document, response.$ref);
|
|
935
|
+
responses[statusCode] = resolved;
|
|
936
|
+
if (!resolved || isReference(resolved)) return false;
|
|
937
|
+
return resolved;
|
|
938
|
+
}
|
|
939
|
+
return response;
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* Resolves the request body (dereferencing a `$ref` in place) and returns its content map, or
|
|
943
|
+
* `undefined` when the operation has no request body.
|
|
944
|
+
*/
|
|
945
|
+
function getRequestBodyContent({ document, operation }) {
|
|
946
|
+
const { schema } = operation;
|
|
947
|
+
let requestBody = schema.requestBody;
|
|
948
|
+
if (!requestBody) return;
|
|
949
|
+
if (isReference(requestBody)) {
|
|
950
|
+
const resolved = resolveRef(document, requestBody.$ref);
|
|
951
|
+
schema.requestBody = resolved;
|
|
952
|
+
if (!resolved || isReference(resolved)) return;
|
|
953
|
+
requestBody = resolved;
|
|
954
|
+
}
|
|
955
|
+
return requestBody.content;
|
|
956
|
+
}
|
|
957
|
+
/**
|
|
958
|
+
* Returns the request body media type. With `mediaType` set, returns that entry or `false`.
|
|
959
|
+
* Otherwise picks the first JSON-like media type, then the first declared one, as a
|
|
960
|
+
* `[mediaType, object]` tuple.
|
|
961
|
+
*/
|
|
962
|
+
function getRequestContent({ document, operation, mediaType }) {
|
|
963
|
+
const content = getRequestBodyContent({
|
|
964
|
+
document,
|
|
965
|
+
operation
|
|
966
|
+
});
|
|
967
|
+
if (!content) return false;
|
|
968
|
+
if (mediaType) return mediaType in content ? content[mediaType] : false;
|
|
969
|
+
const mediaTypes = Object.keys(content);
|
|
970
|
+
const available = mediaTypes.find((mt) => isJsonMimeType(mt)) ?? mediaTypes[0];
|
|
971
|
+
return available ? [available, content[available]] : false;
|
|
972
|
+
}
|
|
973
|
+
/**
|
|
974
|
+
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
975
|
+
* when several are declared), then the first declared one, defaulting to `'application/json'`.
|
|
976
|
+
*/
|
|
977
|
+
function getRequestContentType({ document, operation }) {
|
|
978
|
+
const content = getRequestBodyContent({
|
|
979
|
+
document,
|
|
980
|
+
operation
|
|
981
|
+
});
|
|
982
|
+
const mediaTypes = content ? Object.keys(content) : [];
|
|
983
|
+
let result = mediaTypes[0] ?? "application/json";
|
|
984
|
+
for (const mt of mediaTypes) if (isJsonMimeType(mt)) result = mt;
|
|
985
|
+
return result;
|
|
769
986
|
}
|
|
770
987
|
/**
|
|
771
|
-
*
|
|
772
|
-
*
|
|
773
|
-
* Useful for parser flows that need both dereferenced fields and pointer
|
|
774
|
-
* identity (for naming/import purposes). Non-reference values are returned as-is.
|
|
988
|
+
* Builds an `Operation` for every supported HTTP method on every path, in document order.
|
|
989
|
+
* `x-` path keys and unresolvable path-item `$ref`s are skipped.
|
|
775
990
|
*
|
|
776
991
|
* @example
|
|
777
992
|
* ```ts
|
|
778
|
-
*
|
|
779
|
-
*
|
|
993
|
+
* for (const operation of getOperations(document)) {
|
|
994
|
+
* parseOperation(options, operation)
|
|
995
|
+
* }
|
|
780
996
|
* ```
|
|
781
997
|
*/
|
|
782
|
-
function
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
998
|
+
function getOperations(document) {
|
|
999
|
+
const operations = [];
|
|
1000
|
+
const paths = document.paths;
|
|
1001
|
+
if (!paths) return operations;
|
|
1002
|
+
for (const path of Object.keys(paths)) {
|
|
1003
|
+
if (path.startsWith("x-")) continue;
|
|
1004
|
+
let pathItem = paths[path];
|
|
1005
|
+
if (!pathItem) continue;
|
|
1006
|
+
if (isReference(pathItem)) {
|
|
1007
|
+
const resolved = resolveRef(document, pathItem.$ref);
|
|
1008
|
+
paths[path] = resolved;
|
|
1009
|
+
if (!resolved || isReference(resolved)) continue;
|
|
1010
|
+
pathItem = resolved;
|
|
1011
|
+
}
|
|
1012
|
+
const item = pathItem;
|
|
1013
|
+
for (const method of Object.keys(item)) {
|
|
1014
|
+
if (!SUPPORTED_METHODS.has(method)) continue;
|
|
1015
|
+
const schema = item[method];
|
|
1016
|
+
if (!schema || typeof schema !== "object") continue;
|
|
1017
|
+
operations.push({
|
|
1018
|
+
path,
|
|
1019
|
+
method,
|
|
1020
|
+
schema
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
}
|
|
1024
|
+
return operations;
|
|
789
1025
|
}
|
|
790
1026
|
//#endregion
|
|
791
1027
|
//#region src/resolvers.ts
|
|
@@ -809,7 +1045,16 @@ function resolveServerUrl(server, overrides) {
|
|
|
809
1045
|
for (const [key, variable] of Object.entries(server.variables)) {
|
|
810
1046
|
const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
|
|
811
1047
|
if (value === void 0) continue;
|
|
812
|
-
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(
|
|
1048
|
+
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.Diagnostics.Error({
|
|
1049
|
+
code: _kubb_core.Diagnostics.code.invalidServerVariable,
|
|
1050
|
+
severity: "error",
|
|
1051
|
+
message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
|
|
1052
|
+
help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
|
|
1053
|
+
location: {
|
|
1054
|
+
kind: "document",
|
|
1055
|
+
pointer: "#/servers"
|
|
1056
|
+
}
|
|
1057
|
+
});
|
|
813
1058
|
url = url.replaceAll(`{${key}}`, value);
|
|
814
1059
|
}
|
|
815
1060
|
return url;
|
|
@@ -822,6 +1067,15 @@ function getSchemaType(format) {
|
|
|
822
1067
|
return formatMap[format] ?? null;
|
|
823
1068
|
}
|
|
824
1069
|
/**
|
|
1070
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry, plus the
|
|
1071
|
+
* `specialCasedFormats` that `convertFormat` handles directly. False means the format falls back to
|
|
1072
|
+
* the base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
1073
|
+
* diagnostic in step with the parser as `formatMap` grows.
|
|
1074
|
+
*/
|
|
1075
|
+
function isHandledFormat(format) {
|
|
1076
|
+
return getSchemaType(format) !== null || specialCasedFormats.has(format);
|
|
1077
|
+
}
|
|
1078
|
+
/**
|
|
825
1079
|
* Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
826
1080
|
* Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
|
|
827
1081
|
*/
|
|
@@ -831,15 +1085,9 @@ function getPrimitiveType(type) {
|
|
|
831
1085
|
return "string";
|
|
832
1086
|
}
|
|
833
1087
|
/**
|
|
834
|
-
* Narrows a content-type string to the `MediaType` union Kubb recognizes, or returns `null`.
|
|
835
|
-
*/
|
|
836
|
-
function getMediaType(contentType) {
|
|
837
|
-
return Object.values(_kubb_core.ast.mediaTypes).includes(contentType) ? contentType : null;
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
1088
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
841
1089
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
842
|
-
* `$ref`
|
|
1090
|
+
* Each `$ref` parameter is dereferenced via `dereferenceWithRef` before merging.
|
|
843
1091
|
*
|
|
844
1092
|
* @example
|
|
845
1093
|
* ```ts
|
|
@@ -868,7 +1116,7 @@ function getResponseBody(responseBody, contentType) {
|
|
|
868
1116
|
}
|
|
869
1117
|
let availableContentType;
|
|
870
1118
|
const contentTypes = Object.keys(body.content);
|
|
871
|
-
for (const mt of contentTypes) if (
|
|
1119
|
+
for (const mt of contentTypes) if (isJsonMimeType(mt)) {
|
|
872
1120
|
availableContentType = mt;
|
|
873
1121
|
break;
|
|
874
1122
|
}
|
|
@@ -891,15 +1139,21 @@ function getResponseBody(responseBody, contentType) {
|
|
|
891
1139
|
* getResponseSchema(document, operation, '4XX') // {}
|
|
892
1140
|
* ```
|
|
893
1141
|
*/
|
|
894
|
-
function
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
1142
|
+
function resolveResponseRefs(document, operation) {
|
|
1143
|
+
const responses = operation.schema.responses;
|
|
1144
|
+
if (!responses) return;
|
|
1145
|
+
for (const key in responses) {
|
|
1146
|
+
const schema = responses[key];
|
|
1147
|
+
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
901
1148
|
}
|
|
902
|
-
|
|
1149
|
+
}
|
|
1150
|
+
function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
1151
|
+
resolveResponseRefs(document, operation);
|
|
1152
|
+
const responseBody = getResponseBody(getResponseByStatusCode({
|
|
1153
|
+
document,
|
|
1154
|
+
operation,
|
|
1155
|
+
statusCode
|
|
1156
|
+
}), options.contentType);
|
|
903
1157
|
if (responseBody === false) return {};
|
|
904
1158
|
const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
|
|
905
1159
|
if (!schema) return {};
|
|
@@ -915,16 +1169,25 @@ function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
|
915
1169
|
*/
|
|
916
1170
|
function getRequestSchema(document, operation, options = {}) {
|
|
917
1171
|
if (operation.schema.requestBody) operation.schema.requestBody = dereferenceWithRef(document, operation.schema.requestBody);
|
|
918
|
-
const requestBody =
|
|
1172
|
+
const requestBody = getRequestContent({
|
|
1173
|
+
document,
|
|
1174
|
+
operation,
|
|
1175
|
+
mediaType: options.contentType
|
|
1176
|
+
});
|
|
919
1177
|
if (requestBody === false) return null;
|
|
1178
|
+
const mediaType = Array.isArray(requestBody) ? requestBody[0] : options.contentType;
|
|
920
1179
|
const schema = Array.isArray(requestBody) ? requestBody[1].schema : requestBody.schema;
|
|
1180
|
+
if (mediaType === "application/octet-stream" && (!schema || Object.keys(schema).length === 0)) return {
|
|
1181
|
+
type: "string",
|
|
1182
|
+
contentMediaType: "application/octet-stream"
|
|
1183
|
+
};
|
|
921
1184
|
if (!schema) return null;
|
|
922
1185
|
return dereferenceWithRef(document, schema);
|
|
923
1186
|
}
|
|
924
1187
|
/**
|
|
925
1188
|
* Flattens a keyword-only `allOf` into its parent schema.
|
|
926
1189
|
*
|
|
927
|
-
* Only flattens when every member is a plain fragment
|
|
1190
|
+
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
928
1191
|
* (see `structuralKeys`). Outer schema values take precedence over fragment values.
|
|
929
1192
|
* Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
|
|
930
1193
|
*
|
|
@@ -932,9 +1195,12 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
932
1195
|
* ```ts
|
|
933
1196
|
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
934
1197
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
1198
|
+
* ```
|
|
935
1199
|
*
|
|
1200
|
+
* @example
|
|
1201
|
+
* ```ts
|
|
936
1202
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
937
|
-
* // returned unchanged
|
|
1203
|
+
* // returned unchanged, contains a $ref
|
|
938
1204
|
* ```
|
|
939
1205
|
*/
|
|
940
1206
|
/**
|
|
@@ -950,7 +1216,7 @@ function hasStructuralKeywords(fragment) {
|
|
|
950
1216
|
function flattenSchema(schema) {
|
|
951
1217
|
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
|
|
952
1218
|
const allOfFragments = schema.allOf;
|
|
953
|
-
if (allOfFragments.some((item) => (
|
|
1219
|
+
if (allOfFragments.some((item) => isReference(item))) return schema;
|
|
954
1220
|
if (allOfFragments.some(hasStructuralKeywords)) return schema;
|
|
955
1221
|
const merged = { ...schema };
|
|
956
1222
|
delete merged.allOf;
|
|
@@ -960,7 +1226,7 @@ function flattenSchema(schema) {
|
|
|
960
1226
|
/**
|
|
961
1227
|
* Extracts the inline schema from a media-type `content` map.
|
|
962
1228
|
*
|
|
963
|
-
* Prefers `preferredContentType` when given
|
|
1229
|
+
* Prefers `preferredContentType` when given, otherwise uses the first key in the map.
|
|
964
1230
|
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
965
1231
|
*
|
|
966
1232
|
* @example
|
|
@@ -979,21 +1245,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
979
1245
|
/**
|
|
980
1246
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
981
1247
|
*/
|
|
982
|
-
function collectRefs(schema
|
|
1248
|
+
function* collectRefs(schema) {
|
|
983
1249
|
if (Array.isArray(schema)) {
|
|
984
|
-
for (const item of schema) collectRefs(item
|
|
985
|
-
return
|
|
1250
|
+
for (const item of schema) yield* collectRefs(item);
|
|
1251
|
+
return;
|
|
986
1252
|
}
|
|
987
1253
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
988
1254
|
const value = schema[key];
|
|
989
|
-
if (key === "$ref" && typeof value === "string") {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1255
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
1256
|
+
yield* collectRefs(value);
|
|
1257
|
+
continue;
|
|
1258
|
+
}
|
|
1259
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
1260
|
+
const name = value.slice(21);
|
|
1261
|
+
if (name) yield name;
|
|
1262
|
+
}
|
|
995
1263
|
}
|
|
996
|
-
return refs;
|
|
997
1264
|
}
|
|
998
1265
|
/**
|
|
999
1266
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -1009,7 +1276,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
1009
1276
|
*/
|
|
1010
1277
|
function sortSchemas(schemas) {
|
|
1011
1278
|
const deps = /* @__PURE__ */ new Map();
|
|
1012
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
1279
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
1013
1280
|
const sorted = [];
|
|
1014
1281
|
const visited = /* @__PURE__ */ new Set();
|
|
1015
1282
|
function visit(name, stack) {
|
|
@@ -1030,9 +1297,6 @@ const semanticSuffixes = {
|
|
|
1030
1297
|
responses: "Response",
|
|
1031
1298
|
requestBodies: "Request"
|
|
1032
1299
|
};
|
|
1033
|
-
function getSemanticSuffix(source) {
|
|
1034
|
-
return semanticSuffixes[source];
|
|
1035
|
-
}
|
|
1036
1300
|
function resolveSchemaRef(document, schema) {
|
|
1037
1301
|
if (!isReference(schema)) return schema;
|
|
1038
1302
|
const resolved = resolveRef(document, schema.$ref);
|
|
@@ -1087,7 +1351,7 @@ function getSchemas(document, { contentType }) {
|
|
|
1087
1351
|
}
|
|
1088
1352
|
}
|
|
1089
1353
|
items.forEach((item, index) => {
|
|
1090
|
-
const suffix = isSingle ? "" : hasMultipleSources ?
|
|
1354
|
+
const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index === 0 ? "" : String(index + 1);
|
|
1091
1355
|
const uniqueName = item.originalName + suffix;
|
|
1092
1356
|
schemas[uniqueName] = item.schema;
|
|
1093
1357
|
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
|
|
@@ -1100,7 +1364,7 @@ function getSchemas(document, { contentType }) {
|
|
|
1100
1364
|
}
|
|
1101
1365
|
/**
|
|
1102
1366
|
* Resolves the AST type descriptor for a date/time format, honoring the `dateType` option.
|
|
1103
|
-
* Returns `null` when `dateType: false`,
|
|
1367
|
+
* Returns `null` when `dateType: false`, so the format falls through to `string`.
|
|
1104
1368
|
*/
|
|
1105
1369
|
function getDateType(options, format) {
|
|
1106
1370
|
if (!options.dateType) return null;
|
|
@@ -1134,6 +1398,15 @@ function getDateType(options, format) {
|
|
|
1134
1398
|
/**
|
|
1135
1399
|
* Collects the shared metadata fields passed to every `createSchema` call.
|
|
1136
1400
|
*/
|
|
1401
|
+
/**
|
|
1402
|
+
* Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
|
|
1403
|
+
* labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
|
|
1404
|
+
* 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
|
|
1405
|
+
*/
|
|
1406
|
+
function extractExamples(schema) {
|
|
1407
|
+
if (Array.isArray(schema.examples)) return schema.examples;
|
|
1408
|
+
return schema.example !== void 0 ? [schema.example] : void 0;
|
|
1409
|
+
}
|
|
1137
1410
|
function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
1138
1411
|
return {
|
|
1139
1412
|
name,
|
|
@@ -1144,15 +1417,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1144
1417
|
readOnly: schema.readOnly,
|
|
1145
1418
|
writeOnly: schema.writeOnly,
|
|
1146
1419
|
default: defaultValue,
|
|
1147
|
-
|
|
1420
|
+
examples: extractExamples(schema),
|
|
1421
|
+
format: schema.format
|
|
1148
1422
|
};
|
|
1149
1423
|
}
|
|
1150
1424
|
/**
|
|
1151
1425
|
* Returns all request body content type keys for an operation.
|
|
1152
1426
|
*
|
|
1153
|
-
* The requestBody is dereferenced
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1427
|
+
* The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
|
|
1428
|
+
* `getRequestSchema` already performs), so the returned list accurately reflects the
|
|
1429
|
+
* available content types even for referenced bodies.
|
|
1156
1430
|
*
|
|
1157
1431
|
* @example
|
|
1158
1432
|
* ```ts
|
|
@@ -1166,15 +1440,38 @@ function getRequestBodyContentTypes(document, operation) {
|
|
|
1166
1440
|
if (!body) return [];
|
|
1167
1441
|
return body.content ? Object.keys(body.content) : [];
|
|
1168
1442
|
}
|
|
1443
|
+
/**
|
|
1444
|
+
* Returns all response content type keys for an operation at a given status code.
|
|
1445
|
+
*
|
|
1446
|
+
* Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
|
|
1447
|
+
* so the returned list reflects the available content types even for referenced responses.
|
|
1448
|
+
*
|
|
1449
|
+
* @example
|
|
1450
|
+
* ```ts
|
|
1451
|
+
* getResponseBodyContentTypes(document, operation, 200)
|
|
1452
|
+
* // ['application/json', 'application/xml']
|
|
1453
|
+
* ```
|
|
1454
|
+
*/
|
|
1455
|
+
function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
1456
|
+
resolveResponseRefs(document, operation);
|
|
1457
|
+
const responseObj = getResponseByStatusCode({
|
|
1458
|
+
document,
|
|
1459
|
+
operation,
|
|
1460
|
+
statusCode
|
|
1461
|
+
});
|
|
1462
|
+
if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
|
|
1463
|
+
const body = responseObj;
|
|
1464
|
+
return body.content ? Object.keys(body.content) : [];
|
|
1465
|
+
}
|
|
1169
1466
|
//#endregion
|
|
1170
1467
|
//#region src/parser.ts
|
|
1171
1468
|
/**
|
|
1172
1469
|
* Normalizes malformed `{ type: 'array', enum: [...] }` schemas by moving enum values into items.
|
|
1173
1470
|
*
|
|
1174
1471
|
* This pattern violates the OpenAPI spec but appears in real specs. The fix moves enum values
|
|
1175
|
-
* from the array to its items sub-schema,
|
|
1472
|
+
* from the array to its items sub-schema, so they are valid for downstream processing.
|
|
1176
1473
|
*
|
|
1177
|
-
* @note
|
|
1474
|
+
* @note A defensive measure for non-compliant specs.
|
|
1178
1475
|
*/
|
|
1179
1476
|
function normalizeArrayEnum(schema) {
|
|
1180
1477
|
const normalizedItems = {
|
|
@@ -1188,15 +1485,47 @@ function normalizeArrayEnum(schema) {
|
|
|
1188
1485
|
};
|
|
1189
1486
|
}
|
|
1190
1487
|
/**
|
|
1488
|
+
* Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
|
|
1489
|
+
* and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
|
|
1490
|
+
*/
|
|
1491
|
+
function createNullSchema(schema, name) {
|
|
1492
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1493
|
+
type: "null",
|
|
1494
|
+
primitive: "null",
|
|
1495
|
+
name,
|
|
1496
|
+
title: schema.title,
|
|
1497
|
+
description: schema.description,
|
|
1498
|
+
deprecated: schema.deprecated,
|
|
1499
|
+
format: schema.format
|
|
1500
|
+
});
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Names the inline enums on a property's schema, and on each item when the property is a tuple, from
|
|
1504
|
+
* the parent and property name. Wraps `macroEnumName` at the property construction site.
|
|
1505
|
+
*/
|
|
1506
|
+
function nameEnums(node, options) {
|
|
1507
|
+
const macro = (0, _kubb_ast_macros.macroEnumName)(options);
|
|
1508
|
+
const named = _kubb_core.ast.applyMacros(node, [macro], { depth: "shallow" });
|
|
1509
|
+
const tupleNode = _kubb_core.ast.narrowSchema(named, "tuple");
|
|
1510
|
+
if (tupleNode?.items) {
|
|
1511
|
+
const namedItems = tupleNode.items.map((item) => _kubb_core.ast.applyMacros(item, [macro], { depth: "shallow" }));
|
|
1512
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1513
|
+
...tupleNode,
|
|
1514
|
+
items: namedItems
|
|
1515
|
+
};
|
|
1516
|
+
}
|
|
1517
|
+
return named;
|
|
1518
|
+
}
|
|
1519
|
+
/**
|
|
1191
1520
|
* Factory function that creates schema and operation converters for a given OpenAPI context.
|
|
1192
1521
|
*
|
|
1193
1522
|
* Returns closures that share mutable state (`resolvingRefs` set for cycle detection).
|
|
1194
1523
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1195
|
-
*
|
|
1524
|
+
* which works because function declarations hoist.
|
|
1196
1525
|
*
|
|
1197
|
-
* @
|
|
1526
|
+
* @internal
|
|
1198
1527
|
*/
|
|
1199
|
-
function createSchemaParser(ctx) {
|
|
1528
|
+
function createSchemaParser(ctx, dialect = oasDialect) {
|
|
1200
1529
|
const document = ctx.document;
|
|
1201
1530
|
/**
|
|
1202
1531
|
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
@@ -1204,6 +1533,15 @@ function createSchemaParser(ctx) {
|
|
|
1204
1533
|
*/
|
|
1205
1534
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1206
1535
|
/**
|
|
1536
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1537
|
+
*
|
|
1538
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1539
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1540
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1541
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1542
|
+
*/
|
|
1543
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1544
|
+
/**
|
|
1207
1545
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1208
1546
|
*
|
|
1209
1547
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1212,20 +1550,26 @@ function createSchemaParser(ctx) {
|
|
|
1212
1550
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1213
1551
|
*/
|
|
1214
1552
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1215
|
-
let resolvedSchema;
|
|
1553
|
+
let resolvedSchema = null;
|
|
1216
1554
|
const refPath = schema.$ref;
|
|
1217
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1555
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1556
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1557
|
+
try {
|
|
1558
|
+
const referenced = dialect.schema.resolveRef(document, refPath);
|
|
1559
|
+
if (referenced) {
|
|
1560
|
+
resolvingRefs.add(refPath);
|
|
1561
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1562
|
+
resolvingRefs.delete(refPath);
|
|
1563
|
+
}
|
|
1564
|
+
} catch {}
|
|
1565
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1223
1566
|
}
|
|
1224
|
-
|
|
1225
|
-
|
|
1567
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1568
|
+
}
|
|
1569
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1226
1570
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1227
1571
|
type: "ref",
|
|
1228
|
-
name:
|
|
1572
|
+
name: (0, _kubb_ast_utils.extractRefName)(schema.$ref),
|
|
1229
1573
|
ref: schema.$ref,
|
|
1230
1574
|
schema: resolvedSchema
|
|
1231
1575
|
});
|
|
@@ -1238,12 +1582,12 @@ function createSchemaParser(ctx) {
|
|
|
1238
1582
|
const [memberSchema] = schema.allOf;
|
|
1239
1583
|
const memberNode = parseSchema({
|
|
1240
1584
|
schema: memberSchema,
|
|
1241
|
-
name
|
|
1585
|
+
name
|
|
1242
1586
|
}, rawOptions);
|
|
1243
1587
|
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1244
1588
|
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
1245
1589
|
const mergedDefault = schema.default === null && mergedNullable ? void 0 : schema.default ?? memberNode.default;
|
|
1246
|
-
return _kubb_core.ast.createSchema({
|
|
1590
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1247
1591
|
...memberNodeProps,
|
|
1248
1592
|
name,
|
|
1249
1593
|
title: schema.title ?? memberNode.title,
|
|
@@ -1253,22 +1597,23 @@ function createSchemaParser(ctx) {
|
|
|
1253
1597
|
readOnly: schema.readOnly ?? memberNode.readOnly,
|
|
1254
1598
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1255
1599
|
default: mergedDefault,
|
|
1256
|
-
|
|
1257
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1600
|
+
examples: extractExamples(schema) ?? memberNode.examples,
|
|
1601
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1602
|
+
format: schema.format ?? memberNode.format
|
|
1258
1603
|
});
|
|
1259
1604
|
}
|
|
1260
1605
|
const filteredDiscriminantValues = [];
|
|
1261
1606
|
const allOfMembers = schema.allOf.filter((item) => {
|
|
1262
|
-
if (!isReference(item) || !name) return true;
|
|
1263
|
-
const deref = resolveRef(document, item.$ref);
|
|
1264
|
-
if (!deref || !isDiscriminator(deref)) return true;
|
|
1607
|
+
if (!dialect.schema.isReference(item) || !name) return true;
|
|
1608
|
+
const deref = dialect.schema.resolveRef(document, item.$ref);
|
|
1609
|
+
if (!deref || !dialect.schema.isDiscriminator(deref)) return true;
|
|
1265
1610
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1266
1611
|
if (!parentUnion) return true;
|
|
1267
1612
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1268
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1613
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.schema.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1269
1614
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1270
1615
|
if (inOneOf || inMapping) {
|
|
1271
|
-
const discriminatorValue =
|
|
1616
|
+
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
|
|
1272
1617
|
if (discriminatorValue) filteredDiscriminantValues.push({
|
|
1273
1618
|
propertyName: deref.discriminator.propertyName,
|
|
1274
1619
|
value: discriminatorValue
|
|
@@ -1276,22 +1621,28 @@ function createSchemaParser(ctx) {
|
|
|
1276
1621
|
return false;
|
|
1277
1622
|
}
|
|
1278
1623
|
return true;
|
|
1279
|
-
}).map((s) => parseSchema({
|
|
1624
|
+
}).map((s) => parseSchema({
|
|
1625
|
+
schema: s,
|
|
1626
|
+
name
|
|
1627
|
+
}, rawOptions));
|
|
1280
1628
|
const syntheticStart = allOfMembers.length;
|
|
1281
1629
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1282
1630
|
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1283
1631
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1284
1632
|
if (missingRequired.length) {
|
|
1285
1633
|
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1286
|
-
if (!isReference(item)) return [item];
|
|
1287
|
-
const deref = resolveRef(document, item.$ref);
|
|
1288
|
-
return deref && !isReference(deref) ? [deref] : [];
|
|
1634
|
+
if (!dialect.schema.isReference(item)) return [item];
|
|
1635
|
+
const deref = dialect.schema.resolveRef(document, item.$ref);
|
|
1636
|
+
return deref && !dialect.schema.isReference(deref) ? [deref] : [];
|
|
1289
1637
|
});
|
|
1290
1638
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1291
|
-
allOfMembers.push(parseSchema({
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1639
|
+
allOfMembers.push(parseSchema({
|
|
1640
|
+
schema: {
|
|
1641
|
+
properties: { [key]: resolved.properties[key] },
|
|
1642
|
+
required: [key]
|
|
1643
|
+
},
|
|
1644
|
+
name
|
|
1645
|
+
}, rawOptions));
|
|
1295
1646
|
break;
|
|
1296
1647
|
}
|
|
1297
1648
|
}
|
|
@@ -1300,13 +1651,13 @@ function createSchemaParser(ctx) {
|
|
|
1300
1651
|
const { allOf: _allOf, ...schemaWithoutAllOf } = schema;
|
|
1301
1652
|
allOfMembers.push(parseSchema({ schema: schemaWithoutAllOf }, rawOptions));
|
|
1302
1653
|
}
|
|
1303
|
-
for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(
|
|
1654
|
+
for (const { propertyName, value } of filteredDiscriminantValues) allOfMembers.push(createDiscriminantNode({
|
|
1304
1655
|
propertyName,
|
|
1305
1656
|
value
|
|
1306
1657
|
}));
|
|
1307
|
-
return _kubb_core.ast.createSchema({
|
|
1658
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1308
1659
|
type: "intersection",
|
|
1309
|
-
members: [...
|
|
1660
|
+
members: [...(0, _kubb_ast_utils.mergeAdjacentObjectsLazy)(allOfMembers.slice(0, syntheticStart)), ...(0, _kubb_ast_utils.mergeAdjacentObjectsLazy)(allOfMembers.slice(syntheticStart))],
|
|
1310
1661
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1311
1662
|
});
|
|
1312
1663
|
}
|
|
@@ -1317,7 +1668,7 @@ function createSchemaParser(ctx) {
|
|
|
1317
1668
|
function pickDiscriminatorPropertyNode(node, propertyName) {
|
|
1318
1669
|
const discriminatorProperty = _kubb_core.ast.narrowSchema(node, "object")?.properties?.find((property) => property.name === propertyName);
|
|
1319
1670
|
if (!discriminatorProperty) return null;
|
|
1320
|
-
return _kubb_core.ast.createSchema({
|
|
1671
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1321
1672
|
type: "object",
|
|
1322
1673
|
primitive: "object",
|
|
1323
1674
|
properties: [discriminatorProperty]
|
|
@@ -1327,10 +1678,10 @@ function createSchemaParser(ctx) {
|
|
|
1327
1678
|
const strategy = schema.oneOf ? "one" : "any";
|
|
1328
1679
|
const unionBase = {
|
|
1329
1680
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1330
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1681
|
+
discriminatorPropertyName: dialect.schema.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1331
1682
|
strategy
|
|
1332
1683
|
};
|
|
1333
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1684
|
+
const discriminator = dialect.schema.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1334
1685
|
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1335
1686
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1336
1687
|
return parseSchema({
|
|
@@ -1340,56 +1691,55 @@ function createSchemaParser(ctx) {
|
|
|
1340
1691
|
})() : void 0;
|
|
1341
1692
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1342
1693
|
const members = unionMembers.map((s) => {
|
|
1343
|
-
const ref = isReference(s) ? s.$ref : void 0;
|
|
1344
|
-
const discriminatorValue =
|
|
1345
|
-
const memberNode = parseSchema({
|
|
1694
|
+
const ref = dialect.schema.isReference(s) ? s.$ref : void 0;
|
|
1695
|
+
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref);
|
|
1696
|
+
const memberNode = parseSchema({
|
|
1697
|
+
schema: s,
|
|
1698
|
+
name
|
|
1699
|
+
}, rawOptions);
|
|
1346
1700
|
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1347
|
-
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.
|
|
1348
|
-
node: sharedPropertiesNode,
|
|
1701
|
+
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_ast_macros.macroDiscriminatorEnum)({
|
|
1349
1702
|
propertyName: discriminator.propertyName,
|
|
1350
1703
|
values: [discriminatorValue]
|
|
1351
|
-
}), discriminator.propertyName) : void 0;
|
|
1352
|
-
return _kubb_core.ast.createSchema({
|
|
1704
|
+
})], { depth: "shallow" }), discriminator.propertyName) : void 0;
|
|
1705
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1353
1706
|
type: "intersection",
|
|
1354
|
-
members: [memberNode, narrowedDiscriminatorNode ??
|
|
1707
|
+
members: [memberNode, narrowedDiscriminatorNode ?? createDiscriminantNode({
|
|
1355
1708
|
propertyName: discriminator.propertyName,
|
|
1356
1709
|
value: discriminatorValue
|
|
1357
1710
|
})]
|
|
1358
1711
|
});
|
|
1359
1712
|
});
|
|
1360
|
-
const unionNode = _kubb_core.ast.createSchema({
|
|
1713
|
+
const unionNode = _kubb_core.ast.factory.createSchema({
|
|
1361
1714
|
type: "union",
|
|
1362
1715
|
...unionBase,
|
|
1363
1716
|
members
|
|
1364
1717
|
});
|
|
1365
1718
|
if (!sharedPropertiesNode) return unionNode;
|
|
1366
|
-
return _kubb_core.ast.createSchema({
|
|
1719
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1367
1720
|
type: "intersection",
|
|
1368
1721
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1369
1722
|
members: [unionNode, sharedPropertiesNode]
|
|
1370
1723
|
});
|
|
1371
1724
|
}
|
|
1372
|
-
|
|
1725
|
+
const unionNode = _kubb_core.ast.factory.createSchema({
|
|
1373
1726
|
type: "union",
|
|
1374
1727
|
...unionBase,
|
|
1375
|
-
members:
|
|
1728
|
+
members: unionMembers.map((s) => parseSchema({
|
|
1729
|
+
schema: s,
|
|
1730
|
+
name
|
|
1731
|
+
}, rawOptions))
|
|
1376
1732
|
});
|
|
1733
|
+
return _kubb_core.ast.applyMacros(unionNode, [_kubb_ast_macros.macroSimplifyUnion], { depth: "shallow" });
|
|
1377
1734
|
}
|
|
1378
1735
|
/**
|
|
1379
1736
|
* Converts an OAS 3.1 `const` schema into a null scalar or a single-value `EnumSchemaNode`.
|
|
1380
1737
|
*/
|
|
1381
1738
|
function convertConst({ schema, name, nullable, defaultValue }) {
|
|
1382
1739
|
const constValue = schema.const;
|
|
1383
|
-
if (constValue === null) return
|
|
1384
|
-
type: "null",
|
|
1385
|
-
primitive: "null",
|
|
1386
|
-
name,
|
|
1387
|
-
title: schema.title,
|
|
1388
|
-
description: schema.description,
|
|
1389
|
-
deprecated: schema.deprecated
|
|
1390
|
-
});
|
|
1740
|
+
if (constValue === null) return createNullSchema(schema, name);
|
|
1391
1741
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1392
|
-
return _kubb_core.ast.createSchema({
|
|
1742
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1393
1743
|
type: "enum",
|
|
1394
1744
|
primitive: constPrimitive,
|
|
1395
1745
|
enumValues: [constValue],
|
|
@@ -1402,7 +1752,7 @@ function createSchemaParser(ctx) {
|
|
|
1402
1752
|
*/
|
|
1403
1753
|
function convertFormat({ schema, name, nullable, defaultValue, options }) {
|
|
1404
1754
|
const base = buildSchemaNode(schema, name, nullable, defaultValue);
|
|
1405
|
-
if (schema.format === "int64") return _kubb_core.ast.createSchema({
|
|
1755
|
+
if (schema.format === "int64") return _kubb_core.ast.factory.createSchema({
|
|
1406
1756
|
type: options.integerType === "bigint" ? "bigint" : "integer",
|
|
1407
1757
|
primitive: "integer",
|
|
1408
1758
|
...base,
|
|
@@ -1414,14 +1764,14 @@ function createSchemaParser(ctx) {
|
|
|
1414
1764
|
if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") {
|
|
1415
1765
|
const dateType = getDateType(options, schema.format);
|
|
1416
1766
|
if (!dateType) return null;
|
|
1417
|
-
if (dateType.type === "datetime") return _kubb_core.ast.createSchema({
|
|
1767
|
+
if (dateType.type === "datetime") return _kubb_core.ast.factory.createSchema({
|
|
1418
1768
|
...base,
|
|
1419
1769
|
primitive: "string",
|
|
1420
1770
|
type: "datetime",
|
|
1421
1771
|
offset: dateType.offset,
|
|
1422
1772
|
local: dateType.local
|
|
1423
1773
|
});
|
|
1424
|
-
return _kubb_core.ast.createSchema({
|
|
1774
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1425
1775
|
...base,
|
|
1426
1776
|
primitive: "string",
|
|
1427
1777
|
type: dateType.type,
|
|
@@ -1431,36 +1781,36 @@ function createSchemaParser(ctx) {
|
|
|
1431
1781
|
const specialType = getSchemaType(schema.format);
|
|
1432
1782
|
if (!specialType) return null;
|
|
1433
1783
|
const specialPrimitive = specialType === "number" || specialType === "integer" || specialType === "bigint" ? specialType : "string";
|
|
1434
|
-
if (specialType === "number" || specialType === "integer" || specialType === "bigint") return _kubb_core.ast.createSchema({
|
|
1784
|
+
if (specialType === "number" || specialType === "integer" || specialType === "bigint") return _kubb_core.ast.factory.createSchema({
|
|
1435
1785
|
...base,
|
|
1436
1786
|
primitive: specialPrimitive,
|
|
1437
1787
|
type: specialType
|
|
1438
1788
|
});
|
|
1439
|
-
if (specialType === "url") return _kubb_core.ast.createSchema({
|
|
1789
|
+
if (specialType === "url") return _kubb_core.ast.factory.createSchema({
|
|
1440
1790
|
...base,
|
|
1441
1791
|
primitive: "string",
|
|
1442
1792
|
type: "url",
|
|
1443
1793
|
min: schema.minLength,
|
|
1444
1794
|
max: schema.maxLength
|
|
1445
1795
|
});
|
|
1446
|
-
if (specialType === "ipv4") return _kubb_core.ast.createSchema({
|
|
1796
|
+
if (specialType === "ipv4") return _kubb_core.ast.factory.createSchema({
|
|
1447
1797
|
...base,
|
|
1448
1798
|
primitive: "string",
|
|
1449
1799
|
type: "ipv4"
|
|
1450
1800
|
});
|
|
1451
|
-
if (specialType === "ipv6") return _kubb_core.ast.createSchema({
|
|
1801
|
+
if (specialType === "ipv6") return _kubb_core.ast.factory.createSchema({
|
|
1452
1802
|
...base,
|
|
1453
1803
|
primitive: "string",
|
|
1454
1804
|
type: "ipv6"
|
|
1455
1805
|
});
|
|
1456
|
-
if (specialType === "uuid" || specialType === "email") return _kubb_core.ast.createSchema({
|
|
1806
|
+
if (specialType === "uuid" || specialType === "email") return _kubb_core.ast.factory.createSchema({
|
|
1457
1807
|
...base,
|
|
1458
1808
|
primitive: "string",
|
|
1459
1809
|
type: specialType,
|
|
1460
1810
|
min: schema.minLength,
|
|
1461
1811
|
max: schema.maxLength
|
|
1462
1812
|
});
|
|
1463
|
-
return _kubb_core.ast.createSchema({
|
|
1813
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1464
1814
|
...base,
|
|
1465
1815
|
primitive: specialPrimitive,
|
|
1466
1816
|
type: specialType
|
|
@@ -1476,6 +1826,7 @@ function createSchemaParser(ctx) {
|
|
|
1476
1826
|
}, rawOptions);
|
|
1477
1827
|
const nullInEnum = schema.enum.includes(null);
|
|
1478
1828
|
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1829
|
+
if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
|
|
1479
1830
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
1480
1831
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1481
1832
|
const enumPrimitive = getPrimitiveType(type);
|
|
@@ -1490,7 +1841,8 @@ function createSchemaParser(ctx) {
|
|
|
1490
1841
|
readOnly: schema.readOnly,
|
|
1491
1842
|
writeOnly: schema.writeOnly,
|
|
1492
1843
|
default: enumDefault,
|
|
1493
|
-
|
|
1844
|
+
examples: extractExamples(schema),
|
|
1845
|
+
format: schema.format
|
|
1494
1846
|
};
|
|
1495
1847
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1496
1848
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1498,7 +1850,7 @@ function createSchemaParser(ctx) {
|
|
|
1498
1850
|
const rawEnumNames = extensionKey ? schema[extensionKey] : void 0;
|
|
1499
1851
|
const uniqueValues = [...new Set(filteredValues)];
|
|
1500
1852
|
const seenNames = /* @__PURE__ */ new Set();
|
|
1501
|
-
return _kubb_core.ast.createSchema({
|
|
1853
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1502
1854
|
...enumBase,
|
|
1503
1855
|
primitive: enumPrimitiveType,
|
|
1504
1856
|
namedEnumValues: uniqueValues.map((value, index) => ({
|
|
@@ -1512,7 +1864,7 @@ function createSchemaParser(ctx) {
|
|
|
1512
1864
|
})
|
|
1513
1865
|
});
|
|
1514
1866
|
}
|
|
1515
|
-
return _kubb_core.ast.createSchema({
|
|
1867
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1516
1868
|
...enumBase,
|
|
1517
1869
|
enumValues: [...new Set(filteredValues)]
|
|
1518
1870
|
});
|
|
@@ -1524,21 +1876,16 @@ function createSchemaParser(ctx) {
|
|
|
1524
1876
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1525
1877
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1526
1878
|
const resolvedPropSchema = propSchema;
|
|
1527
|
-
const propNullable = isNullable(resolvedPropSchema);
|
|
1528
|
-
const
|
|
1879
|
+
const propNullable = dialect.schema.isNullable(resolvedPropSchema);
|
|
1880
|
+
const schemaNode = nameEnums(parseSchema({
|
|
1529
1881
|
schema: resolvedPropSchema,
|
|
1530
|
-
name:
|
|
1531
|
-
}, rawOptions)
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
...tupleNode,
|
|
1538
|
-
items: namedItems
|
|
1539
|
-
};
|
|
1540
|
-
}
|
|
1541
|
-
return _kubb_core.ast.createProperty({
|
|
1882
|
+
name: (0, _kubb_ast_utils.childName)(name, propName)
|
|
1883
|
+
}, rawOptions), {
|
|
1884
|
+
parentName: name,
|
|
1885
|
+
propName,
|
|
1886
|
+
enumSuffix: options.enumSuffix
|
|
1887
|
+
});
|
|
1888
|
+
return _kubb_core.ast.factory.createProperty({
|
|
1542
1889
|
name: propName,
|
|
1543
1890
|
schema: {
|
|
1544
1891
|
...schemaNode,
|
|
@@ -1548,14 +1895,15 @@ function createSchemaParser(ctx) {
|
|
|
1548
1895
|
});
|
|
1549
1896
|
}) : [];
|
|
1550
1897
|
const additionalProperties = schema.additionalProperties;
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1898
|
+
const additionalPropertiesNode = (() => {
|
|
1899
|
+
if (additionalProperties === true) return true;
|
|
1900
|
+
if (additionalProperties === false) return false;
|
|
1901
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1902
|
+
if (additionalProperties) return _kubb_core.ast.factory.createSchema({ type: options.unknownType });
|
|
1903
|
+
})();
|
|
1556
1904
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1557
|
-
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_core.ast.createSchema({ type:
|
|
1558
|
-
const objectNode = _kubb_core.ast.createSchema({
|
|
1905
|
+
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_core.ast.factory.createSchema({ type: options.unknownType }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1906
|
+
const objectNode = _kubb_core.ast.factory.createSchema({
|
|
1559
1907
|
type: "object",
|
|
1560
1908
|
primitive: "object",
|
|
1561
1909
|
properties,
|
|
@@ -1565,16 +1913,15 @@ function createSchemaParser(ctx) {
|
|
|
1565
1913
|
maxProperties: schema.maxProperties,
|
|
1566
1914
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1567
1915
|
});
|
|
1568
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1916
|
+
if (dialect.schema.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1569
1917
|
const discPropName = schema.discriminator.propertyName;
|
|
1570
1918
|
const values = Object.keys(schema.discriminator.mapping);
|
|
1571
|
-
const enumName = name ?
|
|
1572
|
-
return _kubb_core.ast.
|
|
1573
|
-
node: objectNode,
|
|
1919
|
+
const enumName = name ? (0, _kubb_ast_utils.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
|
|
1920
|
+
return _kubb_core.ast.applyMacros(objectNode, [(0, _kubb_ast_macros.macroDiscriminatorEnum)({
|
|
1574
1921
|
propertyName: discPropName,
|
|
1575
1922
|
values,
|
|
1576
1923
|
enumName
|
|
1577
|
-
});
|
|
1924
|
+
})], { depth: "shallow" });
|
|
1578
1925
|
}
|
|
1579
1926
|
return objectNode;
|
|
1580
1927
|
}
|
|
@@ -1583,8 +1930,8 @@ function createSchemaParser(ctx) {
|
|
|
1583
1930
|
*/
|
|
1584
1931
|
function convertTuple({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1585
1932
|
const tupleItems = (schema.prefixItems ?? []).map((item) => parseSchema({ schema: item }, rawOptions));
|
|
1586
|
-
const rest = schema.items ? parseSchema({ schema: schema.items }, rawOptions) : _kubb_core.ast.createSchema({ type: "any" });
|
|
1587
|
-
return _kubb_core.ast.createSchema({
|
|
1933
|
+
const rest = schema.items ? parseSchema({ schema: schema.items }, rawOptions) : _kubb_core.ast.factory.createSchema({ type: "any" });
|
|
1934
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1588
1935
|
type: "tuple",
|
|
1589
1936
|
primitive: "array",
|
|
1590
1937
|
items: tupleItems,
|
|
@@ -1599,12 +1946,12 @@ function createSchemaParser(ctx) {
|
|
|
1599
1946
|
*/
|
|
1600
1947
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1601
1948
|
const rawItems = schema.items;
|
|
1602
|
-
const itemName = rawItems?.enum?.length && name ?
|
|
1949
|
+
const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast_utils.enumPropName)(null, name, options.enumSuffix) : name;
|
|
1603
1950
|
const items = rawItems ? [parseSchema({
|
|
1604
1951
|
schema: rawItems,
|
|
1605
1952
|
name: itemName
|
|
1606
1953
|
}, rawOptions)] : [];
|
|
1607
|
-
return _kubb_core.ast.createSchema({
|
|
1954
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1608
1955
|
type: "array",
|
|
1609
1956
|
primitive: "array",
|
|
1610
1957
|
items,
|
|
@@ -1618,7 +1965,7 @@ function createSchemaParser(ctx) {
|
|
|
1618
1965
|
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
1619
1966
|
*/
|
|
1620
1967
|
function convertString({ schema, name, nullable, defaultValue }) {
|
|
1621
|
-
return _kubb_core.ast.createSchema({
|
|
1968
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1622
1969
|
type: "string",
|
|
1623
1970
|
primitive: "string",
|
|
1624
1971
|
min: schema.minLength,
|
|
@@ -1631,7 +1978,7 @@ function createSchemaParser(ctx) {
|
|
|
1631
1978
|
* Converts a `type: 'number'` or `type: 'integer'` schema.
|
|
1632
1979
|
*/
|
|
1633
1980
|
function convertNumeric({ schema, name, nullable, defaultValue }, type) {
|
|
1634
|
-
return _kubb_core.ast.createSchema({
|
|
1981
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1635
1982
|
type,
|
|
1636
1983
|
primitive: type,
|
|
1637
1984
|
min: schema.minimum,
|
|
@@ -1646,7 +1993,7 @@ function createSchemaParser(ctx) {
|
|
|
1646
1993
|
* Converts a `type: 'boolean'` schema.
|
|
1647
1994
|
*/
|
|
1648
1995
|
function convertBoolean({ schema, name, nullable, defaultValue }) {
|
|
1649
|
-
return _kubb_core.ast.createSchema({
|
|
1996
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1650
1997
|
type: "boolean",
|
|
1651
1998
|
primitive: "boolean",
|
|
1652
1999
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
@@ -1656,22 +2003,155 @@ function createSchemaParser(ctx) {
|
|
|
1656
2003
|
* Converts an explicit `type: 'null'` schema.
|
|
1657
2004
|
*/
|
|
1658
2005
|
function convertNull({ schema, name, nullable }) {
|
|
1659
|
-
return _kubb_core.ast.createSchema({
|
|
2006
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1660
2007
|
type: "null",
|
|
1661
2008
|
primitive: "null",
|
|
1662
2009
|
name,
|
|
1663
2010
|
title: schema.title,
|
|
1664
2011
|
description: schema.description,
|
|
1665
2012
|
deprecated: schema.deprecated,
|
|
1666
|
-
nullable
|
|
2013
|
+
nullable,
|
|
2014
|
+
format: schema.format
|
|
2015
|
+
});
|
|
2016
|
+
}
|
|
2017
|
+
/**
|
|
2018
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
2019
|
+
* into a `blob` node.
|
|
2020
|
+
*/
|
|
2021
|
+
function convertBlob({ schema, name, nullable, defaultValue }) {
|
|
2022
|
+
return _kubb_core.ast.factory.createSchema({
|
|
2023
|
+
type: "blob",
|
|
2024
|
+
primitive: "string",
|
|
2025
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
2026
|
+
});
|
|
2027
|
+
}
|
|
2028
|
+
/**
|
|
2029
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
2030
|
+
*
|
|
2031
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
|
|
2032
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
2033
|
+
*/
|
|
2034
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
2035
|
+
const types = schema.type;
|
|
2036
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
2037
|
+
if (nonNullTypes.length <= 1) return null;
|
|
2038
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
2039
|
+
return _kubb_core.ast.factory.createSchema({
|
|
2040
|
+
type: "union",
|
|
2041
|
+
members: nonNullTypes.map((t) => parseSchema({
|
|
2042
|
+
schema: {
|
|
2043
|
+
...schema,
|
|
2044
|
+
type: t
|
|
2045
|
+
},
|
|
2046
|
+
name
|
|
2047
|
+
}, rawOptions)),
|
|
2048
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1667
2049
|
});
|
|
1668
2050
|
}
|
|
1669
2051
|
/**
|
|
1670
|
-
*
|
|
2052
|
+
* Ordered schema rule table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
2053
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
2054
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
2055
|
+
* match/convert/fall-through contract.
|
|
2056
|
+
*/
|
|
2057
|
+
const schemaRules = [
|
|
2058
|
+
{
|
|
2059
|
+
name: "ref",
|
|
2060
|
+
match: ({ schema }) => dialect.schema.isReference(schema),
|
|
2061
|
+
convert: convertRef
|
|
2062
|
+
},
|
|
2063
|
+
{
|
|
2064
|
+
name: "allOf",
|
|
2065
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
2066
|
+
convert: convertAllOf
|
|
2067
|
+
},
|
|
2068
|
+
{
|
|
2069
|
+
name: "union",
|
|
2070
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
2071
|
+
convert: convertUnion
|
|
2072
|
+
},
|
|
2073
|
+
{
|
|
2074
|
+
name: "const",
|
|
2075
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
2076
|
+
convert: convertConst
|
|
2077
|
+
},
|
|
2078
|
+
{
|
|
2079
|
+
name: "format",
|
|
2080
|
+
match: ({ schema }) => !!schema.format,
|
|
2081
|
+
convert: convertFormat
|
|
2082
|
+
},
|
|
2083
|
+
{
|
|
2084
|
+
name: "blob",
|
|
2085
|
+
match: ({ schema }) => dialect.schema.isBinary(schema),
|
|
2086
|
+
convert: convertBlob
|
|
2087
|
+
},
|
|
2088
|
+
{
|
|
2089
|
+
name: "multi-type",
|
|
2090
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
2091
|
+
convert: convertMultiType
|
|
2092
|
+
},
|
|
2093
|
+
{
|
|
2094
|
+
name: "constrained-string",
|
|
2095
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
2096
|
+
convert: convertString
|
|
2097
|
+
},
|
|
2098
|
+
{
|
|
2099
|
+
name: "constrained-number",
|
|
2100
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
2101
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
2102
|
+
},
|
|
2103
|
+
{
|
|
2104
|
+
name: "enum",
|
|
2105
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
2106
|
+
convert: convertEnum
|
|
2107
|
+
},
|
|
2108
|
+
{
|
|
2109
|
+
name: "object",
|
|
2110
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
2111
|
+
convert: convertObject
|
|
2112
|
+
},
|
|
2113
|
+
{
|
|
2114
|
+
name: "tuple",
|
|
2115
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
2116
|
+
convert: convertTuple
|
|
2117
|
+
},
|
|
2118
|
+
{
|
|
2119
|
+
name: "array",
|
|
2120
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
2121
|
+
convert: convertArray
|
|
2122
|
+
},
|
|
2123
|
+
{
|
|
2124
|
+
name: "string",
|
|
2125
|
+
match: ({ type }) => type === "string",
|
|
2126
|
+
convert: convertString
|
|
2127
|
+
},
|
|
2128
|
+
{
|
|
2129
|
+
name: "number",
|
|
2130
|
+
match: ({ type }) => type === "number",
|
|
2131
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
name: "integer",
|
|
2135
|
+
match: ({ type }) => type === "integer",
|
|
2136
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
2137
|
+
},
|
|
2138
|
+
{
|
|
2139
|
+
name: "boolean",
|
|
2140
|
+
match: ({ type }) => type === "boolean",
|
|
2141
|
+
convert: convertBoolean
|
|
2142
|
+
},
|
|
2143
|
+
{
|
|
2144
|
+
name: "null",
|
|
2145
|
+
match: ({ type }) => type === "null",
|
|
2146
|
+
convert: convertNull
|
|
2147
|
+
}
|
|
2148
|
+
];
|
|
2149
|
+
/**
|
|
2150
|
+
* Converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1671
2151
|
*
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
2152
|
+
* Builds the per-schema context, then walks the ordered {@link schemaRules} table and returns
|
|
2153
|
+
* the first converter that produces a node. When none match, falls back to the configured
|
|
2154
|
+
* `emptySchemaType`.
|
|
1675
2155
|
*/
|
|
1676
2156
|
function parseSchema({ schema, name }, rawOptions) {
|
|
1677
2157
|
const options = {
|
|
@@ -1683,75 +2163,43 @@ function createSchemaParser(ctx) {
|
|
|
1683
2163
|
schema: flattenedSchema,
|
|
1684
2164
|
name
|
|
1685
2165
|
}, rawOptions);
|
|
1686
|
-
const nullable = isNullable(schema) || void 0;
|
|
1687
|
-
const
|
|
1688
|
-
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
1689
|
-
const ctx = {
|
|
2166
|
+
const nullable = dialect.schema.isNullable(schema) || void 0;
|
|
2167
|
+
const schemaCtx = {
|
|
1690
2168
|
schema,
|
|
1691
2169
|
name,
|
|
1692
2170
|
nullable,
|
|
1693
|
-
defaultValue,
|
|
1694
|
-
type,
|
|
2171
|
+
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
2172
|
+
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1695
2173
|
rawOptions,
|
|
1696
2174
|
options
|
|
1697
2175
|
};
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
if (schema.format) {
|
|
1703
|
-
const formatResult = convertFormat(ctx);
|
|
1704
|
-
if (formatResult) return formatResult;
|
|
1705
|
-
}
|
|
1706
|
-
if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return _kubb_core.ast.createSchema({
|
|
1707
|
-
type: "blob",
|
|
1708
|
-
primitive: "string",
|
|
1709
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1710
|
-
});
|
|
1711
|
-
if (Array.isArray(schema.type) && schema.type.length > 1) {
|
|
1712
|
-
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
1713
|
-
const arrayNullable = schema.type.includes("null") || nullable || void 0;
|
|
1714
|
-
if (nonNullTypes.length > 1) return _kubb_core.ast.createSchema({
|
|
1715
|
-
type: "union",
|
|
1716
|
-
members: nonNullTypes.map((t) => parseSchema({
|
|
1717
|
-
schema: {
|
|
1718
|
-
...schema,
|
|
1719
|
-
type: t
|
|
1720
|
-
},
|
|
1721
|
-
name
|
|
1722
|
-
}, rawOptions)),
|
|
1723
|
-
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1726
|
-
if (!type) {
|
|
1727
|
-
if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
|
|
1728
|
-
if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
|
|
2176
|
+
for (const rule of schemaRules) {
|
|
2177
|
+
if (!rule.match(schemaCtx)) continue;
|
|
2178
|
+
const node = rule.convert(schemaCtx);
|
|
2179
|
+
if (node) return node;
|
|
1729
2180
|
}
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
if ("prefixItems" in schema) return convertTuple(ctx);
|
|
1733
|
-
if (type === "array" || "items" in schema) return convertArray(ctx);
|
|
1734
|
-
if (type === "string") return convertString(ctx);
|
|
1735
|
-
if (type === "number") return convertNumeric(ctx, "number");
|
|
1736
|
-
if (type === "integer") return convertNumeric(ctx, "integer");
|
|
1737
|
-
if (type === "boolean") return convertBoolean(ctx);
|
|
1738
|
-
if (type === "null") return convertNull(ctx);
|
|
1739
|
-
const emptyType = typeOptionMap.get(options.emptySchemaType);
|
|
1740
|
-
return _kubb_core.ast.createSchema({
|
|
2181
|
+
const emptyType = options.emptySchemaType;
|
|
2182
|
+
return _kubb_core.ast.factory.createSchema({
|
|
1741
2183
|
type: emptyType,
|
|
1742
2184
|
name,
|
|
1743
2185
|
title: schema.title,
|
|
1744
|
-
description: schema.description
|
|
2186
|
+
description: schema.description,
|
|
2187
|
+
format: schema.format
|
|
1745
2188
|
});
|
|
1746
2189
|
}
|
|
1747
2190
|
/**
|
|
1748
2191
|
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
1749
2192
|
*/
|
|
1750
|
-
function parseParameter(options, param) {
|
|
2193
|
+
function parseParameter(options, param, parentName) {
|
|
1751
2194
|
const required = param["required"] ?? false;
|
|
1752
|
-
const
|
|
1753
|
-
|
|
1754
|
-
|
|
2195
|
+
const paramName = param["name"];
|
|
2196
|
+
const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
|
|
2197
|
+
const schema = param["schema"] ? parseSchema({
|
|
2198
|
+
schema: param["schema"],
|
|
2199
|
+
name: schemaName
|
|
2200
|
+
}, options) : _kubb_core.ast.factory.createSchema({ type: options.unknownType });
|
|
2201
|
+
return _kubb_core.ast.factory.createParameter({
|
|
2202
|
+
name: paramName,
|
|
1755
2203
|
in: param["in"],
|
|
1756
2204
|
schema: {
|
|
1757
2205
|
...schema,
|
|
@@ -1788,58 +2236,93 @@ function createSchemaParser(ctx) {
|
|
|
1788
2236
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1789
2237
|
*/
|
|
1790
2238
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1791
|
-
if (!schema?.properties) return
|
|
2239
|
+
if (!schema?.properties) return null;
|
|
1792
2240
|
const keys = [];
|
|
1793
2241
|
for (const key in schema.properties) {
|
|
1794
2242
|
const prop = schema.properties[key];
|
|
1795
|
-
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
2243
|
+
if (prop && !dialect.schema.isReference(prop) && prop[flag]) keys.push(key);
|
|
1796
2244
|
}
|
|
1797
|
-
return keys.length ? keys :
|
|
2245
|
+
return keys.length ? keys : null;
|
|
1798
2246
|
}
|
|
1799
2247
|
/**
|
|
1800
2248
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1801
2249
|
*/
|
|
1802
2250
|
function parseOperation(options, operation) {
|
|
1803
|
-
const
|
|
2251
|
+
const operationId = getOperationId(operation);
|
|
2252
|
+
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
2253
|
+
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
|
|
1804
2254
|
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
|
|
1805
2255
|
const requestBodyMeta = getRequestBodyMeta(operation);
|
|
2256
|
+
const requestBodyName = operationName ? `${operationName}Request` : void 0;
|
|
1806
2257
|
const content = allContentTypes.flatMap((ct) => {
|
|
1807
2258
|
const schema = getRequestSchema(document, operation, { contentType: ct });
|
|
1808
2259
|
if (!schema) return [];
|
|
1809
|
-
return [{
|
|
2260
|
+
return [_kubb_core.ast.factory.createContent({
|
|
1810
2261
|
contentType: ct,
|
|
1811
|
-
schema: _kubb_core.ast.
|
|
2262
|
+
schema: _kubb_core.ast.optionality(parseSchema({
|
|
2263
|
+
schema,
|
|
2264
|
+
name: requestBodyName
|
|
2265
|
+
}, options), requestBodyMeta.required),
|
|
1812
2266
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1813
|
-
}];
|
|
2267
|
+
})];
|
|
1814
2268
|
});
|
|
1815
2269
|
const requestBody = content.length > 0 || requestBodyMeta.description ? {
|
|
1816
2270
|
description: requestBodyMeta.description,
|
|
1817
2271
|
required: requestBodyMeta.required || void 0,
|
|
1818
2272
|
content: content.length > 0 ? content : void 0
|
|
1819
2273
|
} : void 0;
|
|
1820
|
-
const responses =
|
|
1821
|
-
const responseObj =
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
2274
|
+
const responses = getResponseStatusCodes(operation).map((statusCode) => {
|
|
2275
|
+
const responseObj = getResponseByStatusCode({
|
|
2276
|
+
document,
|
|
2277
|
+
operation,
|
|
2278
|
+
statusCode
|
|
2279
|
+
});
|
|
2280
|
+
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
2281
|
+
const { description } = getResponseMeta(responseObj);
|
|
2282
|
+
const parseEntrySchema = (contentType) => {
|
|
2283
|
+
const raw = getResponseSchema(document, operation, statusCode, { contentType });
|
|
2284
|
+
return {
|
|
2285
|
+
schema: raw && Object.keys(raw).length > 0 ? parseSchema({
|
|
2286
|
+
schema: raw,
|
|
2287
|
+
name: responseName
|
|
2288
|
+
}, options) : _kubb_core.ast.factory.createSchema({ type: options.emptySchemaType }),
|
|
2289
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
2290
|
+
};
|
|
2291
|
+
};
|
|
2292
|
+
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => _kubb_core.ast.factory.createContent({
|
|
2293
|
+
contentType,
|
|
2294
|
+
...parseEntrySchema(contentType)
|
|
2295
|
+
}));
|
|
2296
|
+
if (content.length === 0) content.push(_kubb_core.ast.factory.createContent({
|
|
2297
|
+
contentType: getRequestContentType({
|
|
2298
|
+
document,
|
|
2299
|
+
operation
|
|
2300
|
+
}) || "application/json",
|
|
2301
|
+
...parseEntrySchema(ctx.contentType)
|
|
2302
|
+
}));
|
|
2303
|
+
return _kubb_core.ast.factory.createResponse({
|
|
1827
2304
|
statusCode,
|
|
1828
2305
|
description,
|
|
1829
|
-
|
|
1830
|
-
mediaType,
|
|
1831
|
-
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
2306
|
+
content
|
|
1832
2307
|
});
|
|
1833
2308
|
});
|
|
1834
|
-
const
|
|
1835
|
-
|
|
1836
|
-
|
|
2309
|
+
const pathItem = document.paths?.[operation.path];
|
|
2310
|
+
const pathItemDoc = pathItem && !dialect.schema.isReference(pathItem) ? pathItem : void 0;
|
|
2311
|
+
const pickDoc = (key) => {
|
|
2312
|
+
const own = operation.schema[key];
|
|
2313
|
+
if (typeof own === "string") return own;
|
|
2314
|
+
const fallback = pathItemDoc?.[key];
|
|
2315
|
+
return typeof fallback === "string" ? fallback : void 0;
|
|
2316
|
+
};
|
|
2317
|
+
return _kubb_core.ast.factory.createOperation({
|
|
2318
|
+
operationId,
|
|
2319
|
+
protocol: "http",
|
|
1837
2320
|
method: operation.method.toUpperCase(),
|
|
1838
|
-
path:
|
|
1839
|
-
tags: operation.
|
|
1840
|
-
summary:
|
|
1841
|
-
description:
|
|
1842
|
-
deprecated: operation.
|
|
2321
|
+
path: operation.path,
|
|
2322
|
+
tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
|
|
2323
|
+
summary: pickDoc("summary") || void 0,
|
|
2324
|
+
description: pickDoc("description") || void 0,
|
|
2325
|
+
deprecated: operation.schema.deprecated || void 0,
|
|
1843
2326
|
parameters,
|
|
1844
2327
|
requestBody,
|
|
1845
2328
|
responses
|
|
@@ -1851,59 +2334,264 @@ function createSchemaParser(ctx) {
|
|
|
1851
2334
|
parseParameter
|
|
1852
2335
|
};
|
|
1853
2336
|
}
|
|
2337
|
+
//#endregion
|
|
2338
|
+
//#region src/promoteEnums.ts
|
|
2339
|
+
/**
|
|
2340
|
+
* Collects inline enums to lift to the top level, keyed by the name the parser derived for them
|
|
2341
|
+
* (e.g. `PetStatusEnum`). An enum already defined as a top-level component is left as-is, and a
|
|
2342
|
+
* name that recurs maps to the first definition so each name yields one shared type.
|
|
2343
|
+
*/
|
|
2344
|
+
function collectInlineEnums(roots, topLevelNames) {
|
|
2345
|
+
const promoted = /* @__PURE__ */ new Map();
|
|
2346
|
+
for (const root of roots) {
|
|
2347
|
+
const isSchemaRoot = root.kind === "Schema";
|
|
2348
|
+
for (const node of _kubb_core.ast.collect(root, { schema: (schemaNode) => schemaNode })) {
|
|
2349
|
+
if (node.type !== "enum" || !node.name) continue;
|
|
2350
|
+
if (isSchemaRoot && node === root) continue;
|
|
2351
|
+
if (topLevelNames.has(node.name)) continue;
|
|
2352
|
+
if (!promoted.has(node.name)) promoted.set(node.name, {
|
|
2353
|
+
...node,
|
|
2354
|
+
optional: void 0,
|
|
2355
|
+
nullish: void 0
|
|
2356
|
+
});
|
|
2357
|
+
}
|
|
2358
|
+
}
|
|
2359
|
+
return promoted;
|
|
2360
|
+
}
|
|
2361
|
+
/**
|
|
2362
|
+
* Replaces every promoted inline enum in `node` with a `ref` to its lifted definition, keeping the
|
|
2363
|
+
* occurrence's usage-slot and documentation fields.
|
|
2364
|
+
*/
|
|
2365
|
+
function refPromotedEnums(node, promoted) {
|
|
2366
|
+
if (promoted.size === 0) return node;
|
|
2367
|
+
return _kubb_core.ast.transform(node, { schema(schemaNode) {
|
|
2368
|
+
if (schemaNode.type !== "enum" || !schemaNode.name || !promoted.has(schemaNode.name)) return void 0;
|
|
2369
|
+
return _kubb_core.ast.factory.createSchema({
|
|
2370
|
+
type: "ref",
|
|
2371
|
+
name: schemaNode.name,
|
|
2372
|
+
ref: `${SCHEMA_REF_PREFIX}${schemaNode.name}`,
|
|
2373
|
+
optional: schemaNode.optional,
|
|
2374
|
+
nullish: schemaNode.nullish,
|
|
2375
|
+
readOnly: schemaNode.readOnly,
|
|
2376
|
+
writeOnly: schemaNode.writeOnly,
|
|
2377
|
+
deprecated: schemaNode.deprecated,
|
|
2378
|
+
description: schemaNode.description,
|
|
2379
|
+
default: schemaNode.default,
|
|
2380
|
+
examples: schemaNode.examples
|
|
2381
|
+
});
|
|
2382
|
+
} });
|
|
2383
|
+
}
|
|
2384
|
+
//#endregion
|
|
2385
|
+
//#region src/schemaDiagnostics.ts
|
|
2386
|
+
/**
|
|
2387
|
+
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
|
|
2388
|
+
* top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
|
|
2389
|
+
* pointer as it descends so a nested field reports against its full path
|
|
2390
|
+
* (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
|
|
2391
|
+
* resolved schema is reported under its own walk. Reports land in the active build run, are a
|
|
2392
|
+
* no-op outside one, and repeats are deduped by the build.
|
|
2393
|
+
*/
|
|
2394
|
+
function reportSchemaDiagnostics({ node, name }) {
|
|
2395
|
+
visit(node, `#/components/schemas/${escapePointerToken(name)}`);
|
|
2396
|
+
}
|
|
2397
|
+
/**
|
|
2398
|
+
* Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
|
|
2399
|
+
* property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
|
|
2400
|
+
*/
|
|
2401
|
+
function escapePointerToken(token) {
|
|
2402
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2403
|
+
}
|
|
2404
|
+
function visit(node, pointer) {
|
|
2405
|
+
if (node.deprecated) _kubb_core.Diagnostics.report({
|
|
2406
|
+
code: _kubb_core.Diagnostics.code.deprecated,
|
|
2407
|
+
severity: "info",
|
|
2408
|
+
message: "This schema is marked as deprecated.",
|
|
2409
|
+
location: {
|
|
2410
|
+
kind: "schema",
|
|
2411
|
+
pointer
|
|
2412
|
+
}
|
|
2413
|
+
});
|
|
2414
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
|
|
2415
|
+
code: _kubb_core.Diagnostics.code.unsupportedFormat,
|
|
2416
|
+
severity: "warning",
|
|
2417
|
+
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
2418
|
+
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
2419
|
+
location: {
|
|
2420
|
+
kind: "schema",
|
|
2421
|
+
pointer
|
|
2422
|
+
}
|
|
2423
|
+
});
|
|
2424
|
+
if (node.type === "object") {
|
|
2425
|
+
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
2426
|
+
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
2427
|
+
return;
|
|
2428
|
+
}
|
|
2429
|
+
if (node.type === "array") {
|
|
2430
|
+
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
2431
|
+
return;
|
|
2432
|
+
}
|
|
2433
|
+
if (node.type === "tuple") {
|
|
2434
|
+
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
2435
|
+
return;
|
|
2436
|
+
}
|
|
2437
|
+
if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2438
|
+
}
|
|
2439
|
+
//#endregion
|
|
2440
|
+
//#region src/stream.ts
|
|
2441
|
+
/**
|
|
2442
|
+
* Reads the server URL from the document's `servers` array at `server.index`,
|
|
2443
|
+
* interpolating any `server.variables` into the URL template.
|
|
2444
|
+
*
|
|
2445
|
+
* Returns `null` when `server.index` is omitted or out of range.
|
|
2446
|
+
*
|
|
2447
|
+
* @example Resolve the first server
|
|
2448
|
+
* `resolveBaseUrl({ document, server: { index: 0 } })`
|
|
2449
|
+
*
|
|
2450
|
+
* @example Override a path variable
|
|
2451
|
+
* `resolveBaseUrl({ document, server: { index: 0, variables: { version: 'v2' } } })`
|
|
2452
|
+
*/
|
|
2453
|
+
function resolveBaseUrl({ document, server }) {
|
|
2454
|
+
const index = server?.index;
|
|
2455
|
+
const entry = index !== void 0 ? document.servers?.at(index) : void 0;
|
|
2456
|
+
return entry?.url ? resolveServerUrl(entry, server?.variables) : null;
|
|
2457
|
+
}
|
|
1854
2458
|
/**
|
|
1855
|
-
* Parses
|
|
2459
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
1856
2460
|
*
|
|
1857
|
-
*
|
|
1858
|
-
*
|
|
1859
|
-
* the
|
|
2461
|
+
* Three things happen in this single pass:
|
|
2462
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
2463
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
2464
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
2465
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
1860
2466
|
*
|
|
1861
|
-
*
|
|
2467
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2468
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
2469
|
+
*
|
|
2470
|
+
* Each schema is parsed again during the streaming pass. This is intentional.
|
|
2471
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1862
2472
|
*
|
|
1863
2473
|
* @example
|
|
1864
2474
|
* ```ts
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
*
|
|
2475
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
2476
|
+
* schemas,
|
|
2477
|
+
* parseSchema,
|
|
2478
|
+
* parserOptions,
|
|
2479
|
+
* discriminator: 'preserve',
|
|
2480
|
+
* })
|
|
1869
2481
|
* ```
|
|
1870
2482
|
*/
|
|
1871
|
-
function
|
|
1872
|
-
const
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2483
|
+
function preScan({ schemas, parseSchema, parseOperation, document, parserOptions, discriminator, enums = "inline" }) {
|
|
2484
|
+
const allNodes = [];
|
|
2485
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2486
|
+
const enumNames = [];
|
|
2487
|
+
const discriminatorParentNodes = [];
|
|
2488
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2489
|
+
const node = parseSchema({
|
|
2490
|
+
schema,
|
|
2491
|
+
name
|
|
2492
|
+
}, parserOptions);
|
|
2493
|
+
allNodes.push(node);
|
|
2494
|
+
reportSchemaDiagnostics({
|
|
2495
|
+
node,
|
|
2496
|
+
name
|
|
2497
|
+
});
|
|
2498
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2499
|
+
if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2500
|
+
if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2501
|
+
}
|
|
2502
|
+
const circularNames = [...(0, _kubb_ast_utils.findCircularSchemas)(allNodes)];
|
|
2503
|
+
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2504
|
+
let promotedEnums = null;
|
|
2505
|
+
if (enums === "root" && document && parseOperation) {
|
|
2506
|
+
const operationNodes = [];
|
|
2507
|
+
for (const operation of getOperations(document)) {
|
|
2508
|
+
const operationNode = parseOperation(parserOptions, operation);
|
|
2509
|
+
if (operationNode) operationNodes.push(operationNode);
|
|
2510
|
+
}
|
|
2511
|
+
promotedEnums = collectInlineEnums([...allNodes, ...operationNodes], new Set(Object.keys(schemas)));
|
|
2512
|
+
for (const name of promotedEnums.keys()) enumNames.push(name);
|
|
2513
|
+
}
|
|
1888
2514
|
return {
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2515
|
+
refAliasMap,
|
|
2516
|
+
enumNames,
|
|
2517
|
+
circularNames,
|
|
2518
|
+
discriminatorChildMap,
|
|
2519
|
+
promotedEnums
|
|
1894
2520
|
};
|
|
1895
2521
|
}
|
|
2522
|
+
/**
|
|
2523
|
+
* Creates a lazy `InputNode<true>` from already-resolved adapter state.
|
|
2524
|
+
*
|
|
2525
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
2526
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
2527
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
2528
|
+
*
|
|
2529
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
2530
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
2531
|
+
*
|
|
2532
|
+
* @example
|
|
2533
|
+
* ```ts
|
|
2534
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2535
|
+
* for await (const schema of streamNode.schemas) {
|
|
2536
|
+
* // each call to for-await restarts from the first schema
|
|
2537
|
+
* }
|
|
2538
|
+
* ```
|
|
2539
|
+
*/
|
|
2540
|
+
function createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, promotedEnums, meta }) {
|
|
2541
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2542
|
+
return (async function* () {
|
|
2543
|
+
if (promotedEnums) for (const definition of promotedEnums.values()) yield definition;
|
|
2544
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2545
|
+
const alias = refAliasMap.get(name);
|
|
2546
|
+
if (alias?.name && schemas[alias.name]) {
|
|
2547
|
+
const aliasNode = {
|
|
2548
|
+
...parseSchema({
|
|
2549
|
+
schema: schemas[alias.name],
|
|
2550
|
+
name: alias.name
|
|
2551
|
+
}, parserOptions),
|
|
2552
|
+
name
|
|
2553
|
+
};
|
|
2554
|
+
yield promotedEnums ? refPromotedEnums(aliasNode, promotedEnums) : aliasNode;
|
|
2555
|
+
continue;
|
|
2556
|
+
}
|
|
2557
|
+
const parsed = parseSchema({
|
|
2558
|
+
schema,
|
|
2559
|
+
name
|
|
2560
|
+
}, parserOptions);
|
|
2561
|
+
const node = discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
2562
|
+
yield promotedEnums ? refPromotedEnums(node, promotedEnums) : node;
|
|
2563
|
+
}
|
|
2564
|
+
})();
|
|
2565
|
+
} };
|
|
2566
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2567
|
+
return (async function* () {
|
|
2568
|
+
for (const operation of getOperations(document)) {
|
|
2569
|
+
const node = parseOperation(parserOptions, operation);
|
|
2570
|
+
if (node) yield promotedEnums ? refPromotedEnums(node, promotedEnums) : node;
|
|
2571
|
+
}
|
|
2572
|
+
})();
|
|
2573
|
+
} };
|
|
2574
|
+
return _kubb_core.ast.factory.createInput({
|
|
2575
|
+
stream: true,
|
|
2576
|
+
schemas: schemasIterable,
|
|
2577
|
+
operations: operationsIterable,
|
|
2578
|
+
meta
|
|
2579
|
+
});
|
|
2580
|
+
}
|
|
1896
2581
|
//#endregion
|
|
1897
2582
|
//#region src/adapter.ts
|
|
1898
2583
|
/**
|
|
1899
|
-
*
|
|
2584
|
+
* The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
|
|
1900
2585
|
*/
|
|
1901
2586
|
const adapterOasName = "oas";
|
|
1902
2587
|
/**
|
|
1903
|
-
*
|
|
2588
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
|
|
2589
|
+
* file at `input.path`, validates it, resolves the base URL, and converts every
|
|
2590
|
+
* schema and operation into the universal AST that every downstream plugin
|
|
2591
|
+
* consumes.
|
|
1904
2592
|
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
2593
|
+
* Configure once on `defineConfig`. The adapter's choices (date representation,
|
|
2594
|
+
* integer width, server URL) apply to every plugin in the build.
|
|
1907
2595
|
*
|
|
1908
2596
|
* @example
|
|
1909
2597
|
* ```ts
|
|
@@ -1912,26 +2600,117 @@ const adapterOasName = "oas";
|
|
|
1912
2600
|
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1913
2601
|
*
|
|
1914
2602
|
* export default defineConfig({
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
2603
|
+
* input: { path: './petStore.yaml' },
|
|
2604
|
+
* output: { path: './src/gen' },
|
|
2605
|
+
* adapter: adapterOas({
|
|
2606
|
+
* server: { index: 0 },
|
|
2607
|
+
* discriminator: 'propagate',
|
|
2608
|
+
* dateType: 'date',
|
|
2609
|
+
* }),
|
|
1917
2610
|
* plugins: [pluginTs()],
|
|
1918
2611
|
* })
|
|
1919
2612
|
* ```
|
|
1920
2613
|
*/
|
|
1921
2614
|
const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
1922
|
-
const { validate = true, contentType,
|
|
2615
|
+
const { validate = true, contentType, server, discriminator = "preserve", enums = "inline", 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;
|
|
2616
|
+
const parserOptions = {
|
|
2617
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2618
|
+
dateType,
|
|
2619
|
+
integerType,
|
|
2620
|
+
unknownType,
|
|
2621
|
+
emptySchemaType,
|
|
2622
|
+
enumSuffix
|
|
2623
|
+
};
|
|
1923
2624
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1924
|
-
let parsedDocument;
|
|
1925
|
-
|
|
2625
|
+
let parsedDocument = null;
|
|
2626
|
+
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2627
|
+
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2628
|
+
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2629
|
+
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2630
|
+
function ensureDocument(source) {
|
|
2631
|
+
const cached = documentCache.get(source);
|
|
2632
|
+
if (cached) return cached;
|
|
2633
|
+
const promise = (async () => {
|
|
2634
|
+
const fresh = await parseFromConfig(source);
|
|
2635
|
+
if (validate) await validateDocument(fresh);
|
|
2636
|
+
parsedDocument = fresh;
|
|
2637
|
+
return fresh;
|
|
2638
|
+
})();
|
|
2639
|
+
documentCache.set(source, promise);
|
|
2640
|
+
return promise;
|
|
2641
|
+
}
|
|
2642
|
+
function ensureSchemas(document) {
|
|
2643
|
+
const cached = schemasCache.get(document);
|
|
2644
|
+
if (cached) return cached;
|
|
2645
|
+
const promise = Promise.resolve().then(() => {
|
|
2646
|
+
const result = getSchemas(document, { contentType });
|
|
2647
|
+
nameMapping = result.nameMapping;
|
|
2648
|
+
return result.schemas;
|
|
2649
|
+
});
|
|
2650
|
+
schemasCache.set(document, promise);
|
|
2651
|
+
return promise;
|
|
2652
|
+
}
|
|
2653
|
+
function ensureSchemaParser(document) {
|
|
2654
|
+
const cached = schemaParserCache.get(document);
|
|
2655
|
+
if (cached) return cached;
|
|
2656
|
+
const parser = createSchemaParser({
|
|
2657
|
+
document,
|
|
2658
|
+
contentType
|
|
2659
|
+
});
|
|
2660
|
+
schemaParserCache.set(document, parser);
|
|
2661
|
+
return parser;
|
|
2662
|
+
}
|
|
2663
|
+
function ensurePreScan(document, schemas, parseSchema, parseOperation) {
|
|
2664
|
+
const cached = preScanCache.get(document);
|
|
2665
|
+
if (cached) return cached;
|
|
2666
|
+
const result = preScan({
|
|
2667
|
+
schemas,
|
|
2668
|
+
parseSchema,
|
|
2669
|
+
parseOperation,
|
|
2670
|
+
document,
|
|
2671
|
+
parserOptions,
|
|
2672
|
+
discriminator,
|
|
2673
|
+
enums
|
|
2674
|
+
});
|
|
2675
|
+
preScanCache.set(document, result);
|
|
2676
|
+
return result;
|
|
2677
|
+
}
|
|
2678
|
+
async function createStream(source) {
|
|
2679
|
+
const document = await ensureDocument(source);
|
|
2680
|
+
const schemas = await ensureSchemas(document);
|
|
2681
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2682
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, promotedEnums } = ensurePreScan(document, schemas, parseSchema, parseOperation);
|
|
2683
|
+
return createInputStream({
|
|
2684
|
+
schemas,
|
|
2685
|
+
parseSchema,
|
|
2686
|
+
parseOperation,
|
|
2687
|
+
document,
|
|
2688
|
+
parserOptions,
|
|
2689
|
+
refAliasMap,
|
|
2690
|
+
discriminatorChildMap,
|
|
2691
|
+
promotedEnums,
|
|
2692
|
+
meta: {
|
|
2693
|
+
title: document.info?.title,
|
|
2694
|
+
description: document.info?.description,
|
|
2695
|
+
version: document.info?.version,
|
|
2696
|
+
baseURL: resolveBaseUrl({
|
|
2697
|
+
document,
|
|
2698
|
+
server
|
|
2699
|
+
}),
|
|
2700
|
+
circularNames,
|
|
2701
|
+
enumNames
|
|
2702
|
+
}
|
|
2703
|
+
});
|
|
2704
|
+
}
|
|
1926
2705
|
return {
|
|
1927
2706
|
name: "oas",
|
|
1928
2707
|
get options() {
|
|
1929
2708
|
return {
|
|
1930
2709
|
validate,
|
|
1931
2710
|
contentType,
|
|
1932
|
-
|
|
1933
|
-
serverVariables,
|
|
2711
|
+
server,
|
|
1934
2712
|
discriminator,
|
|
2713
|
+
enums,
|
|
1935
2714
|
dateType,
|
|
1936
2715
|
integerType,
|
|
1937
2716
|
unknownType,
|
|
@@ -1943,71 +2722,37 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1943
2722
|
get document() {
|
|
1944
2723
|
return parsedDocument;
|
|
1945
2724
|
},
|
|
1946
|
-
get inputNode() {
|
|
1947
|
-
return inputNode;
|
|
1948
|
-
},
|
|
1949
2725
|
async validate(input, options) {
|
|
2726
|
+
await assertInputExists(input);
|
|
1950
2727
|
await validateDocument(await parseDocument(input), options);
|
|
1951
2728
|
},
|
|
1952
2729
|
getImports(node, resolve) {
|
|
1953
|
-
return
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
});
|
|
2730
|
+
return (0, _kubb_ast.collect)(node, { schema(schemaNode) {
|
|
2731
|
+
const schemaRef = (0, _kubb_ast.narrowSchema)(schemaNode, "ref");
|
|
2732
|
+
if (!schemaRef?.ref) return null;
|
|
2733
|
+
const rawName = (0, _kubb_ast_utils.extractRefName)(schemaRef.ref);
|
|
2734
|
+
const result = resolve(nameMapping.get(rawName) ?? rawName);
|
|
2735
|
+
if (!result) return null;
|
|
2736
|
+
return _kubb_core.ast.factory.createImport({
|
|
2737
|
+
name: [result.name],
|
|
2738
|
+
path: result.path
|
|
2739
|
+
});
|
|
2740
|
+
} });
|
|
1965
2741
|
},
|
|
1966
2742
|
async parse(source) {
|
|
1967
|
-
const
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
dateType,
|
|
1974
|
-
integerType,
|
|
1975
|
-
unknownType,
|
|
1976
|
-
emptySchemaType,
|
|
1977
|
-
enumSuffix
|
|
2743
|
+
const streamNode = await createStream(source);
|
|
2744
|
+
const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)]);
|
|
2745
|
+
return _kubb_core.ast.factory.createInput({
|
|
2746
|
+
schemas,
|
|
2747
|
+
operations,
|
|
2748
|
+
meta: streamNode.meta
|
|
1978
2749
|
});
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
parsedDocument = document;
|
|
1982
|
-
inputNode = _kubb_core.ast.createInput({
|
|
1983
|
-
...node,
|
|
1984
|
-
meta: {
|
|
1985
|
-
title: document.info?.title,
|
|
1986
|
-
description: document.info?.description,
|
|
1987
|
-
version: document.info?.version,
|
|
1988
|
-
baseURL
|
|
1989
|
-
}
|
|
1990
|
-
});
|
|
1991
|
-
return inputNode;
|
|
1992
|
-
}
|
|
2750
|
+
},
|
|
2751
|
+
stream: createStream
|
|
1993
2752
|
};
|
|
1994
2753
|
});
|
|
1995
2754
|
//#endregion
|
|
1996
|
-
//#region src/types.ts
|
|
1997
|
-
/**
|
|
1998
|
-
* Maps uppercase HTTP method names to lowercase for backwards compatibility.
|
|
1999
|
-
*
|
|
2000
|
-
* @example
|
|
2001
|
-
* ```ts
|
|
2002
|
-
* HttpMethods['GET'] // 'get'
|
|
2003
|
-
* HttpMethods['POST'] // 'post'
|
|
2004
|
-
* ```
|
|
2005
|
-
*/
|
|
2006
|
-
const HttpMethods = Object.fromEntries(Object.entries(_kubb_core.ast.httpMethods).map(([lower, upper]) => [upper, lower]));
|
|
2007
|
-
//#endregion
|
|
2008
|
-
exports.HttpMethods = HttpMethods;
|
|
2009
2755
|
exports.adapterOas = adapterOas;
|
|
2010
2756
|
exports.adapterOasName = adapterOasName;
|
|
2011
|
-
exports.mergeDocuments = mergeDocuments;
|
|
2012
2757
|
|
|
2013
2758
|
//# sourceMappingURL=index.cjs.map
|