@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.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import "./chunk
|
|
2
|
-
import { ast, createAdapter } from "@kubb/core";
|
|
1
|
+
import "./chunk-C0LytTxp.js";
|
|
2
|
+
import { Diagnostics, ast, createAdapter } from "@kubb/core";
|
|
3
|
+
import BaseOas from "oas";
|
|
3
4
|
import path from "node:path";
|
|
4
|
-
import {
|
|
5
|
+
import { access } from "node:fs/promises";
|
|
5
6
|
import OASNormalize from "oas-normalize";
|
|
6
|
-
import
|
|
7
|
-
import BaseOas from "oas";
|
|
7
|
+
import { childName, enumPropName, extractRefName, findDiscriminator } from "@kubb/ast/utils";
|
|
8
8
|
import { isRef } from "oas/types";
|
|
9
9
|
import { matchesMimeType } from "oas/utils";
|
|
10
10
|
//#region src/constants.ts
|
|
@@ -89,6 +89,18 @@ const structuralKeys = new Set([
|
|
|
89
89
|
* formatMap['float'] // 'number'
|
|
90
90
|
* ```
|
|
91
91
|
*/
|
|
92
|
+
/**
|
|
93
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
94
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
95
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
96
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
97
|
+
*/
|
|
98
|
+
const specialCasedFormats = new Set([
|
|
99
|
+
"int64",
|
|
100
|
+
"date-time",
|
|
101
|
+
"date",
|
|
102
|
+
"time"
|
|
103
|
+
]);
|
|
92
104
|
const formatMap = {
|
|
93
105
|
uuid: "uuid",
|
|
94
106
|
email: "email",
|
|
@@ -127,87 +139,6 @@ const typeOptionMap = new Map([
|
|
|
127
139
|
["void", ast.schemaTypes.void]
|
|
128
140
|
]);
|
|
129
141
|
//#endregion
|
|
130
|
-
//#region src/discriminator.ts
|
|
131
|
-
/**
|
|
132
|
-
* Injects discriminator enum values into child schemas so they know which value identifies them.
|
|
133
|
-
*
|
|
134
|
-
* Finds every union schema in `input.schemas` that has a `discriminatorPropertyName`, collects the
|
|
135
|
-
* enum value each union member is mapped to, then adds (or replaces) that property on the matching
|
|
136
|
-
* child object schema.
|
|
137
|
-
*
|
|
138
|
-
* Returns a new `InputNode` — the original is never mutated.
|
|
139
|
-
*
|
|
140
|
-
* @example
|
|
141
|
-
* ```ts
|
|
142
|
-
* const { root } = parseOas(document, options)
|
|
143
|
-
* const next = applyDiscriminatorInheritance(root)
|
|
144
|
-
* ```
|
|
145
|
-
*/
|
|
146
|
-
function applyDiscriminatorInheritance(root) {
|
|
147
|
-
const childMap = /* @__PURE__ */ new Map();
|
|
148
|
-
for (const schema of root.schemas) {
|
|
149
|
-
let unionNode = ast.narrowSchema(schema, "union");
|
|
150
|
-
if (!unionNode) {
|
|
151
|
-
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
152
|
-
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
153
|
-
const u = ast.narrowSchema(m, "union");
|
|
154
|
-
if (u) {
|
|
155
|
-
unionNode = u;
|
|
156
|
-
break;
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
161
|
-
const { discriminatorPropertyName, members } = unionNode;
|
|
162
|
-
for (const member of members) {
|
|
163
|
-
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
164
|
-
if (!intersectionNode?.members) continue;
|
|
165
|
-
let refNode;
|
|
166
|
-
let objNode;
|
|
167
|
-
for (const m of intersectionNode.members) {
|
|
168
|
-
refNode ??= ast.narrowSchema(m, "ref");
|
|
169
|
-
objNode ??= ast.narrowSchema(m, "object");
|
|
170
|
-
}
|
|
171
|
-
if (!refNode?.name || !objNode) continue;
|
|
172
|
-
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
173
|
-
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : void 0;
|
|
174
|
-
if (!enumNode?.enumValues?.length) continue;
|
|
175
|
-
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
176
|
-
if (!enumValues.length) continue;
|
|
177
|
-
const existing = childMap.get(refNode.name);
|
|
178
|
-
if (existing) existing.enumValues.push(...enumValues);
|
|
179
|
-
else childMap.set(refNode.name, {
|
|
180
|
-
propertyName: discriminatorPropertyName,
|
|
181
|
-
enumValues: [...enumValues]
|
|
182
|
-
});
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (childMap.size === 0) return root;
|
|
186
|
-
return ast.transform(root, { schema(node, { parent }) {
|
|
187
|
-
if (parent?.kind !== "Input" || !node.name) return;
|
|
188
|
-
const entry = childMap.get(node.name);
|
|
189
|
-
if (!entry) return;
|
|
190
|
-
const objectNode = ast.narrowSchema(node, "object");
|
|
191
|
-
if (!objectNode) return;
|
|
192
|
-
const { propertyName, enumValues } = entry;
|
|
193
|
-
const enumSchema = ast.createSchema({
|
|
194
|
-
type: "enum",
|
|
195
|
-
enumValues
|
|
196
|
-
});
|
|
197
|
-
const newProp = ast.createProperty({
|
|
198
|
-
name: propertyName,
|
|
199
|
-
required: true,
|
|
200
|
-
schema: enumSchema
|
|
201
|
-
});
|
|
202
|
-
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
203
|
-
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
204
|
-
return {
|
|
205
|
-
...objectNode,
|
|
206
|
-
properties: newProperties
|
|
207
|
-
};
|
|
208
|
-
} });
|
|
209
|
-
}
|
|
210
|
-
//#endregion
|
|
211
142
|
//#region ../../internals/utils/src/casing.ts
|
|
212
143
|
/**
|
|
213
144
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -272,6 +203,42 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
272
203
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
273
204
|
}
|
|
274
205
|
//#endregion
|
|
206
|
+
//#region ../../internals/utils/src/runtime.ts
|
|
207
|
+
/**
|
|
208
|
+
* Returns `true` when the current process is running under Bun.
|
|
209
|
+
*
|
|
210
|
+
* Detection keys off the global `Bun` object rather than `process.versions`,
|
|
211
|
+
* because Bun polyfills `process.versions.node` for Node compatibility and would
|
|
212
|
+
* otherwise look like Node.
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* if (isBun()) {
|
|
217
|
+
* await Bun.write(path, data)
|
|
218
|
+
* }
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
function isBun() {
|
|
222
|
+
return typeof Bun !== "undefined";
|
|
223
|
+
}
|
|
224
|
+
//#endregion
|
|
225
|
+
//#region ../../internals/utils/src/fs.ts
|
|
226
|
+
/**
|
|
227
|
+
* Resolves to `true` when the file or directory at `path` exists.
|
|
228
|
+
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* ```ts
|
|
232
|
+
* if (await exists('./kubb.config.ts')) {
|
|
233
|
+
* const content = await read('./kubb.config.ts')
|
|
234
|
+
* }
|
|
235
|
+
* ```
|
|
236
|
+
*/
|
|
237
|
+
async function exists(path) {
|
|
238
|
+
if (isBun()) return Bun.file(path).exists();
|
|
239
|
+
return access(path).then(() => true, () => false);
|
|
240
|
+
}
|
|
241
|
+
//#endregion
|
|
275
242
|
//#region ../../internals/utils/src/object.ts
|
|
276
243
|
/**
|
|
277
244
|
* Returns `true` when `value` is a plain (non-null, non-array) object.
|
|
@@ -406,6 +373,22 @@ const reservedWords = new Set([
|
|
|
406
373
|
*/
|
|
407
374
|
function isValidVarName(name) {
|
|
408
375
|
if (!name || reservedWords.has(name)) return false;
|
|
376
|
+
return isIdentifier(name);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Returns `true` when `name` is syntactically a valid identifier, ignoring reserved words.
|
|
380
|
+
*
|
|
381
|
+
* Reserved words and globals (`class`, `name`, `Date`, …) are valid as bare object-literal keys
|
|
382
|
+
* even though they are not valid variable names, so use this (not {@link isValidVarName}) when
|
|
383
|
+
* deciding whether an object key needs quoting.
|
|
384
|
+
*
|
|
385
|
+
* @example
|
|
386
|
+
* ```ts
|
|
387
|
+
* isIdentifier('name') // true
|
|
388
|
+
* isIdentifier('x-total')// false
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
function isIdentifier(name) {
|
|
409
392
|
return /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(name);
|
|
410
393
|
}
|
|
411
394
|
//#endregion
|
|
@@ -517,12 +500,13 @@ var URLPath = class {
|
|
|
517
500
|
* @example
|
|
518
501
|
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
519
502
|
*/
|
|
520
|
-
toTemplateString({ prefix
|
|
521
|
-
|
|
503
|
+
toTemplateString({ prefix, replacer } = {}) {
|
|
504
|
+
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
522
505
|
if (i % 2 === 0) return part;
|
|
523
506
|
const param = this.#transformParam(part);
|
|
524
507
|
return `\${${replacer ? replacer(param) : param}}`;
|
|
525
|
-
}).join("")
|
|
508
|
+
}).join("");
|
|
509
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
526
510
|
}
|
|
527
511
|
/**
|
|
528
512
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
@@ -620,8 +604,8 @@ function isDiscriminator(obj) {
|
|
|
620
604
|
* Loads and dereferences an OpenAPI document, returning the raw `Document`.
|
|
621
605
|
*
|
|
622
606
|
* Accepts a file path string or an already-parsed document object. File paths are bundled via
|
|
623
|
-
*
|
|
624
|
-
* to OpenAPI 3.0 via `swagger2openapi`.
|
|
607
|
+
* `@apidevtools/json-schema-ref-parser` to resolve external `$ref`s. Swagger 2.0 documents are
|
|
608
|
+
* automatically up-converted to OpenAPI 3.0 via `swagger2openapi`.
|
|
625
609
|
*
|
|
626
610
|
* @example
|
|
627
611
|
* ```ts
|
|
@@ -630,19 +614,19 @@ function isDiscriminator(obj) {
|
|
|
630
614
|
* ```
|
|
631
615
|
*/
|
|
632
616
|
async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true } = {}) {
|
|
633
|
-
if (typeof pathOrApi === "string" && canBundle)
|
|
634
|
-
ref
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
});
|
|
617
|
+
if (typeof pathOrApi === "string" && canBundle) {
|
|
618
|
+
const { $RefParser } = await import("@apidevtools/json-schema-ref-parser");
|
|
619
|
+
return parseDocument(await $RefParser.bundle(pathOrApi), {
|
|
620
|
+
canBundle: false,
|
|
621
|
+
enablePaths
|
|
622
|
+
});
|
|
623
|
+
}
|
|
641
624
|
const document = await new OASNormalize(pathOrApi, {
|
|
642
625
|
enablePaths,
|
|
643
626
|
colorizeErrors: true
|
|
644
627
|
}).load();
|
|
645
628
|
if (isOpenApiV2Document(document)) {
|
|
629
|
+
const { default: swagger2openapi } = await import("swagger2openapi");
|
|
646
630
|
const { openapi } = await swagger2openapi.convertObj(document, { anchors: true });
|
|
647
631
|
return openapi;
|
|
648
632
|
}
|
|
@@ -660,12 +644,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
660
644
|
* ```
|
|
661
645
|
*/
|
|
662
646
|
async function mergeDocuments(pathOrApi) {
|
|
663
|
-
const documents =
|
|
664
|
-
for (const p of pathOrApi) documents.push(await parseDocument(p, {
|
|
647
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
665
648
|
enablePaths: false,
|
|
666
649
|
canBundle: false
|
|
667
|
-
}));
|
|
668
|
-
if (documents.length === 0) throw new Error(
|
|
650
|
+
})));
|
|
651
|
+
if (documents.length === 0) throw new Diagnostics.Error({
|
|
652
|
+
code: Diagnostics.code.inputRequired,
|
|
653
|
+
severity: "error",
|
|
654
|
+
message: "No OAS documents were provided for merging.",
|
|
655
|
+
help: "Pass at least one path or document to `input.path`.",
|
|
656
|
+
location: { kind: "config" }
|
|
657
|
+
});
|
|
669
658
|
const seed = {
|
|
670
659
|
openapi: MERGE_OPENAPI_VERSION,
|
|
671
660
|
info: {
|
|
@@ -681,9 +670,9 @@ async function mergeDocuments(pathOrApi) {
|
|
|
681
670
|
* Creates a `Document` from an `AdapterSource`.
|
|
682
671
|
*
|
|
683
672
|
* Handles all three source types:
|
|
684
|
-
* - `{ type: 'path' }`
|
|
685
|
-
* - `{ type: 'paths' }`
|
|
686
|
-
* - `{ type: 'data' }`
|
|
673
|
+
* - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
|
|
674
|
+
* - `{ type: 'paths' }` merges multiple file paths into a single document.
|
|
675
|
+
* - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
|
|
687
676
|
*
|
|
688
677
|
* @example
|
|
689
678
|
* ```ts
|
|
@@ -691,14 +680,31 @@ async function mergeDocuments(pathOrApi) {
|
|
|
691
680
|
* const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
|
|
692
681
|
* ```
|
|
693
682
|
*/
|
|
694
|
-
function parseFromConfig(source) {
|
|
683
|
+
async function parseFromConfig(source) {
|
|
695
684
|
if (source.type === "data") {
|
|
696
685
|
if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
|
|
697
686
|
return parseDocument(source.data, { canBundle: false });
|
|
698
687
|
}
|
|
699
688
|
if (source.type === "paths") return mergeDocuments(source.paths);
|
|
700
689
|
if (new URLPath(source.path).isURL) return parseDocument(source.path);
|
|
701
|
-
|
|
690
|
+
const resolved = path.resolve(path.dirname(source.path), source.path);
|
|
691
|
+
await assertInputExists(resolved);
|
|
692
|
+
return parseDocument(resolved);
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
|
|
696
|
+
* URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
|
|
697
|
+
* its parse error instead.
|
|
698
|
+
*/
|
|
699
|
+
async function assertInputExists(input) {
|
|
700
|
+
if (new URLPath(input).isURL) return;
|
|
701
|
+
if (!await exists(input)) throw new Diagnostics.Error({
|
|
702
|
+
code: Diagnostics.code.inputNotFound,
|
|
703
|
+
severity: "error",
|
|
704
|
+
message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
|
|
705
|
+
help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
|
|
706
|
+
location: { kind: "config" }
|
|
707
|
+
});
|
|
702
708
|
}
|
|
703
709
|
/**
|
|
704
710
|
* Validates an OpenAPI document using `oas-normalize` with colorized error output.
|
|
@@ -720,6 +726,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
720
726
|
}
|
|
721
727
|
//#endregion
|
|
722
728
|
//#region src/refs.ts
|
|
729
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
723
730
|
/**
|
|
724
731
|
* Resolves a local JSON pointer reference from a document.
|
|
725
732
|
*
|
|
@@ -735,10 +742,31 @@ function resolveRef(document, $ref) {
|
|
|
735
742
|
const origRef = $ref;
|
|
736
743
|
$ref = $ref.trim();
|
|
737
744
|
if ($ref === "") return null;
|
|
738
|
-
if (
|
|
739
|
-
|
|
745
|
+
if (!$ref.startsWith("#")) return null;
|
|
746
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
747
|
+
let docCache = _refCache.get(document);
|
|
748
|
+
if (!docCache) {
|
|
749
|
+
docCache = /* @__PURE__ */ new Map();
|
|
750
|
+
_refCache.set(document, docCache);
|
|
751
|
+
}
|
|
752
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
740
753
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
741
|
-
if (!current)
|
|
754
|
+
if (!current) {
|
|
755
|
+
const diagnostic = {
|
|
756
|
+
code: Diagnostics.code.refNotFound,
|
|
757
|
+
severity: "error",
|
|
758
|
+
message: `Could not find a definition for ${origRef}.`,
|
|
759
|
+
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
760
|
+
location: {
|
|
761
|
+
kind: "schema",
|
|
762
|
+
pointer: origRef,
|
|
763
|
+
ref: origRef
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
|
|
767
|
+
return null;
|
|
768
|
+
}
|
|
769
|
+
docCache.set($ref, current);
|
|
742
770
|
return current;
|
|
743
771
|
}
|
|
744
772
|
/**
|
|
@@ -762,6 +790,35 @@ function dereferenceWithRef(document, schema) {
|
|
|
762
790
|
return schema;
|
|
763
791
|
}
|
|
764
792
|
//#endregion
|
|
793
|
+
//#region src/dialect.ts
|
|
794
|
+
/**
|
|
795
|
+
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
796
|
+
*
|
|
797
|
+
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
798
|
+
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
799
|
+
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
800
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
801
|
+
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
802
|
+
* the rest unchanged.
|
|
803
|
+
*
|
|
804
|
+
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
805
|
+
* JSON Schema vocabulary, so the converters keep that common case.
|
|
806
|
+
*
|
|
807
|
+
* @example
|
|
808
|
+
* ```ts
|
|
809
|
+
* const parser = createSchemaParser(context) // uses oasDialect
|
|
810
|
+
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
811
|
+
* ```
|
|
812
|
+
*/
|
|
813
|
+
const oasDialect = ast.defineSchemaDialect({
|
|
814
|
+
name: "oas",
|
|
815
|
+
isNullable,
|
|
816
|
+
isReference,
|
|
817
|
+
isDiscriminator,
|
|
818
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
819
|
+
resolveRef
|
|
820
|
+
});
|
|
821
|
+
//#endregion
|
|
765
822
|
//#region src/resolvers.ts
|
|
766
823
|
/**
|
|
767
824
|
* Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
|
|
@@ -783,7 +840,16 @@ function resolveServerUrl(server, overrides) {
|
|
|
783
840
|
for (const [key, variable] of Object.entries(server.variables)) {
|
|
784
841
|
const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
|
|
785
842
|
if (value === void 0) continue;
|
|
786
|
-
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(
|
|
843
|
+
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Diagnostics.Error({
|
|
844
|
+
code: Diagnostics.code.invalidServerVariable,
|
|
845
|
+
severity: "error",
|
|
846
|
+
message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
|
|
847
|
+
help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
|
|
848
|
+
location: {
|
|
849
|
+
kind: "document",
|
|
850
|
+
pointer: "#/servers"
|
|
851
|
+
}
|
|
852
|
+
});
|
|
787
853
|
url = url.replaceAll(`{${key}}`, value);
|
|
788
854
|
}
|
|
789
855
|
return url;
|
|
@@ -796,6 +862,15 @@ function getSchemaType(format) {
|
|
|
796
862
|
return formatMap[format] ?? null;
|
|
797
863
|
}
|
|
798
864
|
/**
|
|
865
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
|
|
866
|
+
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
|
|
867
|
+
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
868
|
+
* diagnostic in step with the parser as `formatMap` grows.
|
|
869
|
+
*/
|
|
870
|
+
function isHandledFormat(format) {
|
|
871
|
+
return getSchemaType(format) !== null || specialCasedFormats.has(format);
|
|
872
|
+
}
|
|
873
|
+
/**
|
|
799
874
|
* Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
800
875
|
* Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
|
|
801
876
|
*/
|
|
@@ -805,12 +880,6 @@ function getPrimitiveType(type) {
|
|
|
805
880
|
return "string";
|
|
806
881
|
}
|
|
807
882
|
/**
|
|
808
|
-
* Narrows a content-type string to the `MediaType` union Kubb recognizes, or returns `null`.
|
|
809
|
-
*/
|
|
810
|
-
function getMediaType(contentType) {
|
|
811
|
-
return Object.values(ast.mediaTypes).includes(contentType) ? contentType : null;
|
|
812
|
-
}
|
|
813
|
-
/**
|
|
814
883
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
815
884
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
816
885
|
* `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
|
|
@@ -898,7 +967,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
898
967
|
/**
|
|
899
968
|
* Flattens a keyword-only `allOf` into its parent schema.
|
|
900
969
|
*
|
|
901
|
-
* Only flattens when every member is a plain fragment
|
|
970
|
+
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
902
971
|
* (see `structuralKeys`). Outer schema values take precedence over fragment values.
|
|
903
972
|
* Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
|
|
904
973
|
*
|
|
@@ -908,7 +977,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
908
977
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
909
978
|
*
|
|
910
979
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
911
|
-
* // returned unchanged
|
|
980
|
+
* // returned unchanged, contains a $ref
|
|
912
981
|
* ```
|
|
913
982
|
*/
|
|
914
983
|
/**
|
|
@@ -934,7 +1003,7 @@ function flattenSchema(schema) {
|
|
|
934
1003
|
/**
|
|
935
1004
|
* Extracts the inline schema from a media-type `content` map.
|
|
936
1005
|
*
|
|
937
|
-
* Prefers `preferredContentType` when given
|
|
1006
|
+
* Prefers `preferredContentType` when given, otherwise uses the first key in the map.
|
|
938
1007
|
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
939
1008
|
*
|
|
940
1009
|
* @example
|
|
@@ -953,21 +1022,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
953
1022
|
/**
|
|
954
1023
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
955
1024
|
*/
|
|
956
|
-
function collectRefs(schema
|
|
1025
|
+
function* collectRefs(schema) {
|
|
957
1026
|
if (Array.isArray(schema)) {
|
|
958
|
-
for (const item of schema) collectRefs(item
|
|
959
|
-
return
|
|
1027
|
+
for (const item of schema) yield* collectRefs(item);
|
|
1028
|
+
return;
|
|
960
1029
|
}
|
|
961
1030
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
962
1031
|
const value = schema[key];
|
|
963
|
-
if (key === "$ref" && typeof value === "string") {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
1032
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
1033
|
+
yield* collectRefs(value);
|
|
1034
|
+
continue;
|
|
1035
|
+
}
|
|
1036
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
1037
|
+
const name = value.slice(21);
|
|
1038
|
+
if (name) yield name;
|
|
1039
|
+
}
|
|
969
1040
|
}
|
|
970
|
-
return refs;
|
|
971
1041
|
}
|
|
972
1042
|
/**
|
|
973
1043
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -983,7 +1053,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
983
1053
|
*/
|
|
984
1054
|
function sortSchemas(schemas) {
|
|
985
1055
|
const deps = /* @__PURE__ */ new Map();
|
|
986
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
1056
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
987
1057
|
const sorted = [];
|
|
988
1058
|
const visited = /* @__PURE__ */ new Set();
|
|
989
1059
|
function visit(name, stack) {
|
|
@@ -1118,15 +1188,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1118
1188
|
readOnly: schema.readOnly,
|
|
1119
1189
|
writeOnly: schema.writeOnly,
|
|
1120
1190
|
default: defaultValue,
|
|
1121
|
-
example: schema.example
|
|
1191
|
+
example: schema.example,
|
|
1192
|
+
format: schema.format
|
|
1122
1193
|
};
|
|
1123
1194
|
}
|
|
1124
1195
|
/**
|
|
1125
1196
|
* Returns all request body content type keys for an operation.
|
|
1126
1197
|
*
|
|
1127
|
-
* The requestBody is dereferenced
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1198
|
+
* The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
|
|
1199
|
+
* `getRequestSchema` already performs), so the returned list accurately reflects the
|
|
1200
|
+
* available content types even for referenced bodies.
|
|
1130
1201
|
*
|
|
1131
1202
|
* @example
|
|
1132
1203
|
* ```ts
|
|
@@ -1140,6 +1211,31 @@ function getRequestBodyContentTypes(document, operation) {
|
|
|
1140
1211
|
if (!body) return [];
|
|
1141
1212
|
return body.content ? Object.keys(body.content) : [];
|
|
1142
1213
|
}
|
|
1214
|
+
/**
|
|
1215
|
+
* Returns all response content type keys for an operation at a given status code.
|
|
1216
|
+
*
|
|
1217
|
+
* Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
|
|
1218
|
+
* so the returned list reflects the available content types even for referenced responses.
|
|
1219
|
+
*
|
|
1220
|
+
* @example
|
|
1221
|
+
* ```ts
|
|
1222
|
+
* getResponseBodyContentTypes(document, operation, 200)
|
|
1223
|
+
* // ['application/json', 'application/xml']
|
|
1224
|
+
* ```
|
|
1225
|
+
*/
|
|
1226
|
+
function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
1227
|
+
if (operation.schema.responses) {
|
|
1228
|
+
const responses = operation.schema.responses;
|
|
1229
|
+
for (const key in responses) {
|
|
1230
|
+
const schema = responses[key];
|
|
1231
|
+
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1235
|
+
if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
|
|
1236
|
+
const body = responseObj;
|
|
1237
|
+
return body.content ? Object.keys(body.content) : [];
|
|
1238
|
+
}
|
|
1143
1239
|
//#endregion
|
|
1144
1240
|
//#region src/parser.ts
|
|
1145
1241
|
/**
|
|
@@ -1168,9 +1264,9 @@ function normalizeArrayEnum(schema) {
|
|
|
1168
1264
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1169
1265
|
* made possible by hoisting of function declarations.
|
|
1170
1266
|
*
|
|
1171
|
-
* @
|
|
1267
|
+
* @internal
|
|
1172
1268
|
*/
|
|
1173
|
-
function createSchemaParser(ctx) {
|
|
1269
|
+
function createSchemaParser(ctx, dialect = oasDialect) {
|
|
1174
1270
|
const document = ctx.document;
|
|
1175
1271
|
/**
|
|
1176
1272
|
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
@@ -1178,6 +1274,15 @@ function createSchemaParser(ctx) {
|
|
|
1178
1274
|
*/
|
|
1179
1275
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1180
1276
|
/**
|
|
1277
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1278
|
+
*
|
|
1279
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1280
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1281
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1282
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1283
|
+
*/
|
|
1284
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1285
|
+
/**
|
|
1181
1286
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1182
1287
|
*
|
|
1183
1288
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1186,20 +1291,26 @@ function createSchemaParser(ctx) {
|
|
|
1186
1291
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1187
1292
|
*/
|
|
1188
1293
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1189
|
-
let resolvedSchema;
|
|
1294
|
+
let resolvedSchema = null;
|
|
1190
1295
|
const refPath = schema.$ref;
|
|
1191
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1296
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1297
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1298
|
+
try {
|
|
1299
|
+
const referenced = dialect.resolveRef(document, refPath);
|
|
1300
|
+
if (referenced) {
|
|
1301
|
+
resolvingRefs.add(refPath);
|
|
1302
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1303
|
+
resolvingRefs.delete(refPath);
|
|
1304
|
+
}
|
|
1305
|
+
} catch {}
|
|
1306
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1197
1307
|
}
|
|
1198
|
-
|
|
1308
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1309
|
+
}
|
|
1199
1310
|
return ast.createSchema({
|
|
1200
1311
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1201
1312
|
type: "ref",
|
|
1202
|
-
name:
|
|
1313
|
+
name: extractRefName(schema.$ref),
|
|
1203
1314
|
ref: schema.$ref,
|
|
1204
1315
|
schema: resolvedSchema
|
|
1205
1316
|
});
|
|
@@ -1212,7 +1323,7 @@ function createSchemaParser(ctx) {
|
|
|
1212
1323
|
const [memberSchema] = schema.allOf;
|
|
1213
1324
|
const memberNode = parseSchema({
|
|
1214
1325
|
schema: memberSchema,
|
|
1215
|
-
name
|
|
1326
|
+
name
|
|
1216
1327
|
}, rawOptions);
|
|
1217
1328
|
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1218
1329
|
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
@@ -1228,21 +1339,22 @@ function createSchemaParser(ctx) {
|
|
|
1228
1339
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1229
1340
|
default: mergedDefault,
|
|
1230
1341
|
example: schema.example ?? memberNode.example,
|
|
1231
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1342
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1343
|
+
format: schema.format ?? memberNode.format
|
|
1232
1344
|
});
|
|
1233
1345
|
}
|
|
1234
1346
|
const filteredDiscriminantValues = [];
|
|
1235
1347
|
const allOfMembers = schema.allOf.filter((item) => {
|
|
1236
|
-
if (!isReference(item) || !name) return true;
|
|
1237
|
-
const deref = resolveRef(document, item.$ref);
|
|
1238
|
-
if (!deref || !isDiscriminator(deref)) return true;
|
|
1348
|
+
if (!dialect.isReference(item) || !name) return true;
|
|
1349
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1350
|
+
if (!deref || !dialect.isDiscriminator(deref)) return true;
|
|
1239
1351
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1240
1352
|
if (!parentUnion) return true;
|
|
1241
1353
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1242
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1354
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1243
1355
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1244
1356
|
if (inOneOf || inMapping) {
|
|
1245
|
-
const discriminatorValue =
|
|
1357
|
+
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
|
|
1246
1358
|
if (discriminatorValue) filteredDiscriminantValues.push({
|
|
1247
1359
|
propertyName: deref.discriminator.propertyName,
|
|
1248
1360
|
value: discriminatorValue
|
|
@@ -1250,22 +1362,28 @@ function createSchemaParser(ctx) {
|
|
|
1250
1362
|
return false;
|
|
1251
1363
|
}
|
|
1252
1364
|
return true;
|
|
1253
|
-
}).map((s) => parseSchema({
|
|
1365
|
+
}).map((s) => parseSchema({
|
|
1366
|
+
schema: s,
|
|
1367
|
+
name
|
|
1368
|
+
}, rawOptions));
|
|
1254
1369
|
const syntheticStart = allOfMembers.length;
|
|
1255
1370
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1256
1371
|
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1257
1372
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1258
1373
|
if (missingRequired.length) {
|
|
1259
1374
|
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1260
|
-
if (!isReference(item)) return [item];
|
|
1261
|
-
const deref = resolveRef(document, item.$ref);
|
|
1262
|
-
return deref && !isReference(deref) ? [deref] : [];
|
|
1375
|
+
if (!dialect.isReference(item)) return [item];
|
|
1376
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1377
|
+
return deref && !dialect.isReference(deref) ? [deref] : [];
|
|
1263
1378
|
});
|
|
1264
1379
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1265
|
-
allOfMembers.push(parseSchema({
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1380
|
+
allOfMembers.push(parseSchema({
|
|
1381
|
+
schema: {
|
|
1382
|
+
properties: { [key]: resolved.properties[key] },
|
|
1383
|
+
required: [key]
|
|
1384
|
+
},
|
|
1385
|
+
name
|
|
1386
|
+
}, rawOptions));
|
|
1269
1387
|
break;
|
|
1270
1388
|
}
|
|
1271
1389
|
}
|
|
@@ -1280,7 +1398,7 @@ function createSchemaParser(ctx) {
|
|
|
1280
1398
|
}));
|
|
1281
1399
|
return ast.createSchema({
|
|
1282
1400
|
type: "intersection",
|
|
1283
|
-
members: [...ast.
|
|
1401
|
+
members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1284
1402
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1285
1403
|
});
|
|
1286
1404
|
}
|
|
@@ -1301,10 +1419,10 @@ function createSchemaParser(ctx) {
|
|
|
1301
1419
|
const strategy = schema.oneOf ? "one" : "any";
|
|
1302
1420
|
const unionBase = {
|
|
1303
1421
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1304
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1422
|
+
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1305
1423
|
strategy
|
|
1306
1424
|
};
|
|
1307
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1425
|
+
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1308
1426
|
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1309
1427
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1310
1428
|
return parseSchema({
|
|
@@ -1314,9 +1432,12 @@ function createSchemaParser(ctx) {
|
|
|
1314
1432
|
})() : void 0;
|
|
1315
1433
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1316
1434
|
const members = unionMembers.map((s) => {
|
|
1317
|
-
const ref = isReference(s) ? s.$ref : void 0;
|
|
1318
|
-
const discriminatorValue =
|
|
1319
|
-
const memberNode = parseSchema({
|
|
1435
|
+
const ref = dialect.isReference(s) ? s.$ref : void 0;
|
|
1436
|
+
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref);
|
|
1437
|
+
const memberNode = parseSchema({
|
|
1438
|
+
schema: s,
|
|
1439
|
+
name
|
|
1440
|
+
}, rawOptions);
|
|
1320
1441
|
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1321
1442
|
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.setDiscriminatorEnum({
|
|
1322
1443
|
node: sharedPropertiesNode,
|
|
@@ -1346,7 +1467,10 @@ function createSchemaParser(ctx) {
|
|
|
1346
1467
|
return ast.createSchema({
|
|
1347
1468
|
type: "union",
|
|
1348
1469
|
...unionBase,
|
|
1349
|
-
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1470
|
+
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1471
|
+
schema: s,
|
|
1472
|
+
name
|
|
1473
|
+
}, rawOptions)))
|
|
1350
1474
|
});
|
|
1351
1475
|
}
|
|
1352
1476
|
/**
|
|
@@ -1360,7 +1484,8 @@ function createSchemaParser(ctx) {
|
|
|
1360
1484
|
name,
|
|
1361
1485
|
title: schema.title,
|
|
1362
1486
|
description: schema.description,
|
|
1363
|
-
deprecated: schema.deprecated
|
|
1487
|
+
deprecated: schema.deprecated,
|
|
1488
|
+
format: schema.format
|
|
1364
1489
|
});
|
|
1365
1490
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1366
1491
|
return ast.createSchema({
|
|
@@ -1450,6 +1575,15 @@ function createSchemaParser(ctx) {
|
|
|
1450
1575
|
}, rawOptions);
|
|
1451
1576
|
const nullInEnum = schema.enum.includes(null);
|
|
1452
1577
|
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1578
|
+
if (nullInEnum && filteredValues.length === 0) return ast.createSchema({
|
|
1579
|
+
type: "null",
|
|
1580
|
+
primitive: "null",
|
|
1581
|
+
name,
|
|
1582
|
+
title: schema.title,
|
|
1583
|
+
description: schema.description,
|
|
1584
|
+
deprecated: schema.deprecated,
|
|
1585
|
+
format: schema.format
|
|
1586
|
+
});
|
|
1453
1587
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
1454
1588
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1455
1589
|
const enumPrimitive = getPrimitiveType(type);
|
|
@@ -1464,7 +1598,8 @@ function createSchemaParser(ctx) {
|
|
|
1464
1598
|
readOnly: schema.readOnly,
|
|
1465
1599
|
writeOnly: schema.writeOnly,
|
|
1466
1600
|
default: enumDefault,
|
|
1467
|
-
example: schema.example
|
|
1601
|
+
example: schema.example,
|
|
1602
|
+
format: schema.format
|
|
1468
1603
|
};
|
|
1469
1604
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1470
1605
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1498,20 +1633,23 @@ function createSchemaParser(ctx) {
|
|
|
1498
1633
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1499
1634
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1500
1635
|
const resolvedPropSchema = propSchema;
|
|
1501
|
-
const propNullable = isNullable(resolvedPropSchema);
|
|
1636
|
+
const propNullable = dialect.isNullable(resolvedPropSchema);
|
|
1502
1637
|
const propNode = parseSchema({
|
|
1503
1638
|
schema: resolvedPropSchema,
|
|
1504
|
-
name:
|
|
1639
|
+
name: childName(name, propName)
|
|
1505
1640
|
}, rawOptions);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1641
|
+
const schemaNode = (() => {
|
|
1642
|
+
const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
|
|
1643
|
+
const tupleNode = ast.narrowSchema(node, "tuple");
|
|
1644
|
+
if (tupleNode?.items) {
|
|
1645
|
+
const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
|
|
1646
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1647
|
+
...tupleNode,
|
|
1648
|
+
items: namedItems
|
|
1649
|
+
};
|
|
1650
|
+
}
|
|
1651
|
+
return node;
|
|
1652
|
+
})();
|
|
1515
1653
|
return ast.createProperty({
|
|
1516
1654
|
name: propName,
|
|
1517
1655
|
schema: {
|
|
@@ -1522,11 +1660,12 @@ function createSchemaParser(ctx) {
|
|
|
1522
1660
|
});
|
|
1523
1661
|
}) : [];
|
|
1524
1662
|
const additionalProperties = schema.additionalProperties;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1663
|
+
const additionalPropertiesNode = (() => {
|
|
1664
|
+
if (additionalProperties === true) return true;
|
|
1665
|
+
if (additionalProperties === false) return false;
|
|
1666
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1667
|
+
if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1668
|
+
})();
|
|
1530
1669
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1531
1670
|
const patternProperties = rawPatternProperties ? Object.fromEntries(Object.entries(rawPatternProperties).map(([pattern, patternSchema]) => [pattern, patternSchema === true || typeof patternSchema === "object" && Object.keys(patternSchema).length === 0 ? ast.createSchema({ type: typeOptionMap.get(options.unknownType) }) : parseSchema({ schema: patternSchema }, rawOptions)])) : void 0;
|
|
1532
1671
|
const objectNode = ast.createSchema({
|
|
@@ -1539,10 +1678,10 @@ function createSchemaParser(ctx) {
|
|
|
1539
1678
|
maxProperties: schema.maxProperties,
|
|
1540
1679
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1541
1680
|
});
|
|
1542
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1681
|
+
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1543
1682
|
const discPropName = schema.discriminator.propertyName;
|
|
1544
1683
|
const values = Object.keys(schema.discriminator.mapping);
|
|
1545
|
-
const enumName = name ?
|
|
1684
|
+
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
1546
1685
|
return ast.setDiscriminatorEnum({
|
|
1547
1686
|
node: objectNode,
|
|
1548
1687
|
propertyName: discPropName,
|
|
@@ -1573,7 +1712,7 @@ function createSchemaParser(ctx) {
|
|
|
1573
1712
|
*/
|
|
1574
1713
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1575
1714
|
const rawItems = schema.items;
|
|
1576
|
-
const itemName = rawItems?.enum?.length && name ?
|
|
1715
|
+
const itemName = rawItems?.enum?.length && name ? enumPropName(null, name, options.enumSuffix) : name;
|
|
1577
1716
|
const items = rawItems ? [parseSchema({
|
|
1578
1717
|
schema: rawItems,
|
|
1579
1718
|
name: itemName
|
|
@@ -1637,15 +1776,148 @@ function createSchemaParser(ctx) {
|
|
|
1637
1776
|
title: schema.title,
|
|
1638
1777
|
description: schema.description,
|
|
1639
1778
|
deprecated: schema.deprecated,
|
|
1640
|
-
nullable
|
|
1779
|
+
nullable,
|
|
1780
|
+
format: schema.format
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
1785
|
+
* into a `blob` node.
|
|
1786
|
+
*/
|
|
1787
|
+
function convertBlob({ schema, name, nullable, defaultValue }) {
|
|
1788
|
+
return ast.createSchema({
|
|
1789
|
+
type: "blob",
|
|
1790
|
+
primitive: "string",
|
|
1791
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1641
1792
|
});
|
|
1642
1793
|
}
|
|
1643
1794
|
/**
|
|
1795
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
1796
|
+
*
|
|
1797
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
|
|
1798
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
1799
|
+
*/
|
|
1800
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1801
|
+
const types = schema.type;
|
|
1802
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
1803
|
+
if (nonNullTypes.length <= 1) return null;
|
|
1804
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
1805
|
+
return ast.createSchema({
|
|
1806
|
+
type: "union",
|
|
1807
|
+
members: nonNullTypes.map((t) => parseSchema({
|
|
1808
|
+
schema: {
|
|
1809
|
+
...schema,
|
|
1810
|
+
type: t
|
|
1811
|
+
},
|
|
1812
|
+
name
|
|
1813
|
+
}, rawOptions)),
|
|
1814
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1819
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1820
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1821
|
+
* match/convert/fall-through contract.
|
|
1822
|
+
*/
|
|
1823
|
+
const schemaRules = [
|
|
1824
|
+
{
|
|
1825
|
+
name: "ref",
|
|
1826
|
+
match: ({ schema }) => dialect.isReference(schema),
|
|
1827
|
+
convert: convertRef
|
|
1828
|
+
},
|
|
1829
|
+
{
|
|
1830
|
+
name: "allOf",
|
|
1831
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
1832
|
+
convert: convertAllOf
|
|
1833
|
+
},
|
|
1834
|
+
{
|
|
1835
|
+
name: "union",
|
|
1836
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
1837
|
+
convert: convertUnion
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
name: "const",
|
|
1841
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1842
|
+
convert: convertConst
|
|
1843
|
+
},
|
|
1844
|
+
{
|
|
1845
|
+
name: "format",
|
|
1846
|
+
match: ({ schema }) => !!schema.format,
|
|
1847
|
+
convert: convertFormat
|
|
1848
|
+
},
|
|
1849
|
+
{
|
|
1850
|
+
name: "blob",
|
|
1851
|
+
match: ({ schema }) => dialect.isBinary(schema),
|
|
1852
|
+
convert: convertBlob
|
|
1853
|
+
},
|
|
1854
|
+
{
|
|
1855
|
+
name: "multi-type",
|
|
1856
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1857
|
+
convert: convertMultiType
|
|
1858
|
+
},
|
|
1859
|
+
{
|
|
1860
|
+
name: "constrained-string",
|
|
1861
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1862
|
+
convert: convertString
|
|
1863
|
+
},
|
|
1864
|
+
{
|
|
1865
|
+
name: "constrained-number",
|
|
1866
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1867
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1868
|
+
},
|
|
1869
|
+
{
|
|
1870
|
+
name: "enum",
|
|
1871
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
1872
|
+
convert: convertEnum
|
|
1873
|
+
},
|
|
1874
|
+
{
|
|
1875
|
+
name: "object",
|
|
1876
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1877
|
+
convert: convertObject
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
name: "tuple",
|
|
1881
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
1882
|
+
convert: convertTuple
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
name: "array",
|
|
1886
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1887
|
+
convert: convertArray
|
|
1888
|
+
},
|
|
1889
|
+
{
|
|
1890
|
+
name: "string",
|
|
1891
|
+
match: ({ type }) => type === "string",
|
|
1892
|
+
convert: convertString
|
|
1893
|
+
},
|
|
1894
|
+
{
|
|
1895
|
+
name: "number",
|
|
1896
|
+
match: ({ type }) => type === "number",
|
|
1897
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1898
|
+
},
|
|
1899
|
+
{
|
|
1900
|
+
name: "integer",
|
|
1901
|
+
match: ({ type }) => type === "integer",
|
|
1902
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1903
|
+
},
|
|
1904
|
+
{
|
|
1905
|
+
name: "boolean",
|
|
1906
|
+
match: ({ type }) => type === "boolean",
|
|
1907
|
+
convert: convertBoolean
|
|
1908
|
+
},
|
|
1909
|
+
{
|
|
1910
|
+
name: "null",
|
|
1911
|
+
match: ({ type }) => type === "null",
|
|
1912
|
+
convert: convertNull
|
|
1913
|
+
}
|
|
1914
|
+
];
|
|
1915
|
+
/**
|
|
1644
1916
|
* Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1645
1917
|
*
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1918
|
+
* Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
|
|
1919
|
+
* via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
|
|
1920
|
+
* `emptySchemaType`.
|
|
1649
1921
|
*/
|
|
1650
1922
|
function parseSchema({ schema, name }, rawOptions) {
|
|
1651
1923
|
const options = {
|
|
@@ -1657,75 +1929,40 @@ function createSchemaParser(ctx) {
|
|
|
1657
1929
|
schema: flattenedSchema,
|
|
1658
1930
|
name
|
|
1659
1931
|
}, rawOptions);
|
|
1660
|
-
const nullable = isNullable(schema) || void 0;
|
|
1661
|
-
const
|
|
1662
|
-
const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
|
|
1663
|
-
const ctx = {
|
|
1932
|
+
const nullable = dialect.isNullable(schema) || void 0;
|
|
1933
|
+
const schemaCtx = {
|
|
1664
1934
|
schema,
|
|
1665
1935
|
name,
|
|
1666
1936
|
nullable,
|
|
1667
|
-
defaultValue,
|
|
1668
|
-
type,
|
|
1937
|
+
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
1938
|
+
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1669
1939
|
rawOptions,
|
|
1670
1940
|
options
|
|
1671
1941
|
};
|
|
1672
|
-
|
|
1673
|
-
if (
|
|
1674
|
-
if ([...schema.oneOf ?? [], ...schema.anyOf ?? []].length) return convertUnion(ctx);
|
|
1675
|
-
if ("const" in schema && schema.const !== void 0) return convertConst(ctx);
|
|
1676
|
-
if (schema.format) {
|
|
1677
|
-
const formatResult = convertFormat(ctx);
|
|
1678
|
-
if (formatResult) return formatResult;
|
|
1679
|
-
}
|
|
1680
|
-
if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return ast.createSchema({
|
|
1681
|
-
type: "blob",
|
|
1682
|
-
primitive: "string",
|
|
1683
|
-
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1684
|
-
});
|
|
1685
|
-
if (Array.isArray(schema.type) && schema.type.length > 1) {
|
|
1686
|
-
const nonNullTypes = schema.type.filter((t) => t !== "null");
|
|
1687
|
-
const arrayNullable = schema.type.includes("null") || nullable || void 0;
|
|
1688
|
-
if (nonNullTypes.length > 1) return ast.createSchema({
|
|
1689
|
-
type: "union",
|
|
1690
|
-
members: nonNullTypes.map((t) => parseSchema({
|
|
1691
|
-
schema: {
|
|
1692
|
-
...schema,
|
|
1693
|
-
type: t
|
|
1694
|
-
},
|
|
1695
|
-
name
|
|
1696
|
-
}, rawOptions)),
|
|
1697
|
-
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1698
|
-
});
|
|
1699
|
-
}
|
|
1700
|
-
if (!type) {
|
|
1701
|
-
if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
|
|
1702
|
-
if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
|
|
1703
|
-
}
|
|
1704
|
-
if (schema.enum?.length) return convertEnum(ctx);
|
|
1705
|
-
if (type === "object" || schema.properties || schema.additionalProperties || "patternProperties" in schema) return convertObject(ctx);
|
|
1706
|
-
if ("prefixItems" in schema) return convertTuple(ctx);
|
|
1707
|
-
if (type === "array" || "items" in schema) return convertArray(ctx);
|
|
1708
|
-
if (type === "string") return convertString(ctx);
|
|
1709
|
-
if (type === "number") return convertNumeric(ctx, "number");
|
|
1710
|
-
if (type === "integer") return convertNumeric(ctx, "integer");
|
|
1711
|
-
if (type === "boolean") return convertBoolean(ctx);
|
|
1712
|
-
if (type === "null") return convertNull(ctx);
|
|
1942
|
+
const node = ast.dispatch(schemaRules, schemaCtx);
|
|
1943
|
+
if (node) return node;
|
|
1713
1944
|
const emptyType = typeOptionMap.get(options.emptySchemaType);
|
|
1714
1945
|
return ast.createSchema({
|
|
1715
1946
|
type: emptyType,
|
|
1716
1947
|
name,
|
|
1717
1948
|
title: schema.title,
|
|
1718
|
-
description: schema.description
|
|
1949
|
+
description: schema.description,
|
|
1950
|
+
format: schema.format
|
|
1719
1951
|
});
|
|
1720
1952
|
}
|
|
1721
1953
|
/**
|
|
1722
1954
|
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
1723
1955
|
*/
|
|
1724
|
-
function parseParameter(options, param) {
|
|
1956
|
+
function parseParameter(options, param, parentName) {
|
|
1725
1957
|
const required = param["required"] ?? false;
|
|
1726
|
-
const
|
|
1958
|
+
const paramName = param["name"];
|
|
1959
|
+
const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
|
|
1960
|
+
const schema = param["schema"] ? parseSchema({
|
|
1961
|
+
schema: param["schema"],
|
|
1962
|
+
name: schemaName
|
|
1963
|
+
}, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1727
1964
|
return ast.createParameter({
|
|
1728
|
-
name:
|
|
1965
|
+
name: paramName,
|
|
1729
1966
|
in: param["in"],
|
|
1730
1967
|
schema: {
|
|
1731
1968
|
...schema,
|
|
@@ -1762,27 +1999,33 @@ function createSchemaParser(ctx) {
|
|
|
1762
1999
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1763
2000
|
*/
|
|
1764
2001
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1765
|
-
if (!schema?.properties) return
|
|
2002
|
+
if (!schema?.properties) return null;
|
|
1766
2003
|
const keys = [];
|
|
1767
2004
|
for (const key in schema.properties) {
|
|
1768
2005
|
const prop = schema.properties[key];
|
|
1769
|
-
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
2006
|
+
if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
|
|
1770
2007
|
}
|
|
1771
|
-
return keys.length ? keys :
|
|
2008
|
+
return keys.length ? keys : null;
|
|
1772
2009
|
}
|
|
1773
2010
|
/**
|
|
1774
2011
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1775
2012
|
*/
|
|
1776
2013
|
function parseOperation(options, operation) {
|
|
1777
|
-
const
|
|
2014
|
+
const operationId = operation.getOperationId();
|
|
2015
|
+
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
2016
|
+
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
|
|
1778
2017
|
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
|
|
1779
2018
|
const requestBodyMeta = getRequestBodyMeta(operation);
|
|
2019
|
+
const requestBodyName = operationName ? `${operationName}Request` : void 0;
|
|
1780
2020
|
const content = allContentTypes.flatMap((ct) => {
|
|
1781
2021
|
const schema = getRequestSchema(document, operation, { contentType: ct });
|
|
1782
2022
|
if (!schema) return [];
|
|
1783
2023
|
return [{
|
|
1784
2024
|
contentType: ct,
|
|
1785
|
-
schema: ast.syncOptionality(parseSchema({
|
|
2025
|
+
schema: ast.syncOptionality(parseSchema({
|
|
2026
|
+
schema,
|
|
2027
|
+
name: requestBodyName
|
|
2028
|
+
}, options), requestBodyMeta.required),
|
|
1786
2029
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1787
2030
|
}];
|
|
1788
2031
|
});
|
|
@@ -1793,21 +2036,36 @@ function createSchemaParser(ctx) {
|
|
|
1793
2036
|
} : void 0;
|
|
1794
2037
|
const responses = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1795
2038
|
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1796
|
-
const
|
|
1797
|
-
const
|
|
1798
|
-
const
|
|
1799
|
-
|
|
2039
|
+
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
2040
|
+
const { description } = getResponseMeta(responseObj);
|
|
2041
|
+
const parseEntrySchema = (contentType) => {
|
|
2042
|
+
const raw = getResponseSchema(document, operation, statusCode, { contentType });
|
|
2043
|
+
return {
|
|
2044
|
+
schema: raw && Object.keys(raw).length > 0 ? parseSchema({
|
|
2045
|
+
schema: raw,
|
|
2046
|
+
name: responseName
|
|
2047
|
+
}, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
|
|
2048
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
2049
|
+
};
|
|
2050
|
+
};
|
|
2051
|
+
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
|
|
2052
|
+
contentType,
|
|
2053
|
+
...parseEntrySchema(contentType)
|
|
2054
|
+
}));
|
|
2055
|
+
if (content.length === 0) content.push({
|
|
2056
|
+
contentType: operation.contentType || "application/json",
|
|
2057
|
+
...parseEntrySchema(ctx.contentType)
|
|
2058
|
+
});
|
|
1800
2059
|
return ast.createResponse({
|
|
1801
2060
|
statusCode,
|
|
1802
2061
|
description,
|
|
1803
|
-
|
|
1804
|
-
mediaType,
|
|
1805
|
-
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
2062
|
+
content
|
|
1806
2063
|
});
|
|
1807
2064
|
});
|
|
1808
2065
|
const urlPath = new URLPath(operation.path);
|
|
1809
2066
|
return ast.createOperation({
|
|
1810
|
-
operationId
|
|
2067
|
+
operationId,
|
|
2068
|
+
protocol: "http",
|
|
1811
2069
|
method: operation.method.toUpperCase(),
|
|
1812
2070
|
path: urlPath.path,
|
|
1813
2071
|
tags: operation.getTags().map((tag) => tag.name),
|
|
@@ -1825,59 +2083,336 @@ function createSchemaParser(ctx) {
|
|
|
1825
2083
|
parseParameter
|
|
1826
2084
|
};
|
|
1827
2085
|
}
|
|
2086
|
+
//#endregion
|
|
2087
|
+
//#region src/discriminator.ts
|
|
2088
|
+
/**
|
|
2089
|
+
* Builds a map of child schema names → discriminator patch data by scanning the given
|
|
2090
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
2091
|
+
*
|
|
2092
|
+
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
2093
|
+
* discriminator parents) rather than on all schemas at once.
|
|
2094
|
+
*/
|
|
2095
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
2096
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
2097
|
+
for (const schema of schemas) {
|
|
2098
|
+
let unionNode = ast.narrowSchema(schema, "union");
|
|
2099
|
+
if (!unionNode) {
|
|
2100
|
+
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
2101
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
2102
|
+
const u = ast.narrowSchema(m, "union");
|
|
2103
|
+
if (u) {
|
|
2104
|
+
unionNode = u;
|
|
2105
|
+
break;
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
}
|
|
2109
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
2110
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
2111
|
+
for (const member of members) {
|
|
2112
|
+
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
2113
|
+
if (!intersectionNode?.members) continue;
|
|
2114
|
+
let refNode = null;
|
|
2115
|
+
let objNode = null;
|
|
2116
|
+
for (const m of intersectionNode.members) {
|
|
2117
|
+
refNode ??= ast.narrowSchema(m, "ref");
|
|
2118
|
+
objNode ??= ast.narrowSchema(m, "object");
|
|
2119
|
+
}
|
|
2120
|
+
if (!refNode?.name || !objNode) continue;
|
|
2121
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
2122
|
+
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
|
|
2123
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
2124
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
2125
|
+
if (!enumValues.length) continue;
|
|
2126
|
+
const existing = childMap.get(refNode.name);
|
|
2127
|
+
if (!existing) {
|
|
2128
|
+
childMap.set(refNode.name, {
|
|
2129
|
+
propertyName: discriminatorPropertyName,
|
|
2130
|
+
enumValues: [...enumValues]
|
|
2131
|
+
});
|
|
2132
|
+
continue;
|
|
2133
|
+
}
|
|
2134
|
+
existing.enumValues.push(...enumValues);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
return childMap;
|
|
2138
|
+
}
|
|
2139
|
+
/**
|
|
2140
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
2141
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
2142
|
+
* without buffering all schemas.
|
|
2143
|
+
*/
|
|
2144
|
+
function patchDiscriminatorNode(node, entry) {
|
|
2145
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
2146
|
+
if (!objectNode) return node;
|
|
2147
|
+
const { propertyName, enumValues } = entry;
|
|
2148
|
+
const enumSchema = ast.createSchema({
|
|
2149
|
+
type: "enum",
|
|
2150
|
+
enumValues
|
|
2151
|
+
});
|
|
2152
|
+
const newProp = ast.createProperty({
|
|
2153
|
+
name: propertyName,
|
|
2154
|
+
required: true,
|
|
2155
|
+
schema: enumSchema
|
|
2156
|
+
});
|
|
2157
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
2158
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
2159
|
+
return {
|
|
2160
|
+
...objectNode,
|
|
2161
|
+
properties: newProperties
|
|
2162
|
+
};
|
|
2163
|
+
}
|
|
2164
|
+
//#endregion
|
|
2165
|
+
//#region src/schemaDiagnostics.ts
|
|
2166
|
+
/**
|
|
2167
|
+
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
|
|
2168
|
+
* top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
|
|
2169
|
+
* pointer as it descends so a nested field reports against its full path
|
|
2170
|
+
* (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
|
|
2171
|
+
* resolved schema is reported under its own walk. Reports land in the active build run, are a
|
|
2172
|
+
* no-op outside one, and repeats are deduped by the build.
|
|
2173
|
+
*/
|
|
2174
|
+
function reportSchemaDiagnostics({ node, name }) {
|
|
2175
|
+
visit(node, `#/components/schemas/${escapePointerToken(name)}`);
|
|
2176
|
+
}
|
|
2177
|
+
/**
|
|
2178
|
+
* Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
|
|
2179
|
+
* property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
|
|
2180
|
+
*/
|
|
2181
|
+
function escapePointerToken(token) {
|
|
2182
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2183
|
+
}
|
|
2184
|
+
function visit(node, pointer) {
|
|
2185
|
+
if (node.deprecated) Diagnostics.report({
|
|
2186
|
+
code: Diagnostics.code.deprecated,
|
|
2187
|
+
severity: "info",
|
|
2188
|
+
message: "This schema is marked as deprecated.",
|
|
2189
|
+
location: {
|
|
2190
|
+
kind: "schema",
|
|
2191
|
+
pointer
|
|
2192
|
+
}
|
|
2193
|
+
});
|
|
2194
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
|
|
2195
|
+
code: Diagnostics.code.unsupportedFormat,
|
|
2196
|
+
severity: "warning",
|
|
2197
|
+
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
2198
|
+
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
2199
|
+
location: {
|
|
2200
|
+
kind: "schema",
|
|
2201
|
+
pointer
|
|
2202
|
+
}
|
|
2203
|
+
});
|
|
2204
|
+
if (node.type === "object") {
|
|
2205
|
+
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
2206
|
+
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (node.type === "array") {
|
|
2210
|
+
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
2211
|
+
return;
|
|
2212
|
+
}
|
|
2213
|
+
if (node.type === "tuple") {
|
|
2214
|
+
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
2215
|
+
return;
|
|
2216
|
+
}
|
|
2217
|
+
if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2218
|
+
}
|
|
2219
|
+
//#endregion
|
|
2220
|
+
//#region src/stream.ts
|
|
2221
|
+
/**
|
|
2222
|
+
* Builds the deduplication plan from the already-parsed top-level schema nodes plus a
|
|
2223
|
+
* single extra parse pass over operations (so duplicates in request/response bodies are seen).
|
|
2224
|
+
*
|
|
2225
|
+
* Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
2226
|
+
* rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
2227
|
+
* name (collision-resolved against existing component names); shapes without a name stay inline.
|
|
2228
|
+
*/
|
|
2229
|
+
function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
|
|
2230
|
+
const circularSchemas = new Set(circularNames);
|
|
2231
|
+
const usedNames = new Set(schemaNames);
|
|
2232
|
+
return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2233
|
+
isCandidate: (node) => {
|
|
2234
|
+
if (node.type === "enum") return true;
|
|
2235
|
+
if (node.type !== "object") return false;
|
|
2236
|
+
if (node.name && circularSchemas.has(node.name)) return false;
|
|
2237
|
+
return !ast.containsCircularRef(node, { circularSchemas });
|
|
2238
|
+
},
|
|
2239
|
+
nameFor: (node) => {
|
|
2240
|
+
const base = node.name;
|
|
2241
|
+
if (!base) return null;
|
|
2242
|
+
let name = base;
|
|
2243
|
+
let counter = 2;
|
|
2244
|
+
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
2245
|
+
usedNames.add(name);
|
|
2246
|
+
return name;
|
|
2247
|
+
},
|
|
2248
|
+
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
|
|
2249
|
+
});
|
|
2250
|
+
}
|
|
1828
2251
|
/**
|
|
1829
|
-
*
|
|
2252
|
+
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
2253
|
+
* interpolating any `serverVariables` into the URL template.
|
|
1830
2254
|
*
|
|
1831
|
-
*
|
|
1832
|
-
* that downstream plugins (`plugin-ts`, `plugin-zod`, etc.) consume for code generation. No code is generated here —
|
|
1833
|
-
* the tree is a pure data structure representing all schemas and operations.
|
|
2255
|
+
* Returns `null` when `serverIndex` is omitted or out of range.
|
|
1834
2256
|
*
|
|
1835
|
-
*
|
|
2257
|
+
* @example Resolve the first server
|
|
2258
|
+
* `resolveBaseUrl({ document, serverIndex: 0 })`
|
|
2259
|
+
*
|
|
2260
|
+
* @example Override a path variable
|
|
2261
|
+
* `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
|
|
2262
|
+
*/
|
|
2263
|
+
function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
2264
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
2265
|
+
return server?.url ? resolveServerUrl(server, serverVariables) : null;
|
|
2266
|
+
}
|
|
2267
|
+
/**
|
|
2268
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
2269
|
+
*
|
|
2270
|
+
* Three things happen in this single pass:
|
|
2271
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
2272
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
2273
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
2274
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
2275
|
+
*
|
|
2276
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2277
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
2278
|
+
*
|
|
2279
|
+
* Each schema is parsed again during the streaming pass. This is intentional.
|
|
2280
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1836
2281
|
*
|
|
1837
2282
|
* @example
|
|
1838
2283
|
* ```ts
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
2284
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
2285
|
+
* schemas,
|
|
2286
|
+
* parseSchema,
|
|
2287
|
+
* parserOptions,
|
|
2288
|
+
* discriminator: 'strict',
|
|
2289
|
+
* })
|
|
1843
2290
|
* ```
|
|
1844
2291
|
*/
|
|
1845
|
-
function
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2292
|
+
function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
|
|
2293
|
+
const allNodes = [];
|
|
2294
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2295
|
+
const enumNames = [];
|
|
2296
|
+
const discriminatorParentNodes = [];
|
|
2297
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2298
|
+
const node = parseSchema({
|
|
2299
|
+
schema,
|
|
2300
|
+
name
|
|
2301
|
+
}, parserOptions);
|
|
2302
|
+
allNodes.push(node);
|
|
2303
|
+
reportSchemaDiagnostics({
|
|
2304
|
+
node,
|
|
2305
|
+
name
|
|
2306
|
+
});
|
|
2307
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2308
|
+
if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2309
|
+
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2310
|
+
}
|
|
2311
|
+
const circularNames = [...ast.findCircularSchemas(allNodes)];
|
|
2312
|
+
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2313
|
+
let dedupePlan = null;
|
|
2314
|
+
if (dedupe) {
|
|
2315
|
+
const operationNodes = [];
|
|
2316
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2317
|
+
if (!operation) continue;
|
|
2318
|
+
const operationNode = parseOperation(parserOptions, operation);
|
|
2319
|
+
if (operationNode) operationNodes.push(operationNode);
|
|
2320
|
+
}
|
|
2321
|
+
dedupePlan = createDedupePlan({
|
|
2322
|
+
schemaNodes: allNodes,
|
|
2323
|
+
operationNodes,
|
|
2324
|
+
schemaNames: Object.keys(schemas),
|
|
2325
|
+
circularNames
|
|
2326
|
+
});
|
|
2327
|
+
for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
|
|
2328
|
+
}
|
|
1862
2329
|
return {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2330
|
+
refAliasMap,
|
|
2331
|
+
enumNames,
|
|
2332
|
+
circularNames,
|
|
2333
|
+
discriminatorChildMap,
|
|
2334
|
+
dedupePlan
|
|
1868
2335
|
};
|
|
1869
2336
|
}
|
|
2337
|
+
/**
|
|
2338
|
+
* Creates a lazy `InputStreamNode` from already-resolved adapter state.
|
|
2339
|
+
*
|
|
2340
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
2341
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
2342
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
2343
|
+
*
|
|
2344
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
2345
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
2346
|
+
*
|
|
2347
|
+
* @example
|
|
2348
|
+
* ```ts
|
|
2349
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2350
|
+
* for await (const schema of streamNode.schemas) {
|
|
2351
|
+
* // each call to for-await restarts from the first schema
|
|
2352
|
+
* }
|
|
2353
|
+
* ```
|
|
2354
|
+
*/
|
|
2355
|
+
function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
|
|
2356
|
+
const rewriteTopLevelSchema = (node) => {
|
|
2357
|
+
if (!dedupePlan) return node;
|
|
2358
|
+
const canonical = dedupePlan.canonicalBySignature.get(ast.schemaSignature(node));
|
|
2359
|
+
if (canonical && canonical.name !== node.name) return ast.createSchema({
|
|
2360
|
+
type: "ref",
|
|
2361
|
+
name: node.name ?? null,
|
|
2362
|
+
ref: canonical.ref,
|
|
2363
|
+
description: node.description,
|
|
2364
|
+
deprecated: node.deprecated
|
|
2365
|
+
});
|
|
2366
|
+
return ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
|
|
2367
|
+
};
|
|
2368
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2369
|
+
return (async function* () {
|
|
2370
|
+
if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
|
|
2371
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2372
|
+
const alias = refAliasMap.get(name);
|
|
2373
|
+
if (alias?.name && schemas[alias.name]) {
|
|
2374
|
+
yield rewriteTopLevelSchema({
|
|
2375
|
+
...parseSchema({
|
|
2376
|
+
schema: schemas[alias.name],
|
|
2377
|
+
name: alias.name
|
|
2378
|
+
}, parserOptions),
|
|
2379
|
+
name
|
|
2380
|
+
});
|
|
2381
|
+
continue;
|
|
2382
|
+
}
|
|
2383
|
+
const parsed = parseSchema({
|
|
2384
|
+
schema,
|
|
2385
|
+
name
|
|
2386
|
+
}, parserOptions);
|
|
2387
|
+
yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
|
|
2388
|
+
}
|
|
2389
|
+
})();
|
|
2390
|
+
} };
|
|
2391
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2392
|
+
return (async function* () {
|
|
2393
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2394
|
+
if (!operation) continue;
|
|
2395
|
+
const node = parseOperation(parserOptions, operation);
|
|
2396
|
+
if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
|
|
2397
|
+
}
|
|
2398
|
+
})();
|
|
2399
|
+
} };
|
|
2400
|
+
return ast.createStreamInput(schemasIterable, operationsIterable, meta);
|
|
2401
|
+
}
|
|
1870
2402
|
//#endregion
|
|
1871
2403
|
//#region src/adapter.ts
|
|
1872
2404
|
/**
|
|
1873
|
-
*
|
|
2405
|
+
* Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
|
|
1874
2406
|
*/
|
|
1875
2407
|
const adapterOasName = "oas";
|
|
1876
2408
|
/**
|
|
1877
|
-
*
|
|
2409
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
|
|
2410
|
+
* file at `input.path`, validates it, resolves the base URL, and converts every
|
|
2411
|
+
* schema and operation into the universal AST that every downstream plugin
|
|
2412
|
+
* consumes.
|
|
1878
2413
|
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
2414
|
+
* Configure once on `defineConfig`. The adapter's choices (date representation,
|
|
2415
|
+
* integer width, server URL) apply to every plugin in the build.
|
|
1881
2416
|
*
|
|
1882
2417
|
* @example
|
|
1883
2418
|
* ```ts
|
|
@@ -1886,17 +2421,118 @@ const adapterOasName = "oas";
|
|
|
1886
2421
|
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1887
2422
|
*
|
|
1888
2423
|
* export default defineConfig({
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
2424
|
+
* input: { path: './petStore.yaml' },
|
|
2425
|
+
* output: { path: './src/gen' },
|
|
2426
|
+
* adapter: adapterOas({
|
|
2427
|
+
* serverIndex: 0,
|
|
2428
|
+
* discriminator: 'inherit',
|
|
2429
|
+
* dateType: 'date',
|
|
2430
|
+
* }),
|
|
1891
2431
|
* plugins: [pluginTs()],
|
|
1892
2432
|
* })
|
|
1893
2433
|
* ```
|
|
1894
2434
|
*/
|
|
1895
2435
|
const adapterOas = createAdapter((options) => {
|
|
1896
|
-
const { validate = true, contentType, serverIndex, serverVariables, discriminator = "strict", dateType = DEFAULT_PARSER_OPTIONS.dateType, integerType = DEFAULT_PARSER_OPTIONS.integerType, unknownType = DEFAULT_PARSER_OPTIONS.unknownType, enumSuffix = DEFAULT_PARSER_OPTIONS.enumSuffix, emptySchemaType = unknownType || DEFAULT_PARSER_OPTIONS.emptySchemaType } = options;
|
|
2436
|
+
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;
|
|
2437
|
+
const parserOptions = {
|
|
2438
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2439
|
+
dateType,
|
|
2440
|
+
integerType,
|
|
2441
|
+
unknownType,
|
|
2442
|
+
emptySchemaType,
|
|
2443
|
+
enumSuffix
|
|
2444
|
+
};
|
|
1897
2445
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1898
|
-
let parsedDocument;
|
|
1899
|
-
|
|
2446
|
+
let parsedDocument = null;
|
|
2447
|
+
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2448
|
+
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2449
|
+
const baseOasCache = /* @__PURE__ */ new WeakMap();
|
|
2450
|
+
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2451
|
+
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2452
|
+
function ensureDocument(source) {
|
|
2453
|
+
const cached = documentCache.get(source);
|
|
2454
|
+
if (cached) return cached;
|
|
2455
|
+
const promise = (async () => {
|
|
2456
|
+
const fresh = await parseFromConfig(source);
|
|
2457
|
+
if (validate) await validateDocument(fresh);
|
|
2458
|
+
parsedDocument = fresh;
|
|
2459
|
+
return fresh;
|
|
2460
|
+
})();
|
|
2461
|
+
documentCache.set(source, promise);
|
|
2462
|
+
return promise;
|
|
2463
|
+
}
|
|
2464
|
+
function ensureSchemas(document) {
|
|
2465
|
+
const cached = schemasCache.get(document);
|
|
2466
|
+
if (cached) return cached;
|
|
2467
|
+
const promise = Promise.resolve().then(() => {
|
|
2468
|
+
const result = getSchemas(document, { contentType });
|
|
2469
|
+
nameMapping = result.nameMapping;
|
|
2470
|
+
return result.schemas;
|
|
2471
|
+
});
|
|
2472
|
+
schemasCache.set(document, promise);
|
|
2473
|
+
return promise;
|
|
2474
|
+
}
|
|
2475
|
+
function ensureBaseOas(document) {
|
|
2476
|
+
const cached = baseOasCache.get(document);
|
|
2477
|
+
if (cached) return cached;
|
|
2478
|
+
const baseOas = new BaseOas(document);
|
|
2479
|
+
baseOasCache.set(document, baseOas);
|
|
2480
|
+
return baseOas;
|
|
2481
|
+
}
|
|
2482
|
+
function ensureSchemaParser(document) {
|
|
2483
|
+
const cached = schemaParserCache.get(document);
|
|
2484
|
+
if (cached) return cached;
|
|
2485
|
+
const parser = createSchemaParser({
|
|
2486
|
+
document,
|
|
2487
|
+
contentType
|
|
2488
|
+
});
|
|
2489
|
+
schemaParserCache.set(document, parser);
|
|
2490
|
+
return parser;
|
|
2491
|
+
}
|
|
2492
|
+
function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
|
|
2493
|
+
const cached = preScanCache.get(document);
|
|
2494
|
+
if (cached) return cached;
|
|
2495
|
+
const result = preScan({
|
|
2496
|
+
schemas,
|
|
2497
|
+
parseSchema,
|
|
2498
|
+
parseOperation,
|
|
2499
|
+
baseOas,
|
|
2500
|
+
parserOptions,
|
|
2501
|
+
discriminator,
|
|
2502
|
+
dedupe
|
|
2503
|
+
});
|
|
2504
|
+
preScanCache.set(document, result);
|
|
2505
|
+
return result;
|
|
2506
|
+
}
|
|
2507
|
+
async function createStream(source) {
|
|
2508
|
+
const document = await ensureDocument(source);
|
|
2509
|
+
const schemas = await ensureSchemas(document);
|
|
2510
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2511
|
+
const baseOas = ensureBaseOas(document);
|
|
2512
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
|
|
2513
|
+
return createInputStream({
|
|
2514
|
+
schemas,
|
|
2515
|
+
parseSchema,
|
|
2516
|
+
parseOperation,
|
|
2517
|
+
baseOas,
|
|
2518
|
+
parserOptions,
|
|
2519
|
+
refAliasMap,
|
|
2520
|
+
discriminatorChildMap,
|
|
2521
|
+
dedupePlan,
|
|
2522
|
+
meta: {
|
|
2523
|
+
title: document.info?.title,
|
|
2524
|
+
description: document.info?.description,
|
|
2525
|
+
version: document.info?.version,
|
|
2526
|
+
baseURL: resolveBaseUrl({
|
|
2527
|
+
document,
|
|
2528
|
+
serverIndex,
|
|
2529
|
+
serverVariables
|
|
2530
|
+
}),
|
|
2531
|
+
circularNames,
|
|
2532
|
+
enumNames
|
|
2533
|
+
}
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
1900
2536
|
return {
|
|
1901
2537
|
name: "oas",
|
|
1902
2538
|
get options() {
|
|
@@ -1906,6 +2542,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
1906
2542
|
serverIndex,
|
|
1907
2543
|
serverVariables,
|
|
1908
2544
|
discriminator,
|
|
2545
|
+
dedupe,
|
|
1909
2546
|
dateType,
|
|
1910
2547
|
integerType,
|
|
1911
2548
|
unknownType,
|
|
@@ -1917,8 +2554,9 @@ const adapterOas = createAdapter((options) => {
|
|
|
1917
2554
|
get document() {
|
|
1918
2555
|
return parsedDocument;
|
|
1919
2556
|
},
|
|
1920
|
-
|
|
1921
|
-
|
|
2557
|
+
async validate(input, options) {
|
|
2558
|
+
await assertInputExists(input);
|
|
2559
|
+
await validateDocument(await parseDocument(input), options);
|
|
1922
2560
|
},
|
|
1923
2561
|
getImports(node, resolve) {
|
|
1924
2562
|
return ast.collectImports({
|
|
@@ -1926,7 +2564,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
1926
2564
|
nameMapping,
|
|
1927
2565
|
resolve: (schemaName) => {
|
|
1928
2566
|
const result = resolve(schemaName);
|
|
1929
|
-
if (!result) return;
|
|
2567
|
+
if (!result) return null;
|
|
1930
2568
|
return ast.createImport({
|
|
1931
2569
|
name: [result.name],
|
|
1932
2570
|
path: result.path
|
|
@@ -1935,47 +2573,18 @@ const adapterOas = createAdapter((options) => {
|
|
|
1935
2573
|
});
|
|
1936
2574
|
},
|
|
1937
2575
|
async parse(source) {
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
dateType,
|
|
1945
|
-
integerType,
|
|
1946
|
-
unknownType,
|
|
1947
|
-
emptySchemaType,
|
|
1948
|
-
enumSuffix
|
|
2576
|
+
const streamNode = await createStream(source);
|
|
2577
|
+
const [schemas, operations] = await Promise.all([Array.fromAsync(streamNode.schemas), Array.fromAsync(streamNode.operations)]);
|
|
2578
|
+
return ast.createInput({
|
|
2579
|
+
schemas,
|
|
2580
|
+
operations,
|
|
2581
|
+
meta: streamNode.meta
|
|
1949
2582
|
});
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
parsedDocument = document;
|
|
1953
|
-
inputNode = ast.createInput({
|
|
1954
|
-
...node,
|
|
1955
|
-
meta: {
|
|
1956
|
-
title: document.info?.title,
|
|
1957
|
-
description: document.info?.description,
|
|
1958
|
-
version: document.info?.version,
|
|
1959
|
-
baseURL
|
|
1960
|
-
}
|
|
1961
|
-
});
|
|
1962
|
-
return inputNode;
|
|
1963
|
-
}
|
|
2583
|
+
},
|
|
2584
|
+
stream: createStream
|
|
1964
2585
|
};
|
|
1965
2586
|
});
|
|
1966
2587
|
//#endregion
|
|
1967
|
-
|
|
1968
|
-
/**
|
|
1969
|
-
* Maps uppercase HTTP method names to lowercase for backwards compatibility.
|
|
1970
|
-
*
|
|
1971
|
-
* @example
|
|
1972
|
-
* ```ts
|
|
1973
|
-
* HttpMethods['GET'] // 'get'
|
|
1974
|
-
* HttpMethods['POST'] // 'post'
|
|
1975
|
-
* ```
|
|
1976
|
-
*/
|
|
1977
|
-
const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
|
|
1978
|
-
//#endregion
|
|
1979
|
-
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments, parseDocument, parseFromConfig, validateDocument };
|
|
2588
|
+
export { adapterOas, adapterOasName };
|
|
1980
2589
|
|
|
1981
2590
|
//# sourceMappingURL=index.js.map
|