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