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