@kubb/adapter-oas 5.0.0-beta.5 → 5.0.0-beta.51
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +17 -10
- package/README.md +100 -0
- package/dist/index.cjs +947 -344
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +52 -96
- package/dist/index.js +948 -339
- package/dist/index.js.map +1 -1
- package/extension.yaml +145 -58
- package/package.json +9 -6
- package/src/adapter.bench.ts +60 -0
- package/src/adapter.ts +139 -50
- package/src/constants.ts +8 -0
- package/src/dialect.ts +38 -0
- package/src/discriminator.ts +31 -47
- package/src/factory.ts +44 -26
- package/src/index.ts +0 -3
- package/src/parser.ts +239 -142
- package/src/refs.ts +31 -5
- package/src/resolvers.ts +77 -38
- package/src/schemaDiagnostics.ts +76 -0
- package/src/stream.ts +278 -0
- package/src/types.ts +34 -11
- /package/dist/{chunk--u3MIqq1.js → chunk-C0LytTxp.js} +0 -0
package/dist/index.cjs
CHANGED
|
@@ -22,15 +22,14 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
24
|
let _kubb_core = require("@kubb/core");
|
|
25
|
+
let oas = require("oas");
|
|
26
|
+
oas = __toESM(oas, 1);
|
|
25
27
|
let node_path = require("node:path");
|
|
26
28
|
node_path = __toESM(node_path, 1);
|
|
27
|
-
let
|
|
29
|
+
let node_fs_promises = require("node:fs/promises");
|
|
28
30
|
let oas_normalize = require("oas-normalize");
|
|
29
31
|
oas_normalize = __toESM(oas_normalize, 1);
|
|
30
|
-
let
|
|
31
|
-
swagger2openapi = __toESM(swagger2openapi, 1);
|
|
32
|
-
let oas = require("oas");
|
|
33
|
-
oas = __toESM(oas, 1);
|
|
32
|
+
let _kubb_ast_utils = require("@kubb/ast/utils");
|
|
34
33
|
let oas_types = require("oas/types");
|
|
35
34
|
let oas_utils = require("oas/utils");
|
|
36
35
|
//#region src/constants.ts
|
|
@@ -115,6 +114,18 @@ const structuralKeys = new Set([
|
|
|
115
114
|
* formatMap['float'] // 'number'
|
|
116
115
|
* ```
|
|
117
116
|
*/
|
|
117
|
+
/**
|
|
118
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
119
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
120
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
121
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
122
|
+
*/
|
|
123
|
+
const specialCasedFormats = new Set([
|
|
124
|
+
"int64",
|
|
125
|
+
"date-time",
|
|
126
|
+
"date",
|
|
127
|
+
"time"
|
|
128
|
+
]);
|
|
118
129
|
const formatMap = {
|
|
119
130
|
uuid: "uuid",
|
|
120
131
|
email: "email",
|
|
@@ -153,87 +164,6 @@ const typeOptionMap = new Map([
|
|
|
153
164
|
["void", _kubb_core.ast.schemaTypes.void]
|
|
154
165
|
]);
|
|
155
166
|
//#endregion
|
|
156
|
-
//#region src/discriminator.ts
|
|
157
|
-
/**
|
|
158
|
-
* Injects discriminator enum values into child schemas so they know which value identifies them.
|
|
159
|
-
*
|
|
160
|
-
* Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
|
|
161
|
-
* enum value each union member is mapped to, then adds (or replaces) that property on the matching
|
|
162
|
-
* child object schema.
|
|
163
|
-
*
|
|
164
|
-
* Returns a new `InputNode` — the original is never mutated.
|
|
165
|
-
*
|
|
166
|
-
* @example
|
|
167
|
-
* ```ts
|
|
168
|
-
* const { root } = parseOas(document, options)
|
|
169
|
-
* const next = applyDiscriminatorInheritance(root)
|
|
170
|
-
* ```
|
|
171
|
-
*/
|
|
172
|
-
function applyDiscriminatorInheritance(root) {
|
|
173
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
174
|
-
for (const schema of root.schemas) {
|
|
175
|
-
let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
|
|
176
|
-
if (!unionNode) {
|
|
177
|
-
const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
|
|
178
|
-
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
179
|
-
const u = _kubb_core.ast.narrowSchema(m, "union");
|
|
180
|
-
if (u) {
|
|
181
|
-
unionNode = u;
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
187
|
-
const { discriminatorPropertyName, members } = unionNode;
|
|
188
|
-
for (const member of members) {
|
|
189
|
-
const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
|
|
190
|
-
if (!intersectionNode?.members) continue;
|
|
191
|
-
let refNode;
|
|
192
|
-
let objNode;
|
|
193
|
-
for (const m of intersectionNode.members) {
|
|
194
|
-
refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
|
|
195
|
-
objNode ??= _kubb_core.ast.narrowSchema(m, "object");
|
|
196
|
-
}
|
|
197
|
-
if (!refNode?.name || !objNode) continue;
|
|
198
|
-
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
199
|
-
const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : void 0;
|
|
200
|
-
if (!enumNode?.enumValues?.length) continue;
|
|
201
|
-
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
202
|
-
if (!enumValues.length) continue;
|
|
203
|
-
const existing = childMap.get(refNode.name);
|
|
204
|
-
if (existing) existing.enumValues.push(...enumValues);
|
|
205
|
-
else childMap.set(refNode.name, {
|
|
206
|
-
propertyName: discriminatorPropertyName,
|
|
207
|
-
enumValues: [...enumValues]
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
if (childMap.size === 0) return root;
|
|
212
|
-
return _kubb_core.ast.transform(root, { schema(node, { parent }) {
|
|
213
|
-
if (parent?.kind !== "Input" || !node.name) return;
|
|
214
|
-
const entry = childMap.get(node.name);
|
|
215
|
-
if (!entry) return;
|
|
216
|
-
const objectNode = _kubb_core.ast.narrowSchema(node, "object");
|
|
217
|
-
if (!objectNode) return;
|
|
218
|
-
const { propertyName, enumValues } = entry;
|
|
219
|
-
const enumSchema = _kubb_core.ast.createSchema({
|
|
220
|
-
type: "enum",
|
|
221
|
-
enumValues
|
|
222
|
-
});
|
|
223
|
-
const newProp = _kubb_core.ast.createProperty({
|
|
224
|
-
name: propertyName,
|
|
225
|
-
required: true,
|
|
226
|
-
schema: enumSchema
|
|
227
|
-
});
|
|
228
|
-
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
229
|
-
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
230
|
-
return {
|
|
231
|
-
...objectNode,
|
|
232
|
-
properties: newProperties
|
|
233
|
-
};
|
|
234
|
-
} });
|
|
235
|
-
}
|
|
236
|
-
//#endregion
|
|
237
167
|
//#region ../../internals/utils/src/casing.ts
|
|
238
168
|
/**
|
|
239
169
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -298,6 +228,42 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
298
228
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
299
229
|
}
|
|
300
230
|
//#endregion
|
|
231
|
+
//#region ../../internals/utils/src/runtime.ts
|
|
232
|
+
/**
|
|
233
|
+
* Returns `true` when the current process is running under Bun.
|
|
234
|
+
*
|
|
235
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
236
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
237
|
+
* otherwise look like Node.
|
|
238
|
+
*
|
|
239
|
+
* @example
|
|
240
|
+
* ```ts
|
|
241
|
+
* if (isBun()) {
|
|
242
|
+
* await Bun.write(path, data)
|
|
243
|
+
* }
|
|
244
|
+
* ```
|
|
245
|
+
*/
|
|
246
|
+
function isBun() {
|
|
247
|
+
return typeof Bun !== "undefined";
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region ../../internals/utils/src/fs.ts
|
|
251
|
+
/**
|
|
252
|
+
* Resolves to `true` when the file or directory at `path` exists.
|
|
253
|
+
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* ```ts
|
|
257
|
+
* if (await exists('./kubb.config.ts')) {
|
|
258
|
+
* const content = await read('./kubb.config.ts')
|
|
259
|
+
* }
|
|
260
|
+
* ```
|
|
261
|
+
*/
|
|
262
|
+
async function exists(path) {
|
|
263
|
+
if (isBun()) return Bun.file(path).exists();
|
|
264
|
+
return (0, node_fs_promises.access)(path).then(() => true, () => false);
|
|
265
|
+
}
|
|
266
|
+
//#endregion
|
|
301
267
|
//#region ../../internals/utils/src/object.ts
|
|
302
268
|
/**
|
|
303
269
|
* Returns `true` when `value` is a plain (non-null, non-array) object.
|
|
@@ -432,6 +398,22 @@ const reservedWords = new Set([
|
|
|
432
398
|
*/
|
|
433
399
|
function isValidVarName(name) {
|
|
434
400
|
if (!name || reservedWords.has(name)) return false;
|
|
401
|
+
return isIdentifier(name);
|
|
402
|
+
}
|
|
403
|
+
/**
|
|
404
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
405
|
+
*
|
|
406
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
407
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
408
|
+
* deciding whether an object key needs quoting.
|
|
409
|
+
*
|
|
410
|
+
* @example
|
|
411
|
+
* ```ts
|
|
412
|
+
* isIdentifier('name') // true
|
|
413
|
+
* isIdentifier('x-total')// false
|
|
414
|
+
* ```
|
|
415
|
+
*/
|
|
416
|
+
function isIdentifier(name) {
|
|
435
417
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
436
418
|
}
|
|
437
419
|
//#endregion
|
|
@@ -543,12 +525,13 @@ var URLPath = class {
|
|
|
543
525
|
* @example
|
|
544
526
|
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
545
527
|
*/
|
|
546
|
-
toTemplateString({ prefix
|
|
547
|
-
|
|
528
|
+
toTemplateString({ prefix, replacer } = {}) {
|
|
529
|
+
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
548
530
|
if (i % 2 === 0) return part;
|
|
549
531
|
const param = this.#transformParam(part);
|
|
550
532
|
return `\${${replacer ? replacer(param) : param}}`;
|
|
551
|
-
}).join("")
|
|
533
|
+
}).join("");
|
|
534
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
552
535
|
}
|
|
553
536
|
/**
|
|
554
537
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
@@ -646,8 +629,8 @@ function isDiscriminator(obj) {
|
|
|
646
629
|
* Loads and dereferences an OpenAPI document, returning the raw `Document`.
|
|
647
630
|
*
|
|
648
631
|
* Accepts a file path string or an already-parsed document object. File paths are bundled via
|
|
649
|
-
*
|
|
650
|
-
* to OpenAPI 3.0 via `swagger2openapi`.
|
|
632
|
+
* `@apidevtools/json-schema-ref-parser` to resolve external `$ref`s. Swagger 2.0 documents are
|
|
633
|
+
* automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
|
|
651
634
|
*
|
|
652
635
|
* @example
|
|
653
636
|
* ```ts
|
|
@@ -656,20 +639,20 @@ function isDiscriminator(obj) {
|
|
|
656
639
|
* ```
|
|
657
640
|
*/
|
|
658
641
|
async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true } = {}) {
|
|
659
|
-
if (typeof pathOrApi === "string" && canBundle)
|
|
660
|
-
ref
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
});
|
|
642
|
+
if (typeof pathOrApi === "string" && canBundle) {
|
|
643
|
+
const { $RefParser } = await import("@apidevtools/json-schema-ref-parser");
|
|
644
|
+
return parseDocument(await $RefParser.bundle(pathOrApi), {
|
|
645
|
+
canBundle: false,
|
|
646
|
+
enablePaths
|
|
647
|
+
});
|
|
648
|
+
}
|
|
667
649
|
const document = await new oas_normalize.default(pathOrApi, {
|
|
668
650
|
enablePaths,
|
|
669
651
|
colorizeErrors: true
|
|
670
652
|
}).load();
|
|
671
653
|
if (isOpenApiV2Document(document)) {
|
|
672
|
-
const {
|
|
654
|
+
const { default: swagger2openapi } = await import("swagger2openapi");
|
|
655
|
+
const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
|
|
673
656
|
return openapi;
|
|
674
657
|
}
|
|
675
658
|
return document;
|
|
@@ -686,12 +669,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
686
669
|
* ```
|
|
687
670
|
*/
|
|
688
671
|
async function mergeDocuments(pathOrApi) {
|
|
689
|
-
const documents =
|
|
690
|
-
for (const p of pathOrApi) documents.push(await parseDocument(p, {
|
|
672
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
691
673
|
enablePaths: false,
|
|
692
674
|
canBundle: false
|
|
693
|
-
}));
|
|
694
|
-
if (documents.length === 0) throw new Error(
|
|
675
|
+
})));
|
|
676
|
+
if (documents.length === 0) throw new _kubb_core.Diagnostics.Error({
|
|
677
|
+
code: _kubb_core.Diagnostics.code.inputRequired,
|
|
678
|
+
severity: "error",
|
|
679
|
+
message: "No OAS documents were provided for merging.",
|
|
680
|
+
help: "Pass at least one path or document to `input.path`.",
|
|
681
|
+
location: { kind: "config" }
|
|
682
|
+
});
|
|
695
683
|
const seed = {
|
|
696
684
|
openapi: MERGE_OPENAPI_VERSION,
|
|
697
685
|
info: {
|
|
@@ -707,9 +695,9 @@ async function mergeDocuments(pathOrApi) {
|
|
|
707
695
|
* Creates a `Document` from an `AdapterSource`.
|
|
708
696
|
*
|
|
709
697
|
* Handles all three source types:
|
|
710
|
-
* - `{ type: 'path' }`
|
|
711
|
-
* - `{ type: 'paths' }`
|
|
712
|
-
* - `{ type: 'data' }`
|
|
698
|
+
* - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
|
|
699
|
+
* - `{ type: 'paths' }` merges multiple file paths into a single document.
|
|
700
|
+
* - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
|
|
713
701
|
*
|
|
714
702
|
* @example
|
|
715
703
|
* ```ts
|
|
@@ -717,14 +705,31 @@ async function mergeDocuments(pathOrApi) {
|
|
|
717
705
|
* const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
|
|
718
706
|
* ```
|
|
719
707
|
*/
|
|
720
|
-
function parseFromConfig(source) {
|
|
708
|
+
async function parseFromConfig(source) {
|
|
721
709
|
if (source.type === "data") {
|
|
722
710
|
if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
|
|
723
711
|
return parseDocument(source.data, { canBundle: false });
|
|
724
712
|
}
|
|
725
713
|
if (source.type === "paths") return mergeDocuments(source.paths);
|
|
726
714
|
if (new URLPath(source.path).isURL) return parseDocument(source.path);
|
|
727
|
-
|
|
715
|
+
const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
|
|
716
|
+
await assertInputExists(resolved);
|
|
717
|
+
return parseDocument(resolved);
|
|
718
|
+
}
|
|
719
|
+
/**
|
|
720
|
+
* Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
|
|
721
|
+
* URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
|
|
722
|
+
* its parse error instead.
|
|
723
|
+
*/
|
|
724
|
+
async function assertInputExists(input) {
|
|
725
|
+
if (new URLPath(input).isURL) return;
|
|
726
|
+
if (!await exists(input)) throw new _kubb_core.Diagnostics.Error({
|
|
727
|
+
code: _kubb_core.Diagnostics.code.inputNotFound,
|
|
728
|
+
severity: "error",
|
|
729
|
+
message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
|
|
730
|
+
help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
|
|
731
|
+
location: { kind: "config" }
|
|
732
|
+
});
|
|
728
733
|
}
|
|
729
734
|
/**
|
|
730
735
|
* Validates an OpenAPI document using `oas-normalize` with colorized error output.
|
|
@@ -746,6 +751,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
746
751
|
}
|
|
747
752
|
//#endregion
|
|
748
753
|
//#region src/refs.ts
|
|
754
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
749
755
|
/**
|
|
750
756
|
* Resolves a local JSON pointer reference from a document.
|
|
751
757
|
*
|
|
@@ -761,10 +767,31 @@ function resolveRef(document, $ref) {
|
|
|
761
767
|
const origRef = $ref;
|
|
762
768
|
$ref = $ref.trim();
|
|
763
769
|
if ($ref === "") return null;
|
|
764
|
-
if (
|
|
765
|
-
|
|
770
|
+
if (!$ref.startsWith("#")) return null;
|
|
771
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
772
|
+
let docCache = _refCache.get(document);
|
|
773
|
+
if (!docCache) {
|
|
774
|
+
docCache = /* @__PURE__ */ new Map();
|
|
775
|
+
_refCache.set(document, docCache);
|
|
776
|
+
}
|
|
777
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
766
778
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
767
|
-
if (!current)
|
|
779
|
+
if (!current) {
|
|
780
|
+
const diagnostic = {
|
|
781
|
+
code: _kubb_core.Diagnostics.code.refNotFound,
|
|
782
|
+
severity: "error",
|
|
783
|
+
message: `Could not find a definition for ${origRef}.`,
|
|
784
|
+
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
785
|
+
location: {
|
|
786
|
+
kind: "schema",
|
|
787
|
+
pointer: origRef,
|
|
788
|
+
ref: origRef
|
|
789
|
+
}
|
|
790
|
+
};
|
|
791
|
+
if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.Diagnostics.Error(diagnostic);
|
|
792
|
+
return null;
|
|
793
|
+
}
|
|
794
|
+
docCache.set($ref, current);
|
|
768
795
|
return current;
|
|
769
796
|
}
|
|
770
797
|
/**
|
|
@@ -788,6 +815,35 @@ function dereferenceWithRef(document, schema) {
|
|
|
788
815
|
return schema;
|
|
789
816
|
}
|
|
790
817
|
//#endregion
|
|
818
|
+
//#region src/dialect.ts
|
|
819
|
+
/**
|
|
820
|
+
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
821
|
+
*
|
|
822
|
+
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
823
|
+
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
824
|
+
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
825
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
826
|
+
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
827
|
+
* the rest unchanged.
|
|
828
|
+
*
|
|
829
|
+
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
830
|
+
* JSON Schema vocabulary, so the converters keep that common case.
|
|
831
|
+
*
|
|
832
|
+
* @example
|
|
833
|
+
* ```ts
|
|
834
|
+
* const parser = createSchemaParser(context) // uses oasDialect
|
|
835
|
+
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
836
|
+
* ```
|
|
837
|
+
*/
|
|
838
|
+
const oasDialect = _kubb_core.ast.defineSchemaDialect({
|
|
839
|
+
name: "oas",
|
|
840
|
+
isNullable,
|
|
841
|
+
isReference,
|
|
842
|
+
isDiscriminator,
|
|
843
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
844
|
+
resolveRef
|
|
845
|
+
});
|
|
846
|
+
//#endregion
|
|
791
847
|
//#region src/resolvers.ts
|
|
792
848
|
/**
|
|
793
849
|
* Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
|
|
@@ -809,7 +865,16 @@ function resolveServerUrl(server, overrides) {
|
|
|
809
865
|
for (const [key, variable] of Object.entries(server.variables)) {
|
|
810
866
|
const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
|
|
811
867
|
if (value === void 0) continue;
|
|
812
|
-
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(
|
|
868
|
+
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.Diagnostics.Error({
|
|
869
|
+
code: _kubb_core.Diagnostics.code.invalidServerVariable,
|
|
870
|
+
severity: "error",
|
|
871
|
+
message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
|
|
872
|
+
help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
|
|
873
|
+
location: {
|
|
874
|
+
kind: "document",
|
|
875
|
+
pointer: "#/servers"
|
|
876
|
+
}
|
|
877
|
+
});
|
|
813
878
|
url = url.replaceAll(`{${key}}`, value);
|
|
814
879
|
}
|
|
815
880
|
return url;
|
|
@@ -822,6 +887,15 @@ function getSchemaType(format) {
|
|
|
822
887
|
return formatMap[format] ?? null;
|
|
823
888
|
}
|
|
824
889
|
/**
|
|
890
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
|
|
891
|
+
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
|
|
892
|
+
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
893
|
+
* diagnostic in step with the parser as `formatMap` grows.
|
|
894
|
+
*/
|
|
895
|
+
function isHandledFormat(format) {
|
|
896
|
+
return getSchemaType(format) !== null || specialCasedFormats.has(format);
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
825
899
|
* Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
826
900
|
* Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
|
|
827
901
|
*/
|
|
@@ -831,12 +905,6 @@ function getPrimitiveType(type) {
|
|
|
831
905
|
return "string";
|
|
832
906
|
}
|
|
833
907
|
/**
|
|
834
|
-
* Narrows a content-type string to the `MediaType` union Kubb recognizes, or returns `null`.
|
|
835
|
-
*/
|
|
836
|
-
function getMediaType(contentType) {
|
|
837
|
-
return Object.values(_kubb_core.ast.mediaTypes).includes(contentType) ? contentType : null;
|
|
838
|
-
}
|
|
839
|
-
/**
|
|
840
908
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
841
909
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
842
910
|
* `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
|
|
@@ -924,7 +992,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
924
992
|
/**
|
|
925
993
|
* Flattens a keyword-only `allOf` into its parent schema.
|
|
926
994
|
*
|
|
927
|
-
* Only flattens when every member is a plain fragment
|
|
995
|
+
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
928
996
|
* (see `structuralKeys`). Outer schema values take precedence over fragment values.
|
|
929
997
|
* Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
|
|
930
998
|
*
|
|
@@ -934,7 +1002,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
934
1002
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
935
1003
|
*
|
|
936
1004
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
937
|
-
* // returned unchanged
|
|
1005
|
+
* // returned unchanged, contains a $ref
|
|
938
1006
|
* ```
|
|
939
1007
|
*/
|
|
940
1008
|
/**
|
|
@@ -960,7 +1028,7 @@ function flattenSchema(schema) {
|
|
|
960
1028
|
/**
|
|
961
1029
|
* Extracts the inline schema from a media-type `content` map.
|
|
962
1030
|
*
|
|
963
|
-
* Prefers `preferredContentType` when given
|
|
1031
|
+
* Prefers `preferredContentType` when given, otherwise uses the first key in the map.
|
|
964
1032
|
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
965
1033
|
*
|
|
966
1034
|
* @example
|
|
@@ -979,21 +1047,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
979
1047
|
/**
|
|
980
1048
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
981
1049
|
*/
|
|
982
|
-
function collectRefs(schema
|
|
1050
|
+
function* collectRefs(schema) {
|
|
983
1051
|
if (Array.isArray(schema)) {
|
|
984
|
-
for (const item of schema) collectRefs(item
|
|
985
|
-
return
|
|
1052
|
+
for (const item of schema) yield* collectRefs(item);
|
|
1053
|
+
return;
|
|
986
1054
|
}
|
|
987
1055
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
988
1056
|
const value = schema[key];
|
|
989
|
-
if (key === "$ref" && typeof value === "string") {
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1057
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
1058
|
+
yield* collectRefs(value);
|
|
1059
|
+
continue;
|
|
1060
|
+
}
|
|
1061
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
1062
|
+
const name = value.slice(21);
|
|
1063
|
+
if (name) yield name;
|
|
1064
|
+
}
|
|
995
1065
|
}
|
|
996
|
-
return refs;
|
|
997
1066
|
}
|
|
998
1067
|
/**
|
|
999
1068
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -1009,7 +1078,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
1009
1078
|
*/
|
|
1010
1079
|
function sortSchemas(schemas) {
|
|
1011
1080
|
const deps = /* @__PURE__ */ new Map();
|
|
1012
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
1081
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
1013
1082
|
const sorted = [];
|
|
1014
1083
|
const visited = /* @__PURE__ */ new Set();
|
|
1015
1084
|
function visit(name, stack) {
|
|
@@ -1144,15 +1213,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1144
1213
|
readOnly: schema.readOnly,
|
|
1145
1214
|
writeOnly: schema.writeOnly,
|
|
1146
1215
|
default: defaultValue,
|
|
1147
|
-
example: schema.example
|
|
1216
|
+
example: schema.example,
|
|
1217
|
+
format: schema.format
|
|
1148
1218
|
};
|
|
1149
1219
|
}
|
|
1150
1220
|
/**
|
|
1151
1221
|
* Returns all request body content type keys for an operation.
|
|
1152
1222
|
*
|
|
1153
|
-
* The requestBody is dereferenced
|
|
1154
|
-
*
|
|
1155
|
-
*
|
|
1223
|
+
* The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
|
|
1224
|
+
* `getRequestSchema` already performs), so the returned list accurately reflects the
|
|
1225
|
+
* available content types even for referenced bodies.
|
|
1156
1226
|
*
|
|
1157
1227
|
* @example
|
|
1158
1228
|
* ```ts
|
|
@@ -1166,6 +1236,31 @@ function getRequestBodyContentTypes(document, operation) {
|
|
|
1166
1236
|
if (!body) return [];
|
|
1167
1237
|
return body.content ? Object.keys(body.content) : [];
|
|
1168
1238
|
}
|
|
1239
|
+
/**
|
|
1240
|
+
* Returns all response content type keys for an operation at a given status code.
|
|
1241
|
+
*
|
|
1242
|
+
* Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
|
|
1243
|
+
* so the returned list reflects the available content types even for referenced responses.
|
|
1244
|
+
*
|
|
1245
|
+
* @example
|
|
1246
|
+
* ```ts
|
|
1247
|
+
* getResponseBodyContentTypes(document, operation, 200)
|
|
1248
|
+
* // ['application/json', 'application/xml']
|
|
1249
|
+
* ```
|
|
1250
|
+
*/
|
|
1251
|
+
function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
1252
|
+
if (operation.schema.responses) {
|
|
1253
|
+
const responses = operation.schema.responses;
|
|
1254
|
+
for (const key in responses) {
|
|
1255
|
+
const schema = responses[key];
|
|
1256
|
+
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1260
|
+
if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
|
|
1261
|
+
const body = responseObj;
|
|
1262
|
+
return body.content ? Object.keys(body.content) : [];
|
|
1263
|
+
}
|
|
1169
1264
|
//#endregion
|
|
1170
1265
|
//#region src/parser.ts
|
|
1171
1266
|
/**
|
|
@@ -1194,9 +1289,9 @@ function normalizeArrayEnum(schema) {
|
|
|
1194
1289
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1195
1290
|
* made possible by hoisting of function declarations.
|
|
1196
1291
|
*
|
|
1197
|
-
* @
|
|
1292
|
+
* @internal
|
|
1198
1293
|
*/
|
|
1199
|
-
function createSchemaParser(ctx) {
|
|
1294
|
+
function createSchemaParser(ctx, dialect = oasDialect) {
|
|
1200
1295
|
const document = ctx.document;
|
|
1201
1296
|
/**
|
|
1202
1297
|
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
@@ -1204,6 +1299,15 @@ function createSchemaParser(ctx) {
|
|
|
1204
1299
|
*/
|
|
1205
1300
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1206
1301
|
/**
|
|
1302
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1303
|
+
*
|
|
1304
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1305
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1306
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1307
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1308
|
+
*/
|
|
1309
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1310
|
+
/**
|
|
1207
1311
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1208
1312
|
*
|
|
1209
1313
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1212,20 +1316,26 @@ function createSchemaParser(ctx) {
|
|
|
1212
1316
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1213
1317
|
*/
|
|
1214
1318
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1215
|
-
let resolvedSchema;
|
|
1319
|
+
let resolvedSchema = null;
|
|
1216
1320
|
const refPath = schema.$ref;
|
|
1217
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1321
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1322
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1323
|
+
try {
|
|
1324
|
+
const referenced = dialect.resolveRef(document, refPath);
|
|
1325
|
+
if (referenced) {
|
|
1326
|
+
resolvingRefs.add(refPath);
|
|
1327
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1328
|
+
resolvingRefs.delete(refPath);
|
|
1329
|
+
}
|
|
1330
|
+
} catch {}
|
|
1331
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1223
1332
|
}
|
|
1224
|
-
|
|
1333
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1334
|
+
}
|
|
1225
1335
|
return _kubb_core.ast.createSchema({
|
|
1226
1336
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1227
1337
|
type: "ref",
|
|
1228
|
-
name:
|
|
1338
|
+
name: (0, _kubb_ast_utils.extractRefName)(schema.$ref),
|
|
1229
1339
|
ref: schema.$ref,
|
|
1230
1340
|
schema: resolvedSchema
|
|
1231
1341
|
});
|
|
@@ -1238,7 +1348,7 @@ function createSchemaParser(ctx) {
|
|
|
1238
1348
|
const [memberSchema] = schema.allOf;
|
|
1239
1349
|
const memberNode = parseSchema({
|
|
1240
1350
|
schema: memberSchema,
|
|
1241
|
-
name
|
|
1351
|
+
name
|
|
1242
1352
|
}, rawOptions);
|
|
1243
1353
|
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1244
1354
|
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
@@ -1254,21 +1364,22 @@ function createSchemaParser(ctx) {
|
|
|
1254
1364
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1255
1365
|
default: mergedDefault,
|
|
1256
1366
|
example: schema.example ?? memberNode.example,
|
|
1257
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1367
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1368
|
+
format: schema.format ?? memberNode.format
|
|
1258
1369
|
});
|
|
1259
1370
|
}
|
|
1260
1371
|
const filteredDiscriminantValues = [];
|
|
1261
1372
|
const allOfMembers = schema.allOf.filter((item) => {
|
|
1262
|
-
if (!isReference(item) || !name) return true;
|
|
1263
|
-
const deref = resolveRef(document, item.$ref);
|
|
1264
|
-
if (!deref || !isDiscriminator(deref)) return true;
|
|
1373
|
+
if (!dialect.isReference(item) || !name) return true;
|
|
1374
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1375
|
+
if (!deref || !dialect.isDiscriminator(deref)) return true;
|
|
1265
1376
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1266
1377
|
if (!parentUnion) return true;
|
|
1267
1378
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1268
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1379
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1269
1380
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1270
1381
|
if (inOneOf || inMapping) {
|
|
1271
|
-
const discriminatorValue =
|
|
1382
|
+
const discriminatorValue = (0, _kubb_ast_utils.findDiscriminator)(deref.discriminator.mapping, childRef);
|
|
1272
1383
|
if (discriminatorValue) filteredDiscriminantValues.push({
|
|
1273
1384
|
propertyName: deref.discriminator.propertyName,
|
|
1274
1385
|
value: discriminatorValue
|
|
@@ -1276,22 +1387,28 @@ function createSchemaParser(ctx) {
|
|
|
1276
1387
|
return false;
|
|
1277
1388
|
}
|
|
1278
1389
|
return true;
|
|
1279
|
-
}).map((s) => parseSchema({
|
|
1390
|
+
}).map((s) => parseSchema({
|
|
1391
|
+
schema: s,
|
|
1392
|
+
name
|
|
1393
|
+
}, rawOptions));
|
|
1280
1394
|
const syntheticStart = allOfMembers.length;
|
|
1281
1395
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1282
1396
|
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1283
1397
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1284
1398
|
if (missingRequired.length) {
|
|
1285
1399
|
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1286
|
-
if (!isReference(item)) return [item];
|
|
1287
|
-
const deref = resolveRef(document, item.$ref);
|
|
1288
|
-
return deref && !isReference(deref) ? [deref] : [];
|
|
1400
|
+
if (!dialect.isReference(item)) return [item];
|
|
1401
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1402
|
+
return deref && !dialect.isReference(deref) ? [deref] : [];
|
|
1289
1403
|
});
|
|
1290
1404
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1291
|
-
allOfMembers.push(parseSchema({
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1405
|
+
allOfMembers.push(parseSchema({
|
|
1406
|
+
schema: {
|
|
1407
|
+
properties: { [key]: resolved.properties[key] },
|
|
1408
|
+
required: [key]
|
|
1409
|
+
},
|
|
1410
|
+
name
|
|
1411
|
+
}, rawOptions));
|
|
1295
1412
|
break;
|
|
1296
1413
|
}
|
|
1297
1414
|
}
|
|
@@ -1306,7 +1423,7 @@ function createSchemaParser(ctx) {
|
|
|
1306
1423
|
}));
|
|
1307
1424
|
return _kubb_core.ast.createSchema({
|
|
1308
1425
|
type: "intersection",
|
|
1309
|
-
members: [..._kubb_core.ast.
|
|
1426
|
+
members: [..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ..._kubb_core.ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1310
1427
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1311
1428
|
});
|
|
1312
1429
|
}
|
|
@@ -1327,10 +1444,10 @@ function createSchemaParser(ctx) {
|
|
|
1327
1444
|
const strategy = schema.oneOf ? "one" : "any";
|
|
1328
1445
|
const unionBase = {
|
|
1329
1446
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1330
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1447
|
+
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1331
1448
|
strategy
|
|
1332
1449
|
};
|
|
1333
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1450
|
+
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1334
1451
|
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1335
1452
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1336
1453
|
return parseSchema({
|
|
@@ -1340,9 +1457,12 @@ function createSchemaParser(ctx) {
|
|
|
1340
1457
|
})() : void 0;
|
|
1341
1458
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1342
1459
|
const members = unionMembers.map((s) => {
|
|
1343
|
-
const ref = isReference(s) ? s.$ref : void 0;
|
|
1344
|
-
const discriminatorValue =
|
|
1345
|
-
const memberNode = parseSchema({
|
|
1460
|
+
const ref = dialect.isReference(s) ? s.$ref : void 0;
|
|
1461
|
+
const discriminatorValue = (0, _kubb_ast_utils.findDiscriminator)(discriminator?.mapping, ref);
|
|
1462
|
+
const memberNode = parseSchema({
|
|
1463
|
+
schema: s,
|
|
1464
|
+
name
|
|
1465
|
+
}, rawOptions);
|
|
1346
1466
|
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1347
1467
|
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_core.ast.setDiscriminatorEnum({
|
|
1348
1468
|
node: sharedPropertiesNode,
|
|
@@ -1372,7 +1492,10 @@ function createSchemaParser(ctx) {
|
|
|
1372
1492
|
return _kubb_core.ast.createSchema({
|
|
1373
1493
|
type: "union",
|
|
1374
1494
|
...unionBase,
|
|
1375
|
-
members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1495
|
+
members: _kubb_core.ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1496
|
+
schema: s,
|
|
1497
|
+
name
|
|
1498
|
+
}, rawOptions)))
|
|
1376
1499
|
});
|
|
1377
1500
|
}
|
|
1378
1501
|
/**
|
|
@@ -1386,7 +1509,8 @@ function createSchemaParser(ctx) {
|
|
|
1386
1509
|
name,
|
|
1387
1510
|
title: schema.title,
|
|
1388
1511
|
description: schema.description,
|
|
1389
|
-
deprecated: schema.deprecated
|
|
1512
|
+
deprecated: schema.deprecated,
|
|
1513
|
+
format: schema.format
|
|
1390
1514
|
});
|
|
1391
1515
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1392
1516
|
return _kubb_core.ast.createSchema({
|
|
@@ -1476,6 +1600,15 @@ function createSchemaParser(ctx) {
|
|
|
1476
1600
|
}, rawOptions);
|
|
1477
1601
|
const nullInEnum = schema.enum.includes(null);
|
|
1478
1602
|
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1603
|
+
if (nullInEnum && filteredValues.length === 0) return _kubb_core.ast.createSchema({
|
|
1604
|
+
type: "null",
|
|
1605
|
+
primitive: "null",
|
|
1606
|
+
name,
|
|
1607
|
+
title: schema.title,
|
|
1608
|
+
description: schema.description,
|
|
1609
|
+
deprecated: schema.deprecated,
|
|
1610
|
+
format: schema.format
|
|
1611
|
+
});
|
|
1479
1612
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
1480
1613
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1481
1614
|
const enumPrimitive = getPrimitiveType(type);
|
|
@@ -1490,7 +1623,8 @@ function createSchemaParser(ctx) {
|
|
|
1490
1623
|
readOnly: schema.readOnly,
|
|
1491
1624
|
writeOnly: schema.writeOnly,
|
|
1492
1625
|
default: enumDefault,
|
|
1493
|
-
example: schema.example
|
|
1626
|
+
example: schema.example,
|
|
1627
|
+
format: schema.format
|
|
1494
1628
|
};
|
|
1495
1629
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1496
1630
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1524,20 +1658,23 @@ function createSchemaParser(ctx) {
|
|
|
1524
1658
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1525
1659
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1526
1660
|
const resolvedPropSchema = propSchema;
|
|
1527
|
-
const propNullable = isNullable(resolvedPropSchema);
|
|
1661
|
+
const propNullable = dialect.isNullable(resolvedPropSchema);
|
|
1528
1662
|
const propNode = parseSchema({
|
|
1529
1663
|
schema: resolvedPropSchema,
|
|
1530
|
-
name:
|
|
1664
|
+
name: (0, _kubb_ast_utils.childName)(name, propName)
|
|
1531
1665
|
}, rawOptions);
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1666
|
+
const schemaNode = (() => {
|
|
1667
|
+
const node = _kubb_core.ast.setEnumName(propNode, name, propName, options.enumSuffix);
|
|
1668
|
+
const tupleNode = _kubb_core.ast.narrowSchema(node, "tuple");
|
|
1669
|
+
if (tupleNode?.items) {
|
|
1670
|
+
const namedItems = tupleNode.items.map((item) => _kubb_core.ast.setEnumName(item, name, propName, options.enumSuffix));
|
|
1671
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1672
|
+
...tupleNode,
|
|
1673
|
+
items: namedItems
|
|
1674
|
+
};
|
|
1675
|
+
}
|
|
1676
|
+
return node;
|
|
1677
|
+
})();
|
|
1541
1678
|
return _kubb_core.ast.createProperty({
|
|
1542
1679
|
name: propName,
|
|
1543
1680
|
schema: {
|
|
@@ -1548,11 +1685,12 @@ function createSchemaParser(ctx) {
|
|
|
1548
1685
|
});
|
|
1549
1686
|
}) : [];
|
|
1550
1687
|
const additionalProperties = schema.additionalProperties;
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1688
|
+
const additionalPropertiesNode = (() => {
|
|
1689
|
+
if (additionalProperties === true) return true;
|
|
1690
|
+
if (additionalProperties === false) return false;
|
|
1691
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1692
|
+
if (additionalProperties) return _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1693
|
+
})();
|
|
1556
1694
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1557
1695
|
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1558
1696
|
const objectNode = _kubb_core.ast.createSchema({
|
|
@@ -1565,10 +1703,10 @@ function createSchemaParser(ctx) {
|
|
|
1565
1703
|
maxProperties: schema.maxProperties,
|
|
1566
1704
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1567
1705
|
});
|
|
1568
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1706
|
+
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1569
1707
|
const discPropName = schema.discriminator.propertyName;
|
|
1570
1708
|
const values = Object.keys(schema.discriminator.mapping);
|
|
1571
|
-
const enumName = name ?
|
|
1709
|
+
const enumName = name ? (0, _kubb_ast_utils.enumPropName)(name, discPropName, options.enumSuffix) : void 0;
|
|
1572
1710
|
return _kubb_core.ast.setDiscriminatorEnum({
|
|
1573
1711
|
node: objectNode,
|
|
1574
1712
|
propertyName: discPropName,
|
|
@@ -1599,7 +1737,7 @@ function createSchemaParser(ctx) {
|
|
|
1599
1737
|
*/
|
|
1600
1738
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1601
1739
|
const rawItems = schema.items;
|
|
1602
|
-
const itemName = rawItems?.enum?.length && name ?
|
|
1740
|
+
const itemName = rawItems?.enum?.length && name ? (0, _kubb_ast_utils.enumPropName)(null, name, options.enumSuffix) : name;
|
|
1603
1741
|
const items = rawItems ? [parseSchema({
|
|
1604
1742
|
schema: rawItems,
|
|
1605
1743
|
name: itemName
|
|
@@ -1663,15 +1801,148 @@ function createSchemaParser(ctx) {
|
|
|
1663
1801
|
title: schema.title,
|
|
1664
1802
|
description: schema.description,
|
|
1665
1803
|
deprecated: schema.deprecated,
|
|
1666
|
-
nullable
|
|
1804
|
+
nullable,
|
|
1805
|
+
format: schema.format
|
|
1806
|
+
});
|
|
1807
|
+
}
|
|
1808
|
+
/**
|
|
1809
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
1810
|
+
* into a `blob` node.
|
|
1811
|
+
*/
|
|
1812
|
+
function convertBlob({ schema, name, nullable, defaultValue }) {
|
|
1813
|
+
return _kubb_core.ast.createSchema({
|
|
1814
|
+
type: "blob",
|
|
1815
|
+
primitive: "string",
|
|
1816
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1667
1817
|
});
|
|
1668
1818
|
}
|
|
1669
1819
|
/**
|
|
1820
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
1821
|
+
*
|
|
1822
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
|
|
1823
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
1824
|
+
*/
|
|
1825
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1826
|
+
const types = schema.type;
|
|
1827
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
1828
|
+
if (nonNullTypes.length <= 1) return null;
|
|
1829
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
1830
|
+
return _kubb_core.ast.createSchema({
|
|
1831
|
+
type: "union",
|
|
1832
|
+
members: nonNullTypes.map((t) => parseSchema({
|
|
1833
|
+
schema: {
|
|
1834
|
+
...schema,
|
|
1835
|
+
type: t
|
|
1836
|
+
},
|
|
1837
|
+
name
|
|
1838
|
+
}, rawOptions)),
|
|
1839
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
/**
|
|
1843
|
+
* Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1844
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1845
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1846
|
+
* match/convert/fall-through contract.
|
|
1847
|
+
*/
|
|
1848
|
+
const schemaRules = [
|
|
1849
|
+
{
|
|
1850
|
+
name: "ref",
|
|
1851
|
+
match: ({ schema }) => dialect.isReference(schema),
|
|
1852
|
+
convert: convertRef
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
name: "allOf",
|
|
1856
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
1857
|
+
convert: convertAllOf
|
|
1858
|
+
},
|
|
1859
|
+
{
|
|
1860
|
+
name: "union",
|
|
1861
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
1862
|
+
convert: convertUnion
|
|
1863
|
+
},
|
|
1864
|
+
{
|
|
1865
|
+
name: "const",
|
|
1866
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1867
|
+
convert: convertConst
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
name: "format",
|
|
1871
|
+
match: ({ schema }) => !!schema.format,
|
|
1872
|
+
convert: convertFormat
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
name: "blob",
|
|
1876
|
+
match: ({ schema }) => dialect.isBinary(schema),
|
|
1877
|
+
convert: convertBlob
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
name: "multi-type",
|
|
1881
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1882
|
+
convert: convertMultiType
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
name: "constrained-string",
|
|
1886
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1887
|
+
convert: convertString
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
name: "constrained-number",
|
|
1891
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1892
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
name: "enum",
|
|
1896
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
1897
|
+
convert: convertEnum
|
|
1898
|
+
},
|
|
1899
|
+
{
|
|
1900
|
+
name: "object",
|
|
1901
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1902
|
+
convert: convertObject
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
name: "tuple",
|
|
1906
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
1907
|
+
convert: convertTuple
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
name: "array",
|
|
1911
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1912
|
+
convert: convertArray
|
|
1913
|
+
},
|
|
1914
|
+
{
|
|
1915
|
+
name: "string",
|
|
1916
|
+
match: ({ type }) => type === "string",
|
|
1917
|
+
convert: convertString
|
|
1918
|
+
},
|
|
1919
|
+
{
|
|
1920
|
+
name: "number",
|
|
1921
|
+
match: ({ type }) => type === "number",
|
|
1922
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1923
|
+
},
|
|
1924
|
+
{
|
|
1925
|
+
name: "integer",
|
|
1926
|
+
match: ({ type }) => type === "integer",
|
|
1927
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
name: "boolean",
|
|
1931
|
+
match: ({ type }) => type === "boolean",
|
|
1932
|
+
convert: convertBoolean
|
|
1933
|
+
},
|
|
1934
|
+
{
|
|
1935
|
+
name: "null",
|
|
1936
|
+
match: ({ type }) => type === "null",
|
|
1937
|
+
convert: convertNull
|
|
1938
|
+
}
|
|
1939
|
+
];
|
|
1940
|
+
/**
|
|
1670
1941
|
* Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1671
1942
|
*
|
|
1672
|
-
*
|
|
1673
|
-
*
|
|
1674
|
-
*
|
|
1943
|
+
* Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
|
|
1944
|
+
* via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
|
|
1945
|
+
* `emptySchemaType`.
|
|
1675
1946
|
*/
|
|
1676
1947
|
function parseSchema({ schema, name }, rawOptions) {
|
|
1677
1948
|
const options = {
|
|
@@ -1683,75 +1954,40 @@ function createSchemaParser(ctx) {
|
|
|
1683
1954
|
schema: flattenedSchema,
|
|
1684
1955
|
name
|
|
1685
1956
|
}, rawOptions);
|
|
1686
|
-
const nullable = isNullable(schema) || void 0;
|
|
1687
|
-
const
|
|
1688
|
-
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
1689
|
-
const ctx = {
|
|
1957
|
+
const nullable = dialect.isNullable(schema) || void 0;
|
|
1958
|
+
const schemaCtx = {
|
|
1690
1959
|
schema,
|
|
1691
1960
|
name,
|
|
1692
1961
|
nullable,
|
|
1693
|
-
defaultValue,
|
|
1694
|
-
type,
|
|
1962
|
+
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
1963
|
+
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1695
1964
|
rawOptions,
|
|
1696
1965
|
options
|
|
1697
1966
|
};
|
|
1698
|
-
|
|
1699
|
-
if (
|
|
1700
|
-
if ([...schema.oneOf ?? [], ...schema.anyOf ?? []].length) return convertUnion(ctx);
|
|
1701
|
-
if ("const" in schema && schema.const !== void 0) return convertConst(ctx);
|
|
1702
|
-
if (schema.format) {
|
|
1703
|
-
const formatResult = convertFormat(ctx);
|
|
1704
|
-
if (formatResult) return formatResult;
|
|
1705
|
-
}
|
|
1706
|
-
if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return _kubb_core.ast.createSchema({
|
|
1707
|
-
type: "blob",
|
|
1708
|
-
primitive: "string",
|
|
1709
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1710
|
-
});
|
|
1711
|
-
if (Array.isArray(schema.type) && schema.type.length > 1) {
|
|
1712
|
-
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
1713
|
-
const arrayNullable = schema.type.includes("null") || nullable || void 0;
|
|
1714
|
-
if (nonNullTypes.length > 1) return _kubb_core.ast.createSchema({
|
|
1715
|
-
type: "union",
|
|
1716
|
-
members: nonNullTypes.map((t) => parseSchema({
|
|
1717
|
-
schema: {
|
|
1718
|
-
...schema,
|
|
1719
|
-
type: t
|
|
1720
|
-
},
|
|
1721
|
-
name
|
|
1722
|
-
}, rawOptions)),
|
|
1723
|
-
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1724
|
-
});
|
|
1725
|
-
}
|
|
1726
|
-
if (!type) {
|
|
1727
|
-
if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
|
|
1728
|
-
if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
|
|
1729
|
-
}
|
|
1730
|
-
if (schema.enum?.length) return convertEnum(ctx);
|
|
1731
|
-
if (type === "object" || schema.properties || schema.additionalProperties || "patternProperties" in schema) return convertObject(ctx);
|
|
1732
|
-
if ("prefixItems" in schema) return convertTuple(ctx);
|
|
1733
|
-
if (type === "array" || "items" in schema) return convertArray(ctx);
|
|
1734
|
-
if (type === "string") return convertString(ctx);
|
|
1735
|
-
if (type === "number") return convertNumeric(ctx, "number");
|
|
1736
|
-
if (type === "integer") return convertNumeric(ctx, "integer");
|
|
1737
|
-
if (type === "boolean") return convertBoolean(ctx);
|
|
1738
|
-
if (type === "null") return convertNull(ctx);
|
|
1967
|
+
const node = _kubb_core.ast.dispatch(schemaRules, schemaCtx);
|
|
1968
|
+
if (node) return node;
|
|
1739
1969
|
const emptyType = typeOptionMap.get(options.emptySchemaType);
|
|
1740
1970
|
return _kubb_core.ast.createSchema({
|
|
1741
1971
|
type: emptyType,
|
|
1742
1972
|
name,
|
|
1743
1973
|
title: schema.title,
|
|
1744
|
-
description: schema.description
|
|
1974
|
+
description: schema.description,
|
|
1975
|
+
format: schema.format
|
|
1745
1976
|
});
|
|
1746
1977
|
}
|
|
1747
1978
|
/**
|
|
1748
1979
|
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
1749
1980
|
*/
|
|
1750
|
-
function parseParameter(options, param) {
|
|
1981
|
+
function parseParameter(options, param, parentName) {
|
|
1751
1982
|
const required = param["required"] ?? false;
|
|
1752
|
-
const
|
|
1983
|
+
const paramName = param["name"];
|
|
1984
|
+
const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
|
|
1985
|
+
const schema = param["schema"] ? parseSchema({
|
|
1986
|
+
schema: param["schema"],
|
|
1987
|
+
name: schemaName
|
|
1988
|
+
}, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1753
1989
|
return _kubb_core.ast.createParameter({
|
|
1754
|
-
name:
|
|
1990
|
+
name: paramName,
|
|
1755
1991
|
in: param["in"],
|
|
1756
1992
|
schema: {
|
|
1757
1993
|
...schema,
|
|
@@ -1788,27 +2024,33 @@ function createSchemaParser(ctx) {
|
|
|
1788
2024
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1789
2025
|
*/
|
|
1790
2026
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1791
|
-
if (!schema?.properties) return
|
|
2027
|
+
if (!schema?.properties) return null;
|
|
1792
2028
|
const keys = [];
|
|
1793
2029
|
for (const key in schema.properties) {
|
|
1794
2030
|
const prop = schema.properties[key];
|
|
1795
|
-
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
2031
|
+
if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
|
|
1796
2032
|
}
|
|
1797
|
-
return keys.length ? keys :
|
|
2033
|
+
return keys.length ? keys : null;
|
|
1798
2034
|
}
|
|
1799
2035
|
/**
|
|
1800
2036
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1801
2037
|
*/
|
|
1802
2038
|
function parseOperation(options, operation) {
|
|
1803
|
-
const
|
|
2039
|
+
const operationId = operation.getOperationId();
|
|
2040
|
+
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
2041
|
+
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
|
|
1804
2042
|
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
|
|
1805
2043
|
const requestBodyMeta = getRequestBodyMeta(operation);
|
|
2044
|
+
const requestBodyName = operationName ? `${operationName}Request` : void 0;
|
|
1806
2045
|
const content = allContentTypes.flatMap((ct) => {
|
|
1807
2046
|
const schema = getRequestSchema(document, operation, { contentType: ct });
|
|
1808
2047
|
if (!schema) return [];
|
|
1809
2048
|
return [{
|
|
1810
2049
|
contentType: ct,
|
|
1811
|
-
schema: _kubb_core.ast.syncOptionality(parseSchema({
|
|
2050
|
+
schema: _kubb_core.ast.syncOptionality(parseSchema({
|
|
2051
|
+
schema,
|
|
2052
|
+
name: requestBodyName
|
|
2053
|
+
}, options), requestBodyMeta.required),
|
|
1812
2054
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1813
2055
|
}];
|
|
1814
2056
|
});
|
|
@@ -1819,21 +2061,36 @@ function createSchemaParser(ctx) {
|
|
|
1819
2061
|
} : void 0;
|
|
1820
2062
|
const responses = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1821
2063
|
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1822
|
-
const
|
|
1823
|
-
const
|
|
1824
|
-
const
|
|
1825
|
-
|
|
2064
|
+
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
2065
|
+
const { description } = getResponseMeta(responseObj);
|
|
2066
|
+
const parseEntrySchema = (contentType) => {
|
|
2067
|
+
const raw = getResponseSchema(document, operation, statusCode, { contentType });
|
|
2068
|
+
return {
|
|
2069
|
+
schema: raw && Object.keys(raw).length > 0 ? parseSchema({
|
|
2070
|
+
schema: raw,
|
|
2071
|
+
name: responseName
|
|
2072
|
+
}, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
|
|
2073
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
2074
|
+
};
|
|
2075
|
+
};
|
|
2076
|
+
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
|
|
2077
|
+
contentType,
|
|
2078
|
+
...parseEntrySchema(contentType)
|
|
2079
|
+
}));
|
|
2080
|
+
if (content.length === 0) content.push({
|
|
2081
|
+
contentType: operation.contentType || "application/json",
|
|
2082
|
+
...parseEntrySchema(ctx.contentType)
|
|
2083
|
+
});
|
|
1826
2084
|
return _kubb_core.ast.createResponse({
|
|
1827
2085
|
statusCode,
|
|
1828
2086
|
description,
|
|
1829
|
-
|
|
1830
|
-
mediaType,
|
|
1831
|
-
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
2087
|
+
content
|
|
1832
2088
|
});
|
|
1833
2089
|
});
|
|
1834
2090
|
const urlPath = new URLPath(operation.path);
|
|
1835
2091
|
return _kubb_core.ast.createOperation({
|
|
1836
|
-
operationId
|
|
2092
|
+
operationId,
|
|
2093
|
+
protocol: "http",
|
|
1837
2094
|
method: operation.method.toUpperCase(),
|
|
1838
2095
|
path: urlPath.path,
|
|
1839
2096
|
tags: operation.getTags().map((tag) => tag.name),
|
|
@@ -1851,59 +2108,336 @@ function createSchemaParser(ctx) {
|
|
|
1851
2108
|
parseParameter
|
|
1852
2109
|
};
|
|
1853
2110
|
}
|
|
2111
|
+
//#endregion
|
|
2112
|
+
//#region src/discriminator.ts
|
|
1854
2113
|
/**
|
|
1855
|
-
*
|
|
2114
|
+
* Builds a map of child schema names → discriminator patch data by scanning the given
|
|
2115
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
1856
2116
|
*
|
|
1857
|
-
*
|
|
1858
|
-
*
|
|
1859
|
-
|
|
2117
|
+
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
2118
|
+
* discriminator parents) rather than on all schemas at once.
|
|
2119
|
+
*/
|
|
2120
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
2121
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
2122
|
+
for (const schema of schemas) {
|
|
2123
|
+
let unionNode = _kubb_core.ast.narrowSchema(schema, "union");
|
|
2124
|
+
if (!unionNode) {
|
|
2125
|
+
const intersectionMembers = _kubb_core.ast.narrowSchema(schema, "intersection")?.members;
|
|
2126
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
2127
|
+
const u = _kubb_core.ast.narrowSchema(m, "union");
|
|
2128
|
+
if (u) {
|
|
2129
|
+
unionNode = u;
|
|
2130
|
+
break;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
2135
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
2136
|
+
for (const member of members) {
|
|
2137
|
+
const intersectionNode = _kubb_core.ast.narrowSchema(member, "intersection");
|
|
2138
|
+
if (!intersectionNode?.members) continue;
|
|
2139
|
+
let refNode = null;
|
|
2140
|
+
let objNode = null;
|
|
2141
|
+
for (const m of intersectionNode.members) {
|
|
2142
|
+
refNode ??= _kubb_core.ast.narrowSchema(m, "ref");
|
|
2143
|
+
objNode ??= _kubb_core.ast.narrowSchema(m, "object");
|
|
2144
|
+
}
|
|
2145
|
+
if (!refNode?.name || !objNode) continue;
|
|
2146
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
2147
|
+
const enumNode = prop ? _kubb_core.ast.narrowSchema(prop.schema, "enum") : null;
|
|
2148
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
2149
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
2150
|
+
if (!enumValues.length) continue;
|
|
2151
|
+
const existing = childMap.get(refNode.name);
|
|
2152
|
+
if (!existing) {
|
|
2153
|
+
childMap.set(refNode.name, {
|
|
2154
|
+
propertyName: discriminatorPropertyName,
|
|
2155
|
+
enumValues: [...enumValues]
|
|
2156
|
+
});
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
2159
|
+
existing.enumValues.push(...enumValues);
|
|
2160
|
+
}
|
|
2161
|
+
}
|
|
2162
|
+
return childMap;
|
|
2163
|
+
}
|
|
2164
|
+
/**
|
|
2165
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
2166
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
2167
|
+
* without buffering all schemas.
|
|
2168
|
+
*/
|
|
2169
|
+
function patchDiscriminatorNode(node, entry) {
|
|
2170
|
+
const objectNode = _kubb_core.ast.narrowSchema(node, "object");
|
|
2171
|
+
if (!objectNode) return node;
|
|
2172
|
+
const { propertyName, enumValues } = entry;
|
|
2173
|
+
const enumSchema = _kubb_core.ast.createSchema({
|
|
2174
|
+
type: "enum",
|
|
2175
|
+
enumValues
|
|
2176
|
+
});
|
|
2177
|
+
const newProp = _kubb_core.ast.createProperty({
|
|
2178
|
+
name: propertyName,
|
|
2179
|
+
required: true,
|
|
2180
|
+
schema: enumSchema
|
|
2181
|
+
});
|
|
2182
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
2183
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
2184
|
+
return {
|
|
2185
|
+
...objectNode,
|
|
2186
|
+
properties: newProperties
|
|
2187
|
+
};
|
|
2188
|
+
}
|
|
2189
|
+
//#endregion
|
|
2190
|
+
//#region src/schemaDiagnostics.ts
|
|
2191
|
+
/**
|
|
2192
|
+
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
|
|
2193
|
+
* top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
|
|
2194
|
+
* pointer as it descends so a nested field reports against its full path
|
|
2195
|
+
* (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
|
|
2196
|
+
* resolved schema is reported under its own walk. Reports land in the active build run, are a
|
|
2197
|
+
* no-op outside one, and repeats are deduped by the build.
|
|
2198
|
+
*/
|
|
2199
|
+
function reportSchemaDiagnostics({ node, name }) {
|
|
2200
|
+
visit(node, `#/components/schemas/${escapePointerToken(name)}`);
|
|
2201
|
+
}
|
|
2202
|
+
/**
|
|
2203
|
+
* Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
|
|
2204
|
+
* property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
|
|
2205
|
+
*/
|
|
2206
|
+
function escapePointerToken(token) {
|
|
2207
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2208
|
+
}
|
|
2209
|
+
function visit(node, pointer) {
|
|
2210
|
+
if (node.deprecated) _kubb_core.Diagnostics.report({
|
|
2211
|
+
code: _kubb_core.Diagnostics.code.deprecated,
|
|
2212
|
+
severity: "info",
|
|
2213
|
+
message: "This schema is marked as deprecated.",
|
|
2214
|
+
location: {
|
|
2215
|
+
kind: "schema",
|
|
2216
|
+
pointer
|
|
2217
|
+
}
|
|
2218
|
+
});
|
|
2219
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
|
|
2220
|
+
code: _kubb_core.Diagnostics.code.unsupportedFormat,
|
|
2221
|
+
severity: "warning",
|
|
2222
|
+
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
2223
|
+
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
2224
|
+
location: {
|
|
2225
|
+
kind: "schema",
|
|
2226
|
+
pointer
|
|
2227
|
+
}
|
|
2228
|
+
});
|
|
2229
|
+
if (node.type === "object") {
|
|
2230
|
+
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
2231
|
+
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
2232
|
+
return;
|
|
2233
|
+
}
|
|
2234
|
+
if (node.type === "array") {
|
|
2235
|
+
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
2236
|
+
return;
|
|
2237
|
+
}
|
|
2238
|
+
if (node.type === "tuple") {
|
|
2239
|
+
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
2240
|
+
return;
|
|
2241
|
+
}
|
|
2242
|
+
if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2243
|
+
}
|
|
2244
|
+
//#endregion
|
|
2245
|
+
//#region src/stream.ts
|
|
2246
|
+
/**
|
|
2247
|
+
* Builds the deduplication plan from the already-parsed top-level schema nodes plus a
|
|
2248
|
+
* single extra parse pass over operations (so duplicates in request/response bodies are seen).
|
|
2249
|
+
*
|
|
2250
|
+
* Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
2251
|
+
* rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
2252
|
+
* name (collision-resolved against existing component names); shapes without a name stay inline.
|
|
2253
|
+
*/
|
|
2254
|
+
function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
|
|
2255
|
+
const circularSchemas = new Set(circularNames);
|
|
2256
|
+
const usedNames = new Set(schemaNames);
|
|
2257
|
+
return _kubb_core.ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2258
|
+
isCandidate: (node) => {
|
|
2259
|
+
if (node.type === "enum") return true;
|
|
2260
|
+
if (node.type !== "object") return false;
|
|
2261
|
+
if (node.name && circularSchemas.has(node.name)) return false;
|
|
2262
|
+
return !_kubb_core.ast.containsCircularRef(node, { circularSchemas });
|
|
2263
|
+
},
|
|
2264
|
+
nameFor: (node) => {
|
|
2265
|
+
const base = node.name;
|
|
2266
|
+
if (!base) return null;
|
|
2267
|
+
let name = base;
|
|
2268
|
+
let counter = 2;
|
|
2269
|
+
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
2270
|
+
usedNames.add(name);
|
|
2271
|
+
return name;
|
|
2272
|
+
},
|
|
2273
|
+
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
2276
|
+
/**
|
|
2277
|
+
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
2278
|
+
* interpolating any `serverVariables` into the URL template.
|
|
2279
|
+
*
|
|
2280
|
+
* Returns `null` when `serverIndex` is omitted or out of range.
|
|
1860
2281
|
*
|
|
1861
|
-
*
|
|
2282
|
+
* @example Resolve the first server
|
|
2283
|
+
* `resolveBaseUrl({ document, serverIndex: 0 })`
|
|
2284
|
+
*
|
|
2285
|
+
* @example Override a path variable
|
|
2286
|
+
* `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
|
|
2287
|
+
*/
|
|
2288
|
+
function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
2289
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
2290
|
+
return server?.url ? resolveServerUrl(server, serverVariables) : null;
|
|
2291
|
+
}
|
|
2292
|
+
/**
|
|
2293
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
2294
|
+
*
|
|
2295
|
+
* Three things happen in this single pass:
|
|
2296
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
2297
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
2298
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
2299
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
2300
|
+
*
|
|
2301
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2302
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
2303
|
+
*
|
|
2304
|
+
* Each schema is parsed again during the streaming pass. This is intentional.
|
|
2305
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1862
2306
|
*
|
|
1863
2307
|
* @example
|
|
1864
2308
|
* ```ts
|
|
1865
|
-
*
|
|
1866
|
-
*
|
|
1867
|
-
*
|
|
1868
|
-
*
|
|
2309
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
2310
|
+
* schemas,
|
|
2311
|
+
* parseSchema,
|
|
2312
|
+
* parserOptions,
|
|
2313
|
+
* discriminator: 'strict',
|
|
2314
|
+
* })
|
|
1869
2315
|
* ```
|
|
1870
2316
|
*/
|
|
1871
|
-
function
|
|
1872
|
-
const
|
|
1873
|
-
const
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
2317
|
+
function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
|
|
2318
|
+
const allNodes = [];
|
|
2319
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2320
|
+
const enumNames = [];
|
|
2321
|
+
const discriminatorParentNodes = [];
|
|
2322
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2323
|
+
const node = parseSchema({
|
|
2324
|
+
schema,
|
|
2325
|
+
name
|
|
2326
|
+
}, parserOptions);
|
|
2327
|
+
allNodes.push(node);
|
|
2328
|
+
reportSchemaDiagnostics({
|
|
2329
|
+
node,
|
|
2330
|
+
name
|
|
2331
|
+
});
|
|
2332
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2333
|
+
if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2334
|
+
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2335
|
+
}
|
|
2336
|
+
const circularNames = [..._kubb_core.ast.findCircularSchemas(allNodes)];
|
|
2337
|
+
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2338
|
+
let dedupePlan = null;
|
|
2339
|
+
if (dedupe) {
|
|
2340
|
+
const operationNodes = [];
|
|
2341
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2342
|
+
if (!operation) continue;
|
|
2343
|
+
const operationNode = parseOperation(parserOptions, operation);
|
|
2344
|
+
if (operationNode) operationNodes.push(operationNode);
|
|
2345
|
+
}
|
|
2346
|
+
dedupePlan = createDedupePlan({
|
|
2347
|
+
schemaNodes: allNodes,
|
|
2348
|
+
operationNodes,
|
|
2349
|
+
schemaNames: Object.keys(schemas),
|
|
2350
|
+
circularNames
|
|
2351
|
+
});
|
|
2352
|
+
for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
|
|
2353
|
+
}
|
|
1888
2354
|
return {
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
2355
|
+
refAliasMap,
|
|
2356
|
+
enumNames,
|
|
2357
|
+
circularNames,
|
|
2358
|
+
discriminatorChildMap,
|
|
2359
|
+
dedupePlan
|
|
1894
2360
|
};
|
|
1895
2361
|
}
|
|
2362
|
+
/**
|
|
2363
|
+
* Creates a lazy `InputStreamNode` from already-resolved adapter state.
|
|
2364
|
+
*
|
|
2365
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
2366
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
2367
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
2368
|
+
*
|
|
2369
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
2370
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
2371
|
+
*
|
|
2372
|
+
* @example
|
|
2373
|
+
* ```ts
|
|
2374
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2375
|
+
* for await (const schema of streamNode.schemas) {
|
|
2376
|
+
* // each call to for-await restarts from the first schema
|
|
2377
|
+
* }
|
|
2378
|
+
* ```
|
|
2379
|
+
*/
|
|
2380
|
+
function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
|
|
2381
|
+
const rewriteTopLevelSchema = (node) => {
|
|
2382
|
+
if (!dedupePlan) return node;
|
|
2383
|
+
const canonical = dedupePlan.canonicalBySignature.get(_kubb_core.ast.schemaSignature(node));
|
|
2384
|
+
if (canonical && canonical.name !== node.name) return _kubb_core.ast.createSchema({
|
|
2385
|
+
type: "ref",
|
|
2386
|
+
name: node.name ?? null,
|
|
2387
|
+
ref: canonical.ref,
|
|
2388
|
+
description: node.description,
|
|
2389
|
+
deprecated: node.deprecated
|
|
2390
|
+
});
|
|
2391
|
+
return _kubb_core.ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
|
|
2392
|
+
};
|
|
2393
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2394
|
+
return (async function* () {
|
|
2395
|
+
if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
|
|
2396
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2397
|
+
const alias = refAliasMap.get(name);
|
|
2398
|
+
if (alias?.name && schemas[alias.name]) {
|
|
2399
|
+
yield rewriteTopLevelSchema({
|
|
2400
|
+
...parseSchema({
|
|
2401
|
+
schema: schemas[alias.name],
|
|
2402
|
+
name: alias.name
|
|
2403
|
+
}, parserOptions),
|
|
2404
|
+
name
|
|
2405
|
+
});
|
|
2406
|
+
continue;
|
|
2407
|
+
}
|
|
2408
|
+
const parsed = parseSchema({
|
|
2409
|
+
schema,
|
|
2410
|
+
name
|
|
2411
|
+
}, parserOptions);
|
|
2412
|
+
yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
|
|
2413
|
+
}
|
|
2414
|
+
})();
|
|
2415
|
+
} };
|
|
2416
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2417
|
+
return (async function* () {
|
|
2418
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2419
|
+
if (!operation) continue;
|
|
2420
|
+
const node = parseOperation(parserOptions, operation);
|
|
2421
|
+
if (node) yield dedupePlan ? _kubb_core.ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
|
|
2422
|
+
}
|
|
2423
|
+
})();
|
|
2424
|
+
} };
|
|
2425
|
+
return _kubb_core.ast.createStreamInput(schemasIterable, operationsIterable, meta);
|
|
2426
|
+
}
|
|
1896
2427
|
//#endregion
|
|
1897
2428
|
//#region src/adapter.ts
|
|
1898
2429
|
/**
|
|
1899
|
-
*
|
|
2430
|
+
* Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
|
|
1900
2431
|
*/
|
|
1901
2432
|
const adapterOasName = "oas";
|
|
1902
2433
|
/**
|
|
1903
|
-
*
|
|
2434
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
|
|
2435
|
+
* file at `input.path`, validates it, resolves the base URL, and converts every
|
|
2436
|
+
* schema and operation into the universal AST that every downstream plugin
|
|
2437
|
+
* consumes.
|
|
1904
2438
|
*
|
|
1905
|
-
*
|
|
1906
|
-
*
|
|
2439
|
+
* Configure once on `defineConfig`. The adapter's choices (date representation,
|
|
2440
|
+
* integer width, server URL) apply to every plugin in the build.
|
|
1907
2441
|
*
|
|
1908
2442
|
* @example
|
|
1909
2443
|
* ```ts
|
|
@@ -1912,17 +2446,118 @@ const adapterOasName = "oas";
|
|
|
1912
2446
|
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1913
2447
|
*
|
|
1914
2448
|
* export default defineConfig({
|
|
1915
|
-
*
|
|
1916
|
-
*
|
|
2449
|
+
* input: { path: './petStore.yaml' },
|
|
2450
|
+
* output: { path: './src/gen' },
|
|
2451
|
+
* adapter: adapterOas({
|
|
2452
|
+
* serverIndex: 0,
|
|
2453
|
+
* discriminator: 'inherit',
|
|
2454
|
+
* dateType: 'date',
|
|
2455
|
+
* }),
|
|
1917
2456
|
* plugins: [pluginTs()],
|
|
1918
2457
|
* })
|
|
1919
2458
|
* ```
|
|
1920
2459
|
*/
|
|
1921
2460
|
const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
1922
|
-
const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", dateType = DEFAULT_PARSER_OPTIONS.dateType, integerType = DEFAULT_PARSER_OPTIONS.integerType, unknownType = DEFAULT_PARSER_OPTIONS.unknownType, enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix, emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType } = options;
|
|
2461
|
+
const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", dedupe = true, 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;
|
|
2462
|
+
const parserOptions = {
|
|
2463
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2464
|
+
dateType,
|
|
2465
|
+
integerType,
|
|
2466
|
+
unknownType,
|
|
2467
|
+
emptySchemaType,
|
|
2468
|
+
enumSuffix
|
|
2469
|
+
};
|
|
1923
2470
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1924
|
-
let parsedDocument;
|
|
1925
|
-
|
|
2471
|
+
let parsedDocument = null;
|
|
2472
|
+
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2473
|
+
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2474
|
+
const baseOasCache = /* @__PURE__ */ new WeakMap();
|
|
2475
|
+
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2476
|
+
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2477
|
+
function ensureDocument(source) {
|
|
2478
|
+
const cached = documentCache.get(source);
|
|
2479
|
+
if (cached) return cached;
|
|
2480
|
+
const promise = (async () => {
|
|
2481
|
+
const fresh = await parseFromConfig(source);
|
|
2482
|
+
if (validate) await validateDocument(fresh);
|
|
2483
|
+
parsedDocument = fresh;
|
|
2484
|
+
return fresh;
|
|
2485
|
+
})();
|
|
2486
|
+
documentCache.set(source, promise);
|
|
2487
|
+
return promise;
|
|
2488
|
+
}
|
|
2489
|
+
function ensureSchemas(document) {
|
|
2490
|
+
const cached = schemasCache.get(document);
|
|
2491
|
+
if (cached) return cached;
|
|
2492
|
+
const promise = Promise.resolve().then(() => {
|
|
2493
|
+
const result = getSchemas(document, { contentType });
|
|
2494
|
+
nameMapping = result.nameMapping;
|
|
2495
|
+
return result.schemas;
|
|
2496
|
+
});
|
|
2497
|
+
schemasCache.set(document, promise);
|
|
2498
|
+
return promise;
|
|
2499
|
+
}
|
|
2500
|
+
function ensureBaseOas(document) {
|
|
2501
|
+
const cached = baseOasCache.get(document);
|
|
2502
|
+
if (cached) return cached;
|
|
2503
|
+
const baseOas = new oas.default(document);
|
|
2504
|
+
baseOasCache.set(document, baseOas);
|
|
2505
|
+
return baseOas;
|
|
2506
|
+
}
|
|
2507
|
+
function ensureSchemaParser(document) {
|
|
2508
|
+
const cached = schemaParserCache.get(document);
|
|
2509
|
+
if (cached) return cached;
|
|
2510
|
+
const parser = createSchemaParser({
|
|
2511
|
+
document,
|
|
2512
|
+
contentType
|
|
2513
|
+
});
|
|
2514
|
+
schemaParserCache.set(document, parser);
|
|
2515
|
+
return parser;
|
|
2516
|
+
}
|
|
2517
|
+
function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
|
|
2518
|
+
const cached = preScanCache.get(document);
|
|
2519
|
+
if (cached) return cached;
|
|
2520
|
+
const result = preScan({
|
|
2521
|
+
schemas,
|
|
2522
|
+
parseSchema,
|
|
2523
|
+
parseOperation,
|
|
2524
|
+
baseOas,
|
|
2525
|
+
parserOptions,
|
|
2526
|
+
discriminator,
|
|
2527
|
+
dedupe
|
|
2528
|
+
});
|
|
2529
|
+
preScanCache.set(document, result);
|
|
2530
|
+
return result;
|
|
2531
|
+
}
|
|
2532
|
+
async function createStream(source) {
|
|
2533
|
+
const document = await ensureDocument(source);
|
|
2534
|
+
const schemas = await ensureSchemas(document);
|
|
2535
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2536
|
+
const baseOas = ensureBaseOas(document);
|
|
2537
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
|
|
2538
|
+
return createInputStream({
|
|
2539
|
+
schemas,
|
|
2540
|
+
parseSchema,
|
|
2541
|
+
parseOperation,
|
|
2542
|
+
baseOas,
|
|
2543
|
+
parserOptions,
|
|
2544
|
+
refAliasMap,
|
|
2545
|
+
discriminatorChildMap,
|
|
2546
|
+
dedupePlan,
|
|
2547
|
+
meta: {
|
|
2548
|
+
title: document.info?.title,
|
|
2549
|
+
description: document.info?.description,
|
|
2550
|
+
version: document.info?.version,
|
|
2551
|
+
baseURL: resolveBaseUrl({
|
|
2552
|
+
document,
|
|
2553
|
+
serverIndex,
|
|
2554
|
+
serverVariables
|
|
2555
|
+
}),
|
|
2556
|
+
circularNames,
|
|
2557
|
+
enumNames
|
|
2558
|
+
}
|
|
2559
|
+
});
|
|
2560
|
+
}
|
|
1926
2561
|
return {
|
|
1927
2562
|
name: "oas",
|
|
1928
2563
|
get options() {
|
|
@@ -1932,6 +2567,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1932
2567
|
serverIndex,
|
|
1933
2568
|
serverVariables,
|
|
1934
2569
|
discriminator,
|
|
2570
|
+
dedupe,
|
|
1935
2571
|
dateType,
|
|
1936
2572
|
integerType,
|
|
1937
2573
|
unknownType,
|
|
@@ -1943,8 +2579,9 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1943
2579
|
get document() {
|
|
1944
2580
|
return parsedDocument;
|
|
1945
2581
|
},
|
|
1946
|
-
|
|
1947
|
-
|
|
2582
|
+
async validate(input, options) {
|
|
2583
|
+
await assertInputExists(input);
|
|
2584
|
+
await validateDocument(await parseDocument(input), options);
|
|
1948
2585
|
},
|
|
1949
2586
|
getImports(node, resolve) {
|
|
1950
2587
|
return _kubb_core.ast.collectImports({
|
|
@@ -1952,7 +2589,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1952
2589
|
nameMapping,
|
|
1953
2590
|
resolve: (schemaName) => {
|
|
1954
2591
|
const result = resolve(schemaName);
|
|
1955
|
-
if (!result) return;
|
|
2592
|
+
if (!result) return null;
|
|
1956
2593
|
return _kubb_core.ast.createImport({
|
|
1957
2594
|
name: [result.name],
|
|
1958
2595
|
path: result.path
|
|
@@ -1961,53 +2598,19 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
1961
2598
|
});
|
|
1962
2599
|
},
|
|
1963
2600
|
async parse(source) {
|
|
1964
|
-
const
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
dateType,
|
|
1971
|
-
integerType,
|
|
1972
|
-
unknownType,
|
|
1973
|
-
emptySchemaType,
|
|
1974
|
-
enumSuffix
|
|
2601
|
+
const streamNode = await createStream(source);
|
|
2602
|
+
const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)]);
|
|
2603
|
+
return _kubb_core.ast.createInput({
|
|
2604
|
+
schemas,
|
|
2605
|
+
operations,
|
|
2606
|
+
meta: streamNode.meta
|
|
1975
2607
|
});
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
parsedDocument = document;
|
|
1979
|
-
inputNode = _kubb_core.ast.createInput({
|
|
1980
|
-
...node,
|
|
1981
|
-
meta: {
|
|
1982
|
-
title: document.info?.title,
|
|
1983
|
-
description: document.info?.description,
|
|
1984
|
-
version: document.info?.version,
|
|
1985
|
-
baseURL
|
|
1986
|
-
}
|
|
1987
|
-
});
|
|
1988
|
-
return inputNode;
|
|
1989
|
-
}
|
|
2608
|
+
},
|
|
2609
|
+
stream: createStream
|
|
1990
2610
|
};
|
|
1991
2611
|
});
|
|
1992
2612
|
//#endregion
|
|
1993
|
-
//#region src/types.ts
|
|
1994
|
-
/**
|
|
1995
|
-
* Maps uppercase HTTP method names to lowercase for backwards compatibility.
|
|
1996
|
-
*
|
|
1997
|
-
* @example
|
|
1998
|
-
* ```ts
|
|
1999
|
-
* HttpMethods['GET'] // 'get'
|
|
2000
|
-
* HttpMethods['POST'] // 'post'
|
|
2001
|
-
* ```
|
|
2002
|
-
*/
|
|
2003
|
-
const HttpMethods = Object.fromEntries(Object.entries(_kubb_core.ast.httpMethods).map(([lower, upper]) => [upper, lower]));
|
|
2004
|
-
//#endregion
|
|
2005
|
-
exports.HttpMethods = HttpMethods;
|
|
2006
2613
|
exports.adapterOas = adapterOas;
|
|
2007
2614
|
exports.adapterOasName = adapterOasName;
|
|
2008
|
-
exports.mergeDocuments = mergeDocuments;
|
|
2009
|
-
exports.parseDocument = parseDocument;
|
|
2010
|
-
exports.parseFromConfig = parseFromConfig;
|
|
2011
|
-
exports.validateDocument = validateDocument;
|
|
2012
2615
|
|
|
2013
2616
|
//# sourceMappingURL=index.cjs.map
|