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