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