@kubb/adapter-oas 5.0.0-beta.4 → 5.0.0-beta.40
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/README.md +100 -0
- package/dist/index.cjs +900 -311
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +52 -69
- package/dist/index.js +902 -310
- package/dist/index.js.map +1 -1
- package/extension.yaml +144 -57
- package/package.json +6 -4
- package/src/adapter.bench.ts +64 -0
- package/src/adapter.ts +143 -48
- package/src/constants.ts +8 -0
- package/src/dialect.ts +38 -0
- package/src/discriminator.ts +31 -47
- package/src/factory.ts +38 -12
- package/src/index.ts +2 -2
- package/src/parser.ts +233 -137
- 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,11 @@
|
|
|
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";
|
|
5
|
+
import { access } from "node:fs/promises";
|
|
4
6
|
import { bundle, loadConfig } from "@redocly/openapi-core";
|
|
5
7
|
import OASNormalize from "oas-normalize";
|
|
6
8
|
import swagger2openapi from "swagger2openapi";
|
|
7
|
-
import BaseOas from "oas";
|
|
8
9
|
import { isRef } from "oas/types";
|
|
9
10
|
import { matchesMimeType } from "oas/utils";
|
|
10
11
|
//#region src/constants.ts
|
|
@@ -89,6 +90,18 @@ const structuralKeys = new Set([
|
|
|
89
90
|
* formatMap['float'] // 'number'
|
|
90
91
|
* ```
|
|
91
92
|
*/
|
|
93
|
+
/**
|
|
94
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
95
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
96
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
97
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
98
|
+
*/
|
|
99
|
+
const specialCasedFormats = new Set([
|
|
100
|
+
"int64",
|
|
101
|
+
"date-time",
|
|
102
|
+
"date",
|
|
103
|
+
"time"
|
|
104
|
+
]);
|
|
92
105
|
const formatMap = {
|
|
93
106
|
uuid: "uuid",
|
|
94
107
|
email: "email",
|
|
@@ -127,87 +140,6 @@ const typeOptionMap = new Map([
|
|
|
127
140
|
["void", ast.schemaTypes.void]
|
|
128
141
|
]);
|
|
129
142
|
//#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
143
|
//#region ../../internals/utils/src/casing.ts
|
|
212
144
|
/**
|
|
213
145
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -272,6 +204,23 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
272
204
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
273
205
|
}
|
|
274
206
|
//#endregion
|
|
207
|
+
//#region ../../internals/utils/src/fs.ts
|
|
208
|
+
/**
|
|
209
|
+
* Resolves to `true` when the file or directory at `path` exists.
|
|
210
|
+
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
211
|
+
*
|
|
212
|
+
* @example
|
|
213
|
+
* ```ts
|
|
214
|
+
* if (await exists('./kubb.config.ts')) {
|
|
215
|
+
* const content = await read('./kubb.config.ts')
|
|
216
|
+
* }
|
|
217
|
+
* ```
|
|
218
|
+
*/
|
|
219
|
+
async function exists(path) {
|
|
220
|
+
if (typeof Bun !== "undefined") return Bun.file(path).exists();
|
|
221
|
+
return access(path).then(() => true, () => false);
|
|
222
|
+
}
|
|
223
|
+
//#endregion
|
|
275
224
|
//#region ../../internals/utils/src/object.ts
|
|
276
225
|
/**
|
|
277
226
|
* Returns `true` when `value` is a plain (non-null, non-array) object.
|
|
@@ -517,12 +466,13 @@ var URLPath = class {
|
|
|
517
466
|
* @example
|
|
518
467
|
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
519
468
|
*/
|
|
520
|
-
toTemplateString({ prefix
|
|
521
|
-
|
|
469
|
+
toTemplateString({ prefix, replacer } = {}) {
|
|
470
|
+
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
522
471
|
if (i % 2 === 0) return part;
|
|
523
472
|
const param = this.#transformParam(part);
|
|
524
473
|
return `\${${replacer ? replacer(param) : param}}`;
|
|
525
|
-
}).join("")
|
|
474
|
+
}).join("");
|
|
475
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
526
476
|
}
|
|
527
477
|
/**
|
|
528
478
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
@@ -660,12 +610,17 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
660
610
|
* ```
|
|
661
611
|
*/
|
|
662
612
|
async function mergeDocuments(pathOrApi) {
|
|
663
|
-
const documents =
|
|
664
|
-
for (const p of pathOrApi) documents.push(await parseDocument(p, {
|
|
613
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
665
614
|
enablePaths: false,
|
|
666
615
|
canBundle: false
|
|
667
|
-
}));
|
|
668
|
-
if (documents.length === 0) throw new Error(
|
|
616
|
+
})));
|
|
617
|
+
if (documents.length === 0) throw new Diagnostics.Error({
|
|
618
|
+
code: Diagnostics.code.inputRequired,
|
|
619
|
+
severity: "error",
|
|
620
|
+
message: "No OAS documents were provided for merging.",
|
|
621
|
+
help: "Pass at least one path or document to `input.path`.",
|
|
622
|
+
location: { kind: "config" }
|
|
623
|
+
});
|
|
669
624
|
const seed = {
|
|
670
625
|
openapi: MERGE_OPENAPI_VERSION,
|
|
671
626
|
info: {
|
|
@@ -681,9 +636,9 @@ async function mergeDocuments(pathOrApi) {
|
|
|
681
636
|
* Creates a `Document` from an `AdapterSource`.
|
|
682
637
|
*
|
|
683
638
|
* Handles all three source types:
|
|
684
|
-
* - `{ type: 'path' }`
|
|
685
|
-
* - `{ type: 'paths' }`
|
|
686
|
-
* - `{ type: 'data' }`
|
|
639
|
+
* - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
|
|
640
|
+
* - `{ type: 'paths' }` merges multiple file paths into a single document.
|
|
641
|
+
* - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
|
|
687
642
|
*
|
|
688
643
|
* @example
|
|
689
644
|
* ```ts
|
|
@@ -691,14 +646,31 @@ async function mergeDocuments(pathOrApi) {
|
|
|
691
646
|
* const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
|
|
692
647
|
* ```
|
|
693
648
|
*/
|
|
694
|
-
function parseFromConfig(source) {
|
|
649
|
+
async function parseFromConfig(source) {
|
|
695
650
|
if (source.type === "data") {
|
|
696
651
|
if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
|
|
697
652
|
return parseDocument(source.data, { canBundle: false });
|
|
698
653
|
}
|
|
699
654
|
if (source.type === "paths") return mergeDocuments(source.paths);
|
|
700
655
|
if (new URLPath(source.path).isURL) return parseDocument(source.path);
|
|
701
|
-
|
|
656
|
+
const resolved = path.resolve(path.dirname(source.path), source.path);
|
|
657
|
+
await assertInputExists(resolved);
|
|
658
|
+
return parseDocument(resolved);
|
|
659
|
+
}
|
|
660
|
+
/**
|
|
661
|
+
* Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
|
|
662
|
+
* URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
|
|
663
|
+
* its parse error instead.
|
|
664
|
+
*/
|
|
665
|
+
async function assertInputExists(input) {
|
|
666
|
+
if (new URLPath(input).isURL) return;
|
|
667
|
+
if (!await exists(input)) throw new Diagnostics.Error({
|
|
668
|
+
code: Diagnostics.code.inputNotFound,
|
|
669
|
+
severity: "error",
|
|
670
|
+
message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
|
|
671
|
+
help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
|
|
672
|
+
location: { kind: "config" }
|
|
673
|
+
});
|
|
702
674
|
}
|
|
703
675
|
/**
|
|
704
676
|
* Validates an OpenAPI document using `oas-normalize` with colorized error output.
|
|
@@ -720,6 +692,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
720
692
|
}
|
|
721
693
|
//#endregion
|
|
722
694
|
//#region src/refs.ts
|
|
695
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
723
696
|
/**
|
|
724
697
|
* Resolves a local JSON pointer reference from a document.
|
|
725
698
|
*
|
|
@@ -735,10 +708,31 @@ function resolveRef(document, $ref) {
|
|
|
735
708
|
const origRef = $ref;
|
|
736
709
|
$ref = $ref.trim();
|
|
737
710
|
if ($ref === "") return null;
|
|
738
|
-
if (
|
|
739
|
-
|
|
711
|
+
if (!$ref.startsWith("#")) return null;
|
|
712
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
713
|
+
let docCache = _refCache.get(document);
|
|
714
|
+
if (!docCache) {
|
|
715
|
+
docCache = /* @__PURE__ */ new Map();
|
|
716
|
+
_refCache.set(document, docCache);
|
|
717
|
+
}
|
|
718
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
740
719
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
741
|
-
if (!current)
|
|
720
|
+
if (!current) {
|
|
721
|
+
const diagnostic = {
|
|
722
|
+
code: Diagnostics.code.refNotFound,
|
|
723
|
+
severity: "error",
|
|
724
|
+
message: `Could not find a definition for ${origRef}.`,
|
|
725
|
+
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
726
|
+
location: {
|
|
727
|
+
kind: "schema",
|
|
728
|
+
pointer: origRef,
|
|
729
|
+
ref: origRef
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
if (!Diagnostics.report(diagnostic)) throw new Diagnostics.Error(diagnostic);
|
|
733
|
+
return null;
|
|
734
|
+
}
|
|
735
|
+
docCache.set($ref, current);
|
|
742
736
|
return current;
|
|
743
737
|
}
|
|
744
738
|
/**
|
|
@@ -762,6 +756,35 @@ function dereferenceWithRef(document, schema) {
|
|
|
762
756
|
return schema;
|
|
763
757
|
}
|
|
764
758
|
//#endregion
|
|
759
|
+
//#region src/dialect.ts
|
|
760
|
+
/**
|
|
761
|
+
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
762
|
+
*
|
|
763
|
+
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
764
|
+
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
765
|
+
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
766
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
767
|
+
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
768
|
+
* the rest unchanged.
|
|
769
|
+
*
|
|
770
|
+
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
771
|
+
* JSON Schema vocabulary, so the converters keep that common case.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```ts
|
|
775
|
+
* const parser = createSchemaParser(context) // uses oasDialect
|
|
776
|
+
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
777
|
+
* ```
|
|
778
|
+
*/
|
|
779
|
+
const oasDialect = ast.defineSchemaDialect({
|
|
780
|
+
name: "oas",
|
|
781
|
+
isNullable,
|
|
782
|
+
isReference,
|
|
783
|
+
isDiscriminator,
|
|
784
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
785
|
+
resolveRef
|
|
786
|
+
});
|
|
787
|
+
//#endregion
|
|
765
788
|
//#region src/resolvers.ts
|
|
766
789
|
/**
|
|
767
790
|
* Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
|
|
@@ -783,7 +806,16 @@ function resolveServerUrl(server, overrides) {
|
|
|
783
806
|
for (const [key, variable] of Object.entries(server.variables)) {
|
|
784
807
|
const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
|
|
785
808
|
if (value === void 0) continue;
|
|
786
|
-
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Error(
|
|
809
|
+
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new Diagnostics.Error({
|
|
810
|
+
code: Diagnostics.code.invalidServerVariable,
|
|
811
|
+
severity: "error",
|
|
812
|
+
message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
|
|
813
|
+
help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
|
|
814
|
+
location: {
|
|
815
|
+
kind: "document",
|
|
816
|
+
pointer: "#/servers"
|
|
817
|
+
}
|
|
818
|
+
});
|
|
787
819
|
url = url.replaceAll(`{${key}}`, value);
|
|
788
820
|
}
|
|
789
821
|
return url;
|
|
@@ -796,6 +828,15 @@ function getSchemaType(format) {
|
|
|
796
828
|
return formatMap[format] ?? null;
|
|
797
829
|
}
|
|
798
830
|
/**
|
|
831
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
|
|
832
|
+
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
|
|
833
|
+
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
834
|
+
* diagnostic in step with the parser as `formatMap` grows.
|
|
835
|
+
*/
|
|
836
|
+
function isHandledFormat(format) {
|
|
837
|
+
return getSchemaType(format) !== null || specialCasedFormats.has(format);
|
|
838
|
+
}
|
|
839
|
+
/**
|
|
799
840
|
* Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
800
841
|
* Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
|
|
801
842
|
*/
|
|
@@ -805,12 +846,6 @@ function getPrimitiveType(type) {
|
|
|
805
846
|
return "string";
|
|
806
847
|
}
|
|
807
848
|
/**
|
|
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
849
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
815
850
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
816
851
|
* `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
|
|
@@ -898,7 +933,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
898
933
|
/**
|
|
899
934
|
* Flattens a keyword-only `allOf` into its parent schema.
|
|
900
935
|
*
|
|
901
|
-
* Only flattens when every member is a plain fragment
|
|
936
|
+
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
902
937
|
* (see `structuralKeys`). Outer schema values take precedence over fragment values.
|
|
903
938
|
* Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
|
|
904
939
|
*
|
|
@@ -908,7 +943,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
908
943
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
909
944
|
*
|
|
910
945
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
911
|
-
* // returned unchanged
|
|
946
|
+
* // returned unchanged, contains a $ref
|
|
912
947
|
* ```
|
|
913
948
|
*/
|
|
914
949
|
/**
|
|
@@ -934,7 +969,7 @@ function flattenSchema(schema) {
|
|
|
934
969
|
/**
|
|
935
970
|
* Extracts the inline schema from a media-type `content` map.
|
|
936
971
|
*
|
|
937
|
-
* Prefers `preferredContentType` when given
|
|
972
|
+
* Prefers `preferredContentType` when given, otherwise uses the first key in the map.
|
|
938
973
|
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
939
974
|
*
|
|
940
975
|
* @example
|
|
@@ -953,21 +988,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
953
988
|
/**
|
|
954
989
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
955
990
|
*/
|
|
956
|
-
function collectRefs(schema
|
|
991
|
+
function* collectRefs(schema) {
|
|
957
992
|
if (Array.isArray(schema)) {
|
|
958
|
-
for (const item of schema) collectRefs(item
|
|
959
|
-
return
|
|
993
|
+
for (const item of schema) yield* collectRefs(item);
|
|
994
|
+
return;
|
|
960
995
|
}
|
|
961
996
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
962
997
|
const value = schema[key];
|
|
963
|
-
if (key === "$ref" && typeof value === "string") {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
998
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
999
|
+
yield* collectRefs(value);
|
|
1000
|
+
continue;
|
|
1001
|
+
}
|
|
1002
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
1003
|
+
const name = value.slice(21);
|
|
1004
|
+
if (name) yield name;
|
|
1005
|
+
}
|
|
969
1006
|
}
|
|
970
|
-
return refs;
|
|
971
1007
|
}
|
|
972
1008
|
/**
|
|
973
1009
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -983,7 +1019,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
983
1019
|
*/
|
|
984
1020
|
function sortSchemas(schemas) {
|
|
985
1021
|
const deps = /* @__PURE__ */ new Map();
|
|
986
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
1022
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
987
1023
|
const sorted = [];
|
|
988
1024
|
const visited = /* @__PURE__ */ new Set();
|
|
989
1025
|
function visit(name, stack) {
|
|
@@ -1118,15 +1154,16 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1118
1154
|
readOnly: schema.readOnly,
|
|
1119
1155
|
writeOnly: schema.writeOnly,
|
|
1120
1156
|
default: defaultValue,
|
|
1121
|
-
example: schema.example
|
|
1157
|
+
example: schema.example,
|
|
1158
|
+
format: schema.format
|
|
1122
1159
|
};
|
|
1123
1160
|
}
|
|
1124
1161
|
/**
|
|
1125
1162
|
* Returns all request body content type keys for an operation.
|
|
1126
1163
|
*
|
|
1127
|
-
* The requestBody is dereferenced
|
|
1128
|
-
*
|
|
1129
|
-
*
|
|
1164
|
+
* The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
|
|
1165
|
+
* `getRequestSchema` already performs), so the returned list accurately reflects the
|
|
1166
|
+
* available content types even for referenced bodies.
|
|
1130
1167
|
*
|
|
1131
1168
|
* @example
|
|
1132
1169
|
* ```ts
|
|
@@ -1140,6 +1177,31 @@ function getRequestBodyContentTypes(document, operation) {
|
|
|
1140
1177
|
if (!body) return [];
|
|
1141
1178
|
return body.content ? Object.keys(body.content) : [];
|
|
1142
1179
|
}
|
|
1180
|
+
/**
|
|
1181
|
+
* Returns all response content type keys for an operation at a given status code.
|
|
1182
|
+
*
|
|
1183
|
+
* Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
|
|
1184
|
+
* so the returned list reflects the available content types even for referenced responses.
|
|
1185
|
+
*
|
|
1186
|
+
* @example
|
|
1187
|
+
* ```ts
|
|
1188
|
+
* getResponseBodyContentTypes(document, operation, 200)
|
|
1189
|
+
* // ['application/json', 'application/xml']
|
|
1190
|
+
* ```
|
|
1191
|
+
*/
|
|
1192
|
+
function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
1193
|
+
if (operation.schema.responses) {
|
|
1194
|
+
const responses = operation.schema.responses;
|
|
1195
|
+
for (const key in responses) {
|
|
1196
|
+
const schema = responses[key];
|
|
1197
|
+
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1201
|
+
if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
|
|
1202
|
+
const body = responseObj;
|
|
1203
|
+
return body.content ? Object.keys(body.content) : [];
|
|
1204
|
+
}
|
|
1143
1205
|
//#endregion
|
|
1144
1206
|
//#region src/parser.ts
|
|
1145
1207
|
/**
|
|
@@ -1168,9 +1230,9 @@ function normalizeArrayEnum(schema) {
|
|
|
1168
1230
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1169
1231
|
* made possible by hoisting of function declarations.
|
|
1170
1232
|
*
|
|
1171
|
-
* @
|
|
1233
|
+
* @internal
|
|
1172
1234
|
*/
|
|
1173
|
-
function createSchemaParser(ctx) {
|
|
1235
|
+
function createSchemaParser(ctx, dialect = oasDialect) {
|
|
1174
1236
|
const document = ctx.document;
|
|
1175
1237
|
/**
|
|
1176
1238
|
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
@@ -1178,6 +1240,15 @@ function createSchemaParser(ctx) {
|
|
|
1178
1240
|
*/
|
|
1179
1241
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1180
1242
|
/**
|
|
1243
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1244
|
+
*
|
|
1245
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1246
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1247
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1248
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1249
|
+
*/
|
|
1250
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1251
|
+
/**
|
|
1181
1252
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1182
1253
|
*
|
|
1183
1254
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1186,16 +1257,22 @@ function createSchemaParser(ctx) {
|
|
|
1186
1257
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1187
1258
|
*/
|
|
1188
1259
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1189
|
-
let resolvedSchema;
|
|
1260
|
+
let resolvedSchema = null;
|
|
1190
1261
|
const refPath = schema.$ref;
|
|
1191
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1262
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1263
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1264
|
+
try {
|
|
1265
|
+
const referenced = dialect.resolveRef(document, refPath);
|
|
1266
|
+
if (referenced) {
|
|
1267
|
+
resolvingRefs.add(refPath);
|
|
1268
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1269
|
+
resolvingRefs.delete(refPath);
|
|
1270
|
+
}
|
|
1271
|
+
} catch {}
|
|
1272
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1197
1273
|
}
|
|
1198
|
-
|
|
1274
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1275
|
+
}
|
|
1199
1276
|
return ast.createSchema({
|
|
1200
1277
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1201
1278
|
type: "ref",
|
|
@@ -1212,7 +1289,7 @@ function createSchemaParser(ctx) {
|
|
|
1212
1289
|
const [memberSchema] = schema.allOf;
|
|
1213
1290
|
const memberNode = parseSchema({
|
|
1214
1291
|
schema: memberSchema,
|
|
1215
|
-
name
|
|
1292
|
+
name
|
|
1216
1293
|
}, rawOptions);
|
|
1217
1294
|
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1218
1295
|
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
@@ -1228,18 +1305,19 @@ function createSchemaParser(ctx) {
|
|
|
1228
1305
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1229
1306
|
default: mergedDefault,
|
|
1230
1307
|
example: schema.example ?? memberNode.example,
|
|
1231
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1308
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1309
|
+
format: schema.format ?? memberNode.format
|
|
1232
1310
|
});
|
|
1233
1311
|
}
|
|
1234
1312
|
const filteredDiscriminantValues = [];
|
|
1235
1313
|
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;
|
|
1314
|
+
if (!dialect.isReference(item) || !name) return true;
|
|
1315
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1316
|
+
if (!deref || !dialect.isDiscriminator(deref)) return true;
|
|
1239
1317
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1240
1318
|
if (!parentUnion) return true;
|
|
1241
1319
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1242
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1320
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1243
1321
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1244
1322
|
if (inOneOf || inMapping) {
|
|
1245
1323
|
const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef);
|
|
@@ -1250,22 +1328,28 @@ function createSchemaParser(ctx) {
|
|
|
1250
1328
|
return false;
|
|
1251
1329
|
}
|
|
1252
1330
|
return true;
|
|
1253
|
-
}).map((s) => parseSchema({
|
|
1331
|
+
}).map((s) => parseSchema({
|
|
1332
|
+
schema: s,
|
|
1333
|
+
name
|
|
1334
|
+
}, rawOptions));
|
|
1254
1335
|
const syntheticStart = allOfMembers.length;
|
|
1255
1336
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1256
1337
|
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1257
1338
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1258
1339
|
if (missingRequired.length) {
|
|
1259
1340
|
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] : [];
|
|
1341
|
+
if (!dialect.isReference(item)) return [item];
|
|
1342
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1343
|
+
return deref && !dialect.isReference(deref) ? [deref] : [];
|
|
1263
1344
|
});
|
|
1264
1345
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1265
|
-
allOfMembers.push(parseSchema({
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1346
|
+
allOfMembers.push(parseSchema({
|
|
1347
|
+
schema: {
|
|
1348
|
+
properties: { [key]: resolved.properties[key] },
|
|
1349
|
+
required: [key]
|
|
1350
|
+
},
|
|
1351
|
+
name
|
|
1352
|
+
}, rawOptions));
|
|
1269
1353
|
break;
|
|
1270
1354
|
}
|
|
1271
1355
|
}
|
|
@@ -1280,7 +1364,7 @@ function createSchemaParser(ctx) {
|
|
|
1280
1364
|
}));
|
|
1281
1365
|
return ast.createSchema({
|
|
1282
1366
|
type: "intersection",
|
|
1283
|
-
members: [...ast.
|
|
1367
|
+
members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1284
1368
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1285
1369
|
});
|
|
1286
1370
|
}
|
|
@@ -1301,10 +1385,10 @@ function createSchemaParser(ctx) {
|
|
|
1301
1385
|
const strategy = schema.oneOf ? "one" : "any";
|
|
1302
1386
|
const unionBase = {
|
|
1303
1387
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1304
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1388
|
+
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1305
1389
|
strategy
|
|
1306
1390
|
};
|
|
1307
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1391
|
+
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1308
1392
|
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1309
1393
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1310
1394
|
return parseSchema({
|
|
@@ -1314,9 +1398,12 @@ function createSchemaParser(ctx) {
|
|
|
1314
1398
|
})() : void 0;
|
|
1315
1399
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1316
1400
|
const members = unionMembers.map((s) => {
|
|
1317
|
-
const ref = isReference(s) ? s.$ref : void 0;
|
|
1401
|
+
const ref = dialect.isReference(s) ? s.$ref : void 0;
|
|
1318
1402
|
const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref);
|
|
1319
|
-
const memberNode = parseSchema({
|
|
1403
|
+
const memberNode = parseSchema({
|
|
1404
|
+
schema: s,
|
|
1405
|
+
name
|
|
1406
|
+
}, rawOptions);
|
|
1320
1407
|
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1321
1408
|
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.setDiscriminatorEnum({
|
|
1322
1409
|
node: sharedPropertiesNode,
|
|
@@ -1346,7 +1433,10 @@ function createSchemaParser(ctx) {
|
|
|
1346
1433
|
return ast.createSchema({
|
|
1347
1434
|
type: "union",
|
|
1348
1435
|
...unionBase,
|
|
1349
|
-
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1436
|
+
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1437
|
+
schema: s,
|
|
1438
|
+
name
|
|
1439
|
+
}, rawOptions)))
|
|
1350
1440
|
});
|
|
1351
1441
|
}
|
|
1352
1442
|
/**
|
|
@@ -1360,7 +1450,8 @@ function createSchemaParser(ctx) {
|
|
|
1360
1450
|
name,
|
|
1361
1451
|
title: schema.title,
|
|
1362
1452
|
description: schema.description,
|
|
1363
|
-
deprecated: schema.deprecated
|
|
1453
|
+
deprecated: schema.deprecated,
|
|
1454
|
+
format: schema.format
|
|
1364
1455
|
});
|
|
1365
1456
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1366
1457
|
return ast.createSchema({
|
|
@@ -1450,6 +1541,15 @@ function createSchemaParser(ctx) {
|
|
|
1450
1541
|
}, rawOptions);
|
|
1451
1542
|
const nullInEnum = schema.enum.includes(null);
|
|
1452
1543
|
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1544
|
+
if (nullInEnum && filteredValues.length === 0) return ast.createSchema({
|
|
1545
|
+
type: "null",
|
|
1546
|
+
primitive: "null",
|
|
1547
|
+
name,
|
|
1548
|
+
title: schema.title,
|
|
1549
|
+
description: schema.description,
|
|
1550
|
+
deprecated: schema.deprecated,
|
|
1551
|
+
format: schema.format
|
|
1552
|
+
});
|
|
1453
1553
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
1454
1554
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1455
1555
|
const enumPrimitive = getPrimitiveType(type);
|
|
@@ -1464,7 +1564,8 @@ function createSchemaParser(ctx) {
|
|
|
1464
1564
|
readOnly: schema.readOnly,
|
|
1465
1565
|
writeOnly: schema.writeOnly,
|
|
1466
1566
|
default: enumDefault,
|
|
1467
|
-
example: schema.example
|
|
1567
|
+
example: schema.example,
|
|
1568
|
+
format: schema.format
|
|
1468
1569
|
};
|
|
1469
1570
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1470
1571
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1498,20 +1599,23 @@ function createSchemaParser(ctx) {
|
|
|
1498
1599
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1499
1600
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1500
1601
|
const resolvedPropSchema = propSchema;
|
|
1501
|
-
const propNullable = isNullable(resolvedPropSchema);
|
|
1602
|
+
const propNullable = dialect.isNullable(resolvedPropSchema);
|
|
1502
1603
|
const propNode = parseSchema({
|
|
1503
1604
|
schema: resolvedPropSchema,
|
|
1504
1605
|
name: ast.childName(name, propName)
|
|
1505
1606
|
}, rawOptions);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1607
|
+
const schemaNode = (() => {
|
|
1608
|
+
const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
|
|
1609
|
+
const tupleNode = ast.narrowSchema(node, "tuple");
|
|
1610
|
+
if (tupleNode?.items) {
|
|
1611
|
+
const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
|
|
1612
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1613
|
+
...tupleNode,
|
|
1614
|
+
items: namedItems
|
|
1615
|
+
};
|
|
1616
|
+
}
|
|
1617
|
+
return node;
|
|
1618
|
+
})();
|
|
1515
1619
|
return ast.createProperty({
|
|
1516
1620
|
name: propName,
|
|
1517
1621
|
schema: {
|
|
@@ -1522,11 +1626,12 @@ function createSchemaParser(ctx) {
|
|
|
1522
1626
|
});
|
|
1523
1627
|
}) : [];
|
|
1524
1628
|
const additionalProperties = schema.additionalProperties;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1629
|
+
const additionalPropertiesNode = (() => {
|
|
1630
|
+
if (additionalProperties === true) return true;
|
|
1631
|
+
if (additionalProperties === false) return false;
|
|
1632
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1633
|
+
if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1634
|
+
})();
|
|
1530
1635
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1531
1636
|
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
1637
|
const objectNode = ast.createSchema({
|
|
@@ -1539,7 +1644,7 @@ function createSchemaParser(ctx) {
|
|
|
1539
1644
|
maxProperties: schema.maxProperties,
|
|
1540
1645
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1541
1646
|
});
|
|
1542
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1647
|
+
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1543
1648
|
const discPropName = schema.discriminator.propertyName;
|
|
1544
1649
|
const values = Object.keys(schema.discriminator.mapping);
|
|
1545
1650
|
const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
@@ -1573,7 +1678,7 @@ function createSchemaParser(ctx) {
|
|
|
1573
1678
|
*/
|
|
1574
1679
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1575
1680
|
const rawItems = schema.items;
|
|
1576
|
-
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(
|
|
1681
|
+
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(null, name, options.enumSuffix) : name;
|
|
1577
1682
|
const items = rawItems ? [parseSchema({
|
|
1578
1683
|
schema: rawItems,
|
|
1579
1684
|
name: itemName
|
|
@@ -1637,15 +1742,148 @@ function createSchemaParser(ctx) {
|
|
|
1637
1742
|
title: schema.title,
|
|
1638
1743
|
description: schema.description,
|
|
1639
1744
|
deprecated: schema.deprecated,
|
|
1640
|
-
nullable
|
|
1745
|
+
nullable,
|
|
1746
|
+
format: schema.format
|
|
1747
|
+
});
|
|
1748
|
+
}
|
|
1749
|
+
/**
|
|
1750
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
1751
|
+
* into a `blob` node.
|
|
1752
|
+
*/
|
|
1753
|
+
function convertBlob({ schema, name, nullable, defaultValue }) {
|
|
1754
|
+
return ast.createSchema({
|
|
1755
|
+
type: "blob",
|
|
1756
|
+
primitive: "string",
|
|
1757
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1641
1758
|
});
|
|
1642
1759
|
}
|
|
1643
1760
|
/**
|
|
1761
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
1762
|
+
*
|
|
1763
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
|
|
1764
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
1765
|
+
*/
|
|
1766
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1767
|
+
const types = schema.type;
|
|
1768
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
1769
|
+
if (nonNullTypes.length <= 1) return null;
|
|
1770
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
1771
|
+
return ast.createSchema({
|
|
1772
|
+
type: "union",
|
|
1773
|
+
members: nonNullTypes.map((t) => parseSchema({
|
|
1774
|
+
schema: {
|
|
1775
|
+
...schema,
|
|
1776
|
+
type: t
|
|
1777
|
+
},
|
|
1778
|
+
name
|
|
1779
|
+
}, rawOptions)),
|
|
1780
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1785
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1786
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1787
|
+
* match/convert/fall-through contract.
|
|
1788
|
+
*/
|
|
1789
|
+
const schemaRules = [
|
|
1790
|
+
{
|
|
1791
|
+
name: "ref",
|
|
1792
|
+
match: ({ schema }) => dialect.isReference(schema),
|
|
1793
|
+
convert: convertRef
|
|
1794
|
+
},
|
|
1795
|
+
{
|
|
1796
|
+
name: "allOf",
|
|
1797
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
1798
|
+
convert: convertAllOf
|
|
1799
|
+
},
|
|
1800
|
+
{
|
|
1801
|
+
name: "union",
|
|
1802
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
1803
|
+
convert: convertUnion
|
|
1804
|
+
},
|
|
1805
|
+
{
|
|
1806
|
+
name: "const",
|
|
1807
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1808
|
+
convert: convertConst
|
|
1809
|
+
},
|
|
1810
|
+
{
|
|
1811
|
+
name: "format",
|
|
1812
|
+
match: ({ schema }) => !!schema.format,
|
|
1813
|
+
convert: convertFormat
|
|
1814
|
+
},
|
|
1815
|
+
{
|
|
1816
|
+
name: "blob",
|
|
1817
|
+
match: ({ schema }) => dialect.isBinary(schema),
|
|
1818
|
+
convert: convertBlob
|
|
1819
|
+
},
|
|
1820
|
+
{
|
|
1821
|
+
name: "multi-type",
|
|
1822
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1823
|
+
convert: convertMultiType
|
|
1824
|
+
},
|
|
1825
|
+
{
|
|
1826
|
+
name: "constrained-string",
|
|
1827
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1828
|
+
convert: convertString
|
|
1829
|
+
},
|
|
1830
|
+
{
|
|
1831
|
+
name: "constrained-number",
|
|
1832
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1833
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
name: "enum",
|
|
1837
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
1838
|
+
convert: convertEnum
|
|
1839
|
+
},
|
|
1840
|
+
{
|
|
1841
|
+
name: "object",
|
|
1842
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1843
|
+
convert: convertObject
|
|
1844
|
+
},
|
|
1845
|
+
{
|
|
1846
|
+
name: "tuple",
|
|
1847
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
1848
|
+
convert: convertTuple
|
|
1849
|
+
},
|
|
1850
|
+
{
|
|
1851
|
+
name: "array",
|
|
1852
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1853
|
+
convert: convertArray
|
|
1854
|
+
},
|
|
1855
|
+
{
|
|
1856
|
+
name: "string",
|
|
1857
|
+
match: ({ type }) => type === "string",
|
|
1858
|
+
convert: convertString
|
|
1859
|
+
},
|
|
1860
|
+
{
|
|
1861
|
+
name: "number",
|
|
1862
|
+
match: ({ type }) => type === "number",
|
|
1863
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1864
|
+
},
|
|
1865
|
+
{
|
|
1866
|
+
name: "integer",
|
|
1867
|
+
match: ({ type }) => type === "integer",
|
|
1868
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1869
|
+
},
|
|
1870
|
+
{
|
|
1871
|
+
name: "boolean",
|
|
1872
|
+
match: ({ type }) => type === "boolean",
|
|
1873
|
+
convert: convertBoolean
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
name: "null",
|
|
1877
|
+
match: ({ type }) => type === "null",
|
|
1878
|
+
convert: convertNull
|
|
1879
|
+
}
|
|
1880
|
+
];
|
|
1881
|
+
/**
|
|
1644
1882
|
* Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1645
1883
|
*
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1884
|
+
* Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
|
|
1885
|
+
* via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
|
|
1886
|
+
* `emptySchemaType`.
|
|
1649
1887
|
*/
|
|
1650
1888
|
function parseSchema({ schema, name }, rawOptions) {
|
|
1651
1889
|
const options = {
|
|
@@ -1657,75 +1895,40 @@ function createSchemaParser(ctx) {
|
|
|
1657
1895
|
schema: flattenedSchema,
|
|
1658
1896
|
name
|
|
1659
1897
|
}, 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 = {
|
|
1898
|
+
const nullable = dialect.isNullable(schema) || void 0;
|
|
1899
|
+
const schemaCtx = {
|
|
1664
1900
|
schema,
|
|
1665
1901
|
name,
|
|
1666
1902
|
nullable,
|
|
1667
|
-
defaultValue,
|
|
1668
|
-
type,
|
|
1903
|
+
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
1904
|
+
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1669
1905
|
rawOptions,
|
|
1670
1906
|
options
|
|
1671
1907
|
};
|
|
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);
|
|
1908
|
+
const node = ast.dispatch(schemaRules, schemaCtx);
|
|
1909
|
+
if (node) return node;
|
|
1713
1910
|
const emptyType = typeOptionMap.get(options.emptySchemaType);
|
|
1714
1911
|
return ast.createSchema({
|
|
1715
1912
|
type: emptyType,
|
|
1716
1913
|
name,
|
|
1717
1914
|
title: schema.title,
|
|
1718
|
-
description: schema.description
|
|
1915
|
+
description: schema.description,
|
|
1916
|
+
format: schema.format
|
|
1719
1917
|
});
|
|
1720
1918
|
}
|
|
1721
1919
|
/**
|
|
1722
1920
|
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
1723
1921
|
*/
|
|
1724
|
-
function parseParameter(options, param) {
|
|
1922
|
+
function parseParameter(options, param, parentName) {
|
|
1725
1923
|
const required = param["required"] ?? false;
|
|
1726
|
-
const
|
|
1924
|
+
const paramName = param["name"];
|
|
1925
|
+
const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
|
|
1926
|
+
const schema = param["schema"] ? parseSchema({
|
|
1927
|
+
schema: param["schema"],
|
|
1928
|
+
name: schemaName
|
|
1929
|
+
}, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1727
1930
|
return ast.createParameter({
|
|
1728
|
-
name:
|
|
1931
|
+
name: paramName,
|
|
1729
1932
|
in: param["in"],
|
|
1730
1933
|
schema: {
|
|
1731
1934
|
...schema,
|
|
@@ -1762,27 +1965,33 @@ function createSchemaParser(ctx) {
|
|
|
1762
1965
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1763
1966
|
*/
|
|
1764
1967
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1765
|
-
if (!schema?.properties) return
|
|
1968
|
+
if (!schema?.properties) return null;
|
|
1766
1969
|
const keys = [];
|
|
1767
1970
|
for (const key in schema.properties) {
|
|
1768
1971
|
const prop = schema.properties[key];
|
|
1769
|
-
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
1972
|
+
if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
|
|
1770
1973
|
}
|
|
1771
|
-
return keys.length ? keys :
|
|
1974
|
+
return keys.length ? keys : null;
|
|
1772
1975
|
}
|
|
1773
1976
|
/**
|
|
1774
1977
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1775
1978
|
*/
|
|
1776
1979
|
function parseOperation(options, operation) {
|
|
1777
|
-
const
|
|
1980
|
+
const operationId = operation.getOperationId();
|
|
1981
|
+
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
1982
|
+
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
|
|
1778
1983
|
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
|
|
1779
1984
|
const requestBodyMeta = getRequestBodyMeta(operation);
|
|
1985
|
+
const requestBodyName = operationName ? `${operationName}Request` : void 0;
|
|
1780
1986
|
const content = allContentTypes.flatMap((ct) => {
|
|
1781
1987
|
const schema = getRequestSchema(document, operation, { contentType: ct });
|
|
1782
1988
|
if (!schema) return [];
|
|
1783
1989
|
return [{
|
|
1784
1990
|
contentType: ct,
|
|
1785
|
-
schema: ast.syncOptionality(parseSchema({
|
|
1991
|
+
schema: ast.syncOptionality(parseSchema({
|
|
1992
|
+
schema,
|
|
1993
|
+
name: requestBodyName
|
|
1994
|
+
}, options), requestBodyMeta.required),
|
|
1786
1995
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1787
1996
|
}];
|
|
1788
1997
|
});
|
|
@@ -1793,21 +2002,36 @@ function createSchemaParser(ctx) {
|
|
|
1793
2002
|
} : void 0;
|
|
1794
2003
|
const responses = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1795
2004
|
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1796
|
-
const
|
|
1797
|
-
const
|
|
1798
|
-
const
|
|
1799
|
-
|
|
2005
|
+
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
2006
|
+
const { description } = getResponseMeta(responseObj);
|
|
2007
|
+
const parseEntrySchema = (contentType) => {
|
|
2008
|
+
const raw = getResponseSchema(document, operation, statusCode, { contentType });
|
|
2009
|
+
return {
|
|
2010
|
+
schema: raw && Object.keys(raw).length > 0 ? parseSchema({
|
|
2011
|
+
schema: raw,
|
|
2012
|
+
name: responseName
|
|
2013
|
+
}, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
|
|
2014
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
2015
|
+
};
|
|
2016
|
+
};
|
|
2017
|
+
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
|
|
2018
|
+
contentType,
|
|
2019
|
+
...parseEntrySchema(contentType)
|
|
2020
|
+
}));
|
|
2021
|
+
if (content.length === 0) content.push({
|
|
2022
|
+
contentType: operation.contentType || "application/json",
|
|
2023
|
+
...parseEntrySchema(ctx.contentType)
|
|
2024
|
+
});
|
|
1800
2025
|
return ast.createResponse({
|
|
1801
2026
|
statusCode,
|
|
1802
2027
|
description,
|
|
1803
|
-
|
|
1804
|
-
mediaType,
|
|
1805
|
-
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
2028
|
+
content
|
|
1806
2029
|
});
|
|
1807
2030
|
});
|
|
1808
2031
|
const urlPath = new URLPath(operation.path);
|
|
1809
2032
|
return ast.createOperation({
|
|
1810
|
-
operationId
|
|
2033
|
+
operationId,
|
|
2034
|
+
protocol: "http",
|
|
1811
2035
|
method: operation.method.toUpperCase(),
|
|
1812
2036
|
path: urlPath.path,
|
|
1813
2037
|
tags: operation.getTags().map((tag) => tag.name),
|
|
@@ -1825,59 +2049,336 @@ function createSchemaParser(ctx) {
|
|
|
1825
2049
|
parseParameter
|
|
1826
2050
|
};
|
|
1827
2051
|
}
|
|
2052
|
+
//#endregion
|
|
2053
|
+
//#region src/discriminator.ts
|
|
2054
|
+
/**
|
|
2055
|
+
* Builds a map of child schema names → discriminator patch data by scanning the given
|
|
2056
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
2057
|
+
*
|
|
2058
|
+
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
2059
|
+
* discriminator parents) rather than on all schemas at once.
|
|
2060
|
+
*/
|
|
2061
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
2062
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
2063
|
+
for (const schema of schemas) {
|
|
2064
|
+
let unionNode = ast.narrowSchema(schema, "union");
|
|
2065
|
+
if (!unionNode) {
|
|
2066
|
+
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
2067
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
2068
|
+
const u = ast.narrowSchema(m, "union");
|
|
2069
|
+
if (u) {
|
|
2070
|
+
unionNode = u;
|
|
2071
|
+
break;
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
2076
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
2077
|
+
for (const member of members) {
|
|
2078
|
+
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
2079
|
+
if (!intersectionNode?.members) continue;
|
|
2080
|
+
let refNode = null;
|
|
2081
|
+
let objNode = null;
|
|
2082
|
+
for (const m of intersectionNode.members) {
|
|
2083
|
+
refNode ??= ast.narrowSchema(m, "ref");
|
|
2084
|
+
objNode ??= ast.narrowSchema(m, "object");
|
|
2085
|
+
}
|
|
2086
|
+
if (!refNode?.name || !objNode) continue;
|
|
2087
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
2088
|
+
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
|
|
2089
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
2090
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
2091
|
+
if (!enumValues.length) continue;
|
|
2092
|
+
const existing = childMap.get(refNode.name);
|
|
2093
|
+
if (!existing) {
|
|
2094
|
+
childMap.set(refNode.name, {
|
|
2095
|
+
propertyName: discriminatorPropertyName,
|
|
2096
|
+
enumValues: [...enumValues]
|
|
2097
|
+
});
|
|
2098
|
+
continue;
|
|
2099
|
+
}
|
|
2100
|
+
existing.enumValues.push(...enumValues);
|
|
2101
|
+
}
|
|
2102
|
+
}
|
|
2103
|
+
return childMap;
|
|
2104
|
+
}
|
|
2105
|
+
/**
|
|
2106
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
2107
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
2108
|
+
* without buffering all schemas.
|
|
2109
|
+
*/
|
|
2110
|
+
function patchDiscriminatorNode(node, entry) {
|
|
2111
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
2112
|
+
if (!objectNode) return node;
|
|
2113
|
+
const { propertyName, enumValues } = entry;
|
|
2114
|
+
const enumSchema = ast.createSchema({
|
|
2115
|
+
type: "enum",
|
|
2116
|
+
enumValues
|
|
2117
|
+
});
|
|
2118
|
+
const newProp = ast.createProperty({
|
|
2119
|
+
name: propertyName,
|
|
2120
|
+
required: true,
|
|
2121
|
+
schema: enumSchema
|
|
2122
|
+
});
|
|
2123
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
2124
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
2125
|
+
return {
|
|
2126
|
+
...objectNode,
|
|
2127
|
+
properties: newProperties
|
|
2128
|
+
};
|
|
2129
|
+
}
|
|
2130
|
+
//#endregion
|
|
2131
|
+
//#region src/schemaDiagnostics.ts
|
|
2132
|
+
/**
|
|
2133
|
+
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
|
|
2134
|
+
* top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
|
|
2135
|
+
* pointer as it descends so a nested field reports against its full path
|
|
2136
|
+
* (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
|
|
2137
|
+
* resolved schema is reported under its own walk. Reports land in the active build run, are a
|
|
2138
|
+
* no-op outside one, and repeats are deduped by the build.
|
|
2139
|
+
*/
|
|
2140
|
+
function reportSchemaDiagnostics({ node, name }) {
|
|
2141
|
+
visit(node, `#/components/schemas/${escapePointerToken(name)}`);
|
|
2142
|
+
}
|
|
2143
|
+
/**
|
|
2144
|
+
* Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
|
|
2145
|
+
* property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
|
|
2146
|
+
*/
|
|
2147
|
+
function escapePointerToken(token) {
|
|
2148
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2149
|
+
}
|
|
2150
|
+
function visit(node, pointer) {
|
|
2151
|
+
if (node.deprecated) Diagnostics.report({
|
|
2152
|
+
code: Diagnostics.code.deprecated,
|
|
2153
|
+
severity: "info",
|
|
2154
|
+
message: "This schema is marked as deprecated.",
|
|
2155
|
+
location: {
|
|
2156
|
+
kind: "schema",
|
|
2157
|
+
pointer
|
|
2158
|
+
}
|
|
2159
|
+
});
|
|
2160
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
|
|
2161
|
+
code: Diagnostics.code.unsupportedFormat,
|
|
2162
|
+
severity: "warning",
|
|
2163
|
+
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
2164
|
+
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
2165
|
+
location: {
|
|
2166
|
+
kind: "schema",
|
|
2167
|
+
pointer
|
|
2168
|
+
}
|
|
2169
|
+
});
|
|
2170
|
+
if (node.type === "object") {
|
|
2171
|
+
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
2172
|
+
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
2173
|
+
return;
|
|
2174
|
+
}
|
|
2175
|
+
if (node.type === "array") {
|
|
2176
|
+
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
2177
|
+
return;
|
|
2178
|
+
}
|
|
2179
|
+
if (node.type === "tuple") {
|
|
2180
|
+
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
2181
|
+
return;
|
|
2182
|
+
}
|
|
2183
|
+
if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2184
|
+
}
|
|
2185
|
+
//#endregion
|
|
2186
|
+
//#region src/stream.ts
|
|
2187
|
+
/**
|
|
2188
|
+
* Builds the deduplication plan from the already-parsed top-level schema nodes plus a
|
|
2189
|
+
* single extra parse pass over operations (so duplicates in request/response bodies are seen).
|
|
2190
|
+
*
|
|
2191
|
+
* Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
2192
|
+
* rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
2193
|
+
* name (collision-resolved against existing component names); shapes without a name stay inline.
|
|
2194
|
+
*/
|
|
2195
|
+
function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
|
|
2196
|
+
const circularSchemas = new Set(circularNames);
|
|
2197
|
+
const usedNames = new Set(schemaNames);
|
|
2198
|
+
return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2199
|
+
isCandidate: (node) => {
|
|
2200
|
+
if (node.type === "enum") return true;
|
|
2201
|
+
if (node.type !== "object") return false;
|
|
2202
|
+
if (node.name && circularSchemas.has(node.name)) return false;
|
|
2203
|
+
return !ast.containsCircularRef(node, { circularSchemas });
|
|
2204
|
+
},
|
|
2205
|
+
nameFor: (node) => {
|
|
2206
|
+
const base = node.name;
|
|
2207
|
+
if (!base) return null;
|
|
2208
|
+
let name = base;
|
|
2209
|
+
let counter = 2;
|
|
2210
|
+
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
2211
|
+
usedNames.add(name);
|
|
2212
|
+
return name;
|
|
2213
|
+
},
|
|
2214
|
+
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
/**
|
|
2218
|
+
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
2219
|
+
* interpolating any `serverVariables` into the URL template.
|
|
2220
|
+
*
|
|
2221
|
+
* Returns `null` when `serverIndex` is omitted or out of range.
|
|
2222
|
+
*
|
|
2223
|
+
* @example Resolve the first server
|
|
2224
|
+
* `resolveBaseUrl({ document, serverIndex: 0 })`
|
|
2225
|
+
*
|
|
2226
|
+
* @example Override a path variable
|
|
2227
|
+
* `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
|
|
2228
|
+
*/
|
|
2229
|
+
function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
2230
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
2231
|
+
return server?.url ? resolveServerUrl(server, serverVariables) : null;
|
|
2232
|
+
}
|
|
1828
2233
|
/**
|
|
1829
|
-
* Parses
|
|
2234
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
2235
|
+
*
|
|
2236
|
+
* Three things happen in this single pass:
|
|
2237
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
2238
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
2239
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
2240
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
1830
2241
|
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* the tree is a pure data structure representing all schemas and operations.
|
|
2242
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2243
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
1834
2244
|
*
|
|
1835
|
-
*
|
|
2245
|
+
* Each schema is parsed again during the streaming pass. This is intentional.
|
|
2246
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1836
2247
|
*
|
|
1837
2248
|
* @example
|
|
1838
2249
|
* ```ts
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
2250
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
2251
|
+
* schemas,
|
|
2252
|
+
* parseSchema,
|
|
2253
|
+
* parserOptions,
|
|
2254
|
+
* discriminator: 'strict',
|
|
2255
|
+
* })
|
|
1843
2256
|
* ```
|
|
1844
2257
|
*/
|
|
1845
|
-
function
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
2258
|
+
function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions, discriminator, dedupe }) {
|
|
2259
|
+
const allNodes = [];
|
|
2260
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2261
|
+
const enumNames = [];
|
|
2262
|
+
const discriminatorParentNodes = [];
|
|
2263
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2264
|
+
const node = parseSchema({
|
|
2265
|
+
schema,
|
|
2266
|
+
name
|
|
2267
|
+
}, parserOptions);
|
|
2268
|
+
allNodes.push(node);
|
|
2269
|
+
reportSchemaDiagnostics({
|
|
2270
|
+
node,
|
|
2271
|
+
name
|
|
2272
|
+
});
|
|
2273
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2274
|
+
if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2275
|
+
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2276
|
+
}
|
|
2277
|
+
const circularNames = [...ast.findCircularSchemas(allNodes)];
|
|
2278
|
+
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2279
|
+
let dedupePlan = null;
|
|
2280
|
+
if (dedupe) {
|
|
2281
|
+
const operationNodes = [];
|
|
2282
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2283
|
+
if (!operation) continue;
|
|
2284
|
+
const operationNode = parseOperation(parserOptions, operation);
|
|
2285
|
+
if (operationNode) operationNodes.push(operationNode);
|
|
2286
|
+
}
|
|
2287
|
+
dedupePlan = createDedupePlan({
|
|
2288
|
+
schemaNodes: allNodes,
|
|
2289
|
+
operationNodes,
|
|
2290
|
+
schemaNames: Object.keys(schemas),
|
|
2291
|
+
circularNames
|
|
2292
|
+
});
|
|
2293
|
+
for (const definition of dedupePlan.hoisted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
|
|
2294
|
+
}
|
|
1862
2295
|
return {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
2296
|
+
refAliasMap,
|
|
2297
|
+
enumNames,
|
|
2298
|
+
circularNames,
|
|
2299
|
+
discriminatorChildMap,
|
|
2300
|
+
dedupePlan
|
|
2301
|
+
};
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Creates a lazy `InputStreamNode` from already-resolved adapter state.
|
|
2305
|
+
*
|
|
2306
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
2307
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
2308
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
2309
|
+
*
|
|
2310
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
2311
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
2312
|
+
*
|
|
2313
|
+
* @example
|
|
2314
|
+
* ```ts
|
|
2315
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2316
|
+
* for await (const schema of streamNode.schemas) {
|
|
2317
|
+
* // each call to for-await restarts from the first schema
|
|
2318
|
+
* }
|
|
2319
|
+
* ```
|
|
2320
|
+
*/
|
|
2321
|
+
function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
|
|
2322
|
+
const rewriteTopLevelSchema = (node) => {
|
|
2323
|
+
if (!dedupePlan) return node;
|
|
2324
|
+
const canonical = dedupePlan.canonicalBySignature.get(ast.schemaSignature(node));
|
|
2325
|
+
if (canonical && canonical.name !== node.name) return ast.createSchema({
|
|
2326
|
+
type: "ref",
|
|
2327
|
+
name: node.name ?? null,
|
|
2328
|
+
ref: canonical.ref,
|
|
2329
|
+
description: node.description,
|
|
2330
|
+
deprecated: node.deprecated
|
|
2331
|
+
});
|
|
2332
|
+
return ast.applyDedupe(node, dedupePlan.canonicalBySignature, true);
|
|
1868
2333
|
};
|
|
2334
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2335
|
+
return (async function* () {
|
|
2336
|
+
if (dedupePlan) for (const definition of dedupePlan.hoisted) yield definition;
|
|
2337
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2338
|
+
const alias = refAliasMap.get(name);
|
|
2339
|
+
if (alias?.name && schemas[alias.name]) {
|
|
2340
|
+
yield rewriteTopLevelSchema({
|
|
2341
|
+
...parseSchema({
|
|
2342
|
+
schema: schemas[alias.name],
|
|
2343
|
+
name: alias.name
|
|
2344
|
+
}, parserOptions),
|
|
2345
|
+
name
|
|
2346
|
+
});
|
|
2347
|
+
continue;
|
|
2348
|
+
}
|
|
2349
|
+
const parsed = parseSchema({
|
|
2350
|
+
schema,
|
|
2351
|
+
name
|
|
2352
|
+
}, parserOptions);
|
|
2353
|
+
yield rewriteTopLevelSchema(discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed);
|
|
2354
|
+
}
|
|
2355
|
+
})();
|
|
2356
|
+
} };
|
|
2357
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2358
|
+
return (async function* () {
|
|
2359
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2360
|
+
if (!operation) continue;
|
|
2361
|
+
const node = parseOperation(parserOptions, operation);
|
|
2362
|
+
if (node) yield dedupePlan ? ast.applyDedupe(node, dedupePlan.canonicalBySignature) : node;
|
|
2363
|
+
}
|
|
2364
|
+
})();
|
|
2365
|
+
} };
|
|
2366
|
+
return ast.createStreamInput(schemasIterable, operationsIterable, meta);
|
|
1869
2367
|
}
|
|
1870
2368
|
//#endregion
|
|
1871
2369
|
//#region src/adapter.ts
|
|
1872
2370
|
/**
|
|
1873
|
-
*
|
|
2371
|
+
* Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
|
|
1874
2372
|
*/
|
|
1875
2373
|
const adapterOasName = "oas";
|
|
1876
2374
|
/**
|
|
1877
|
-
*
|
|
2375
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
|
|
2376
|
+
* file at `input.path`, validates it, resolves the base URL, and converts every
|
|
2377
|
+
* schema and operation into the universal AST that every downstream plugin
|
|
2378
|
+
* consumes.
|
|
1878
2379
|
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
2380
|
+
* Configure once on `defineConfig`. The adapter's choices (date representation,
|
|
2381
|
+
* integer width, server URL) apply to every plugin in the build.
|
|
1881
2382
|
*
|
|
1882
2383
|
* @example
|
|
1883
2384
|
* ```ts
|
|
@@ -1886,17 +2387,118 @@ const adapterOasName = "oas";
|
|
|
1886
2387
|
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1887
2388
|
*
|
|
1888
2389
|
* export default defineConfig({
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
2390
|
+
* input: { path: './petStore.yaml' },
|
|
2391
|
+
* output: { path: './src/gen' },
|
|
2392
|
+
* adapter: adapterOas({
|
|
2393
|
+
* serverIndex: 0,
|
|
2394
|
+
* discriminator: 'inherit',
|
|
2395
|
+
* dateType: 'date',
|
|
2396
|
+
* }),
|
|
1891
2397
|
* plugins: [pluginTs()],
|
|
1892
2398
|
* })
|
|
1893
2399
|
* ```
|
|
1894
2400
|
*/
|
|
1895
2401
|
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;
|
|
2402
|
+
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;
|
|
2403
|
+
const parserOptions = {
|
|
2404
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2405
|
+
dateType,
|
|
2406
|
+
integerType,
|
|
2407
|
+
unknownType,
|
|
2408
|
+
emptySchemaType,
|
|
2409
|
+
enumSuffix
|
|
2410
|
+
};
|
|
1897
2411
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1898
|
-
let parsedDocument;
|
|
1899
|
-
|
|
2412
|
+
let parsedDocument = null;
|
|
2413
|
+
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2414
|
+
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2415
|
+
const baseOasCache = /* @__PURE__ */ new WeakMap();
|
|
2416
|
+
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2417
|
+
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2418
|
+
function ensureDocument(source) {
|
|
2419
|
+
const cached = documentCache.get(source);
|
|
2420
|
+
if (cached) return cached;
|
|
2421
|
+
const promise = (async () => {
|
|
2422
|
+
const fresh = await parseFromConfig(source);
|
|
2423
|
+
if (validate) await validateDocument(fresh);
|
|
2424
|
+
parsedDocument = fresh;
|
|
2425
|
+
return fresh;
|
|
2426
|
+
})();
|
|
2427
|
+
documentCache.set(source, promise);
|
|
2428
|
+
return promise;
|
|
2429
|
+
}
|
|
2430
|
+
function ensureSchemas(document) {
|
|
2431
|
+
const cached = schemasCache.get(document);
|
|
2432
|
+
if (cached) return cached;
|
|
2433
|
+
const promise = Promise.resolve().then(() => {
|
|
2434
|
+
const result = getSchemas(document, { contentType });
|
|
2435
|
+
nameMapping = result.nameMapping;
|
|
2436
|
+
return result.schemas;
|
|
2437
|
+
});
|
|
2438
|
+
schemasCache.set(document, promise);
|
|
2439
|
+
return promise;
|
|
2440
|
+
}
|
|
2441
|
+
function ensureBaseOas(document) {
|
|
2442
|
+
const cached = baseOasCache.get(document);
|
|
2443
|
+
if (cached) return cached;
|
|
2444
|
+
const baseOas = new BaseOas(document);
|
|
2445
|
+
baseOasCache.set(document, baseOas);
|
|
2446
|
+
return baseOas;
|
|
2447
|
+
}
|
|
2448
|
+
function ensureSchemaParser(document) {
|
|
2449
|
+
const cached = schemaParserCache.get(document);
|
|
2450
|
+
if (cached) return cached;
|
|
2451
|
+
const parser = createSchemaParser({
|
|
2452
|
+
document,
|
|
2453
|
+
contentType
|
|
2454
|
+
});
|
|
2455
|
+
schemaParserCache.set(document, parser);
|
|
2456
|
+
return parser;
|
|
2457
|
+
}
|
|
2458
|
+
function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
|
|
2459
|
+
const cached = preScanCache.get(document);
|
|
2460
|
+
if (cached) return cached;
|
|
2461
|
+
const result = preScan({
|
|
2462
|
+
schemas,
|
|
2463
|
+
parseSchema,
|
|
2464
|
+
parseOperation,
|
|
2465
|
+
baseOas,
|
|
2466
|
+
parserOptions,
|
|
2467
|
+
discriminator,
|
|
2468
|
+
dedupe
|
|
2469
|
+
});
|
|
2470
|
+
preScanCache.set(document, result);
|
|
2471
|
+
return result;
|
|
2472
|
+
}
|
|
2473
|
+
async function createStream(source) {
|
|
2474
|
+
const document = await ensureDocument(source);
|
|
2475
|
+
const schemas = await ensureSchemas(document);
|
|
2476
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2477
|
+
const baseOas = ensureBaseOas(document);
|
|
2478
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
|
|
2479
|
+
return createInputStream({
|
|
2480
|
+
schemas,
|
|
2481
|
+
parseSchema,
|
|
2482
|
+
parseOperation,
|
|
2483
|
+
baseOas,
|
|
2484
|
+
parserOptions,
|
|
2485
|
+
refAliasMap,
|
|
2486
|
+
discriminatorChildMap,
|
|
2487
|
+
dedupePlan,
|
|
2488
|
+
meta: {
|
|
2489
|
+
title: document.info?.title,
|
|
2490
|
+
description: document.info?.description,
|
|
2491
|
+
version: document.info?.version,
|
|
2492
|
+
baseURL: resolveBaseUrl({
|
|
2493
|
+
document,
|
|
2494
|
+
serverIndex,
|
|
2495
|
+
serverVariables
|
|
2496
|
+
}),
|
|
2497
|
+
circularNames,
|
|
2498
|
+
enumNames
|
|
2499
|
+
}
|
|
2500
|
+
});
|
|
2501
|
+
}
|
|
1900
2502
|
return {
|
|
1901
2503
|
name: "oas",
|
|
1902
2504
|
get options() {
|
|
@@ -1906,6 +2508,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
1906
2508
|
serverIndex,
|
|
1907
2509
|
serverVariables,
|
|
1908
2510
|
discriminator,
|
|
2511
|
+
dedupe,
|
|
1909
2512
|
dateType,
|
|
1910
2513
|
integerType,
|
|
1911
2514
|
unknownType,
|
|
@@ -1917,8 +2520,9 @@ const adapterOas = createAdapter((options) => {
|
|
|
1917
2520
|
get document() {
|
|
1918
2521
|
return parsedDocument;
|
|
1919
2522
|
},
|
|
1920
|
-
|
|
1921
|
-
|
|
2523
|
+
async validate(input, options) {
|
|
2524
|
+
await assertInputExists(input);
|
|
2525
|
+
await validateDocument(await parseDocument(input), options);
|
|
1922
2526
|
},
|
|
1923
2527
|
getImports(node, resolve) {
|
|
1924
2528
|
return ast.collectImports({
|
|
@@ -1926,7 +2530,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
1926
2530
|
nameMapping,
|
|
1927
2531
|
resolve: (schemaName) => {
|
|
1928
2532
|
const result = resolve(schemaName);
|
|
1929
|
-
if (!result) return;
|
|
2533
|
+
if (!result) return null;
|
|
1930
2534
|
return ast.createImport({
|
|
1931
2535
|
name: [result.name],
|
|
1932
2536
|
path: result.path
|
|
@@ -1935,32 +2539,20 @@ const adapterOas = createAdapter((options) => {
|
|
|
1935
2539
|
});
|
|
1936
2540
|
},
|
|
1937
2541
|
async parse(source) {
|
|
1938
|
-
const
|
|
1939
|
-
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
});
|
|
1950
|
-
const node = discriminator === "inherit" ? applyDiscriminatorInheritance(parsedRoot) : parsedRoot;
|
|
1951
|
-
nameMapping = parsedNameMapping;
|
|
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
|
-
}
|
|
2542
|
+
const streamNode = await createStream(source);
|
|
2543
|
+
const collect = async (iter) => {
|
|
2544
|
+
const out = [];
|
|
2545
|
+
for await (const item of iter) out.push(item);
|
|
2546
|
+
return out;
|
|
2547
|
+
};
|
|
2548
|
+
const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
|
|
2549
|
+
return ast.createInput({
|
|
2550
|
+
schemas,
|
|
2551
|
+
operations,
|
|
2552
|
+
meta: streamNode.meta
|
|
1961
2553
|
});
|
|
1962
|
-
|
|
1963
|
-
|
|
2554
|
+
},
|
|
2555
|
+
stream: createStream
|
|
1964
2556
|
};
|
|
1965
2557
|
});
|
|
1966
2558
|
//#endregion
|
|
@@ -1976,6 +2568,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
1976
2568
|
*/
|
|
1977
2569
|
const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
|
|
1978
2570
|
//#endregion
|
|
1979
|
-
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments
|
|
2571
|
+
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments };
|
|
1980
2572
|
|
|
1981
2573
|
//# sourceMappingURL=index.js.map
|