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