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