@kubb/adapter-oas 5.0.0-beta.3 → 5.0.0-beta.30
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 +98 -0
- package/dist/index.cjs +778 -417
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +37 -66
- package/dist/index.js +778 -414
- package/dist/index.js.map +1 -1
- package/extension.yaml +431 -0
- package/package.json +6 -4
- package/src/adapter.bench.ts +64 -0
- package/src/adapter.ts +90 -48
- package/src/dialect.ts +38 -0
- package/src/discriminator.ts +57 -40
- package/src/factory.ts +1 -4
- package/src/index.ts +2 -2
- package/src/parser.ts +219 -134
- package/src/refs.ts +16 -4
- package/src/resolvers.ts +50 -21
- package/src/stream.ts +175 -0
- package/src/types.ts +21 -11
package/dist/index.js
CHANGED
|
@@ -1,213 +1,12 @@
|
|
|
1
1
|
import "./chunk--u3MIqq1.js";
|
|
2
|
-
import { ast, createAdapter } from "@kubb/core";
|
|
3
2
|
import path from "node:path";
|
|
3
|
+
import { ast, createAdapter } from "@kubb/core";
|
|
4
|
+
import BaseOas from "oas";
|
|
4
5
|
import { bundle, loadConfig } from "@redocly/openapi-core";
|
|
5
6
|
import OASNormalize from "oas-normalize";
|
|
6
7
|
import swagger2openapi from "swagger2openapi";
|
|
7
|
-
import BaseOas from "oas";
|
|
8
8
|
import { isRef } from "oas/types";
|
|
9
9
|
import { matchesMimeType } from "oas/utils";
|
|
10
|
-
//#region src/constants.ts
|
|
11
|
-
/**
|
|
12
|
-
* Default parser options applied when no explicit options are provided.
|
|
13
|
-
*
|
|
14
|
-
* @example
|
|
15
|
-
* ```ts
|
|
16
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
17
|
-
*
|
|
18
|
-
* const parser = createOasParser(oas)
|
|
19
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
20
|
-
* ```
|
|
21
|
-
*/
|
|
22
|
-
const DEFAULT_PARSER_OPTIONS = {
|
|
23
|
-
dateType: "string",
|
|
24
|
-
integerType: "bigint",
|
|
25
|
-
unknownType: "any",
|
|
26
|
-
emptySchemaType: "any",
|
|
27
|
-
enumSuffix: "enum"
|
|
28
|
-
};
|
|
29
|
-
/**
|
|
30
|
-
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
31
|
-
*
|
|
32
|
-
* Used when building or parsing `$ref` strings.
|
|
33
|
-
*
|
|
34
|
-
* @example
|
|
35
|
-
* ```ts
|
|
36
|
-
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
37
|
-
* ```
|
|
38
|
-
*/
|
|
39
|
-
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
40
|
-
/**
|
|
41
|
-
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
42
|
-
*/
|
|
43
|
-
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
44
|
-
/**
|
|
45
|
-
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
46
|
-
*/
|
|
47
|
-
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
48
|
-
/**
|
|
49
|
-
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
50
|
-
*/
|
|
51
|
-
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
52
|
-
/**
|
|
53
|
-
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
54
|
-
*
|
|
55
|
-
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
56
|
-
* intersection member rather than being merged into the parent.
|
|
57
|
-
*
|
|
58
|
-
* @example
|
|
59
|
-
* ```ts
|
|
60
|
-
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
61
|
-
*
|
|
62
|
-
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
63
|
-
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
64
|
-
* ```
|
|
65
|
-
*/
|
|
66
|
-
const structuralKeys = new Set([
|
|
67
|
-
"properties",
|
|
68
|
-
"items",
|
|
69
|
-
"additionalProperties",
|
|
70
|
-
"oneOf",
|
|
71
|
-
"anyOf",
|
|
72
|
-
"allOf",
|
|
73
|
-
"not"
|
|
74
|
-
]);
|
|
75
|
-
/**
|
|
76
|
-
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
77
|
-
*
|
|
78
|
-
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
79
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
80
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
81
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
82
|
-
*
|
|
83
|
-
* @example
|
|
84
|
-
* ```ts
|
|
85
|
-
* import { formatMap } from '@kubb/adapter-oas'
|
|
86
|
-
*
|
|
87
|
-
* formatMap['uuid'] // 'uuid'
|
|
88
|
-
* formatMap['binary'] // 'blob'
|
|
89
|
-
* formatMap['float'] // 'number'
|
|
90
|
-
* ```
|
|
91
|
-
*/
|
|
92
|
-
const formatMap = {
|
|
93
|
-
uuid: "uuid",
|
|
94
|
-
email: "email",
|
|
95
|
-
"idn-email": "email",
|
|
96
|
-
uri: "url",
|
|
97
|
-
"uri-reference": "url",
|
|
98
|
-
url: "url",
|
|
99
|
-
ipv4: "ipv4",
|
|
100
|
-
ipv6: "ipv6",
|
|
101
|
-
hostname: "url",
|
|
102
|
-
"idn-hostname": "url",
|
|
103
|
-
binary: "blob",
|
|
104
|
-
byte: "blob",
|
|
105
|
-
int32: "integer",
|
|
106
|
-
float: "number",
|
|
107
|
-
double: "number"
|
|
108
|
-
};
|
|
109
|
-
/**
|
|
110
|
-
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
111
|
-
*
|
|
112
|
-
* @example
|
|
113
|
-
* ```ts
|
|
114
|
-
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
115
|
-
*
|
|
116
|
-
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
117
|
-
* ```
|
|
118
|
-
*/
|
|
119
|
-
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
120
|
-
/**
|
|
121
|
-
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
122
|
-
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
123
|
-
*/
|
|
124
|
-
const typeOptionMap = new Map([
|
|
125
|
-
["any", ast.schemaTypes.any],
|
|
126
|
-
["unknown", ast.schemaTypes.unknown],
|
|
127
|
-
["void", ast.schemaTypes.void]
|
|
128
|
-
]);
|
|
129
|
-
//#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
10
|
//#region ../../internals/utils/src/casing.ts
|
|
212
11
|
/**
|
|
213
12
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -306,6 +105,30 @@ function mergeDeep(target, source) {
|
|
|
306
105
|
return result;
|
|
307
106
|
}
|
|
308
107
|
//#endregion
|
|
108
|
+
//#region ../../internals/utils/src/promise.ts
|
|
109
|
+
/**
|
|
110
|
+
* Returns a wrapper that caches the result of the first invocation and replays
|
|
111
|
+
* it for every subsequent call, ignoring later arguments.
|
|
112
|
+
*
|
|
113
|
+
* Works for sync and async factories — for async, the cached value is the
|
|
114
|
+
* promise itself, so concurrent callers share one in-flight execution and
|
|
115
|
+
* cannot race each other.
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* ```ts
|
|
119
|
+
* const loadDocument = once(async (path: string) => parse(await readFile(path)))
|
|
120
|
+
* const a = loadDocument('./a.yaml') // parses
|
|
121
|
+
* const b = loadDocument('./b.yaml') // returns the cached promise from the first call
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
function once(factory) {
|
|
125
|
+
let cache;
|
|
126
|
+
return (...args) => {
|
|
127
|
+
if (!cache) cache = { value: factory(...args) };
|
|
128
|
+
return cache.value;
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
//#endregion
|
|
309
132
|
//#region ../../internals/utils/src/reserved.ts
|
|
310
133
|
/**
|
|
311
134
|
* JavaScript and Java reserved words.
|
|
@@ -517,12 +340,13 @@ var URLPath = class {
|
|
|
517
340
|
* @example
|
|
518
341
|
* new URLPath('/pet/{petId}').toTemplateString() // '`/pet/${petId}`'
|
|
519
342
|
*/
|
|
520
|
-
toTemplateString({ prefix
|
|
521
|
-
|
|
343
|
+
toTemplateString({ prefix, replacer } = {}) {
|
|
344
|
+
const result = this.path.split(/\{([^}]+)\}/).map((part, i) => {
|
|
522
345
|
if (i % 2 === 0) return part;
|
|
523
346
|
const param = this.#transformParam(part);
|
|
524
347
|
return `\${${replacer ? replacer(param) : param}}`;
|
|
525
|
-
}).join("")
|
|
348
|
+
}).join("");
|
|
349
|
+
return `\`${prefix ?? ""}${result}\``;
|
|
526
350
|
}
|
|
527
351
|
/**
|
|
528
352
|
* Extracts all `{param}` segments from the path and returns them as a key-value map.
|
|
@@ -555,6 +379,126 @@ var URLPath = class {
|
|
|
555
379
|
}
|
|
556
380
|
};
|
|
557
381
|
//#endregion
|
|
382
|
+
//#region src/constants.ts
|
|
383
|
+
/**
|
|
384
|
+
* Default parser options applied when no explicit options are provided.
|
|
385
|
+
*
|
|
386
|
+
* @example
|
|
387
|
+
* ```ts
|
|
388
|
+
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
389
|
+
*
|
|
390
|
+
* const parser = createOasParser(oas)
|
|
391
|
+
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
392
|
+
* ```
|
|
393
|
+
*/
|
|
394
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
395
|
+
dateType: "string",
|
|
396
|
+
integerType: "bigint",
|
|
397
|
+
unknownType: "any",
|
|
398
|
+
emptySchemaType: "any",
|
|
399
|
+
enumSuffix: "enum"
|
|
400
|
+
};
|
|
401
|
+
/**
|
|
402
|
+
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
403
|
+
*
|
|
404
|
+
* Used when building or parsing `$ref` strings.
|
|
405
|
+
*
|
|
406
|
+
* @example
|
|
407
|
+
* ```ts
|
|
408
|
+
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
409
|
+
* ```
|
|
410
|
+
*/
|
|
411
|
+
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
412
|
+
/**
|
|
413
|
+
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
414
|
+
*/
|
|
415
|
+
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
416
|
+
/**
|
|
417
|
+
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
418
|
+
*/
|
|
419
|
+
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
420
|
+
/**
|
|
421
|
+
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
422
|
+
*/
|
|
423
|
+
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
424
|
+
/**
|
|
425
|
+
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
426
|
+
*
|
|
427
|
+
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
428
|
+
* intersection member rather than being merged into the parent.
|
|
429
|
+
*
|
|
430
|
+
* @example
|
|
431
|
+
* ```ts
|
|
432
|
+
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
433
|
+
*
|
|
434
|
+
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
435
|
+
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
436
|
+
* ```
|
|
437
|
+
*/
|
|
438
|
+
const structuralKeys = new Set([
|
|
439
|
+
"properties",
|
|
440
|
+
"items",
|
|
441
|
+
"additionalProperties",
|
|
442
|
+
"oneOf",
|
|
443
|
+
"anyOf",
|
|
444
|
+
"allOf",
|
|
445
|
+
"not"
|
|
446
|
+
]);
|
|
447
|
+
/**
|
|
448
|
+
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
449
|
+
*
|
|
450
|
+
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
451
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
452
|
+
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
453
|
+
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
454
|
+
*
|
|
455
|
+
* @example
|
|
456
|
+
* ```ts
|
|
457
|
+
* import { formatMap } from '@kubb/adapter-oas'
|
|
458
|
+
*
|
|
459
|
+
* formatMap['uuid'] // 'uuid'
|
|
460
|
+
* formatMap['binary'] // 'blob'
|
|
461
|
+
* formatMap['float'] // 'number'
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
const formatMap = {
|
|
465
|
+
uuid: "uuid",
|
|
466
|
+
email: "email",
|
|
467
|
+
"idn-email": "email",
|
|
468
|
+
uri: "url",
|
|
469
|
+
"uri-reference": "url",
|
|
470
|
+
url: "url",
|
|
471
|
+
ipv4: "ipv4",
|
|
472
|
+
ipv6: "ipv6",
|
|
473
|
+
hostname: "url",
|
|
474
|
+
"idn-hostname": "url",
|
|
475
|
+
binary: "blob",
|
|
476
|
+
byte: "blob",
|
|
477
|
+
int32: "integer",
|
|
478
|
+
float: "number",
|
|
479
|
+
double: "number"
|
|
480
|
+
};
|
|
481
|
+
/**
|
|
482
|
+
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
483
|
+
*
|
|
484
|
+
* @example
|
|
485
|
+
* ```ts
|
|
486
|
+
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
487
|
+
*
|
|
488
|
+
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
489
|
+
* ```
|
|
490
|
+
*/
|
|
491
|
+
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
492
|
+
/**
|
|
493
|
+
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
494
|
+
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
495
|
+
*/
|
|
496
|
+
const typeOptionMap = new Map([
|
|
497
|
+
["any", ast.schemaTypes.any],
|
|
498
|
+
["unknown", ast.schemaTypes.unknown],
|
|
499
|
+
["void", ast.schemaTypes.void]
|
|
500
|
+
]);
|
|
501
|
+
//#endregion
|
|
558
502
|
//#region src/guards.ts
|
|
559
503
|
/**
|
|
560
504
|
* Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
|
|
@@ -660,11 +604,10 @@ async function parseDocument(pathOrApi, { canBundle = true, enablePaths = true }
|
|
|
660
604
|
* ```
|
|
661
605
|
*/
|
|
662
606
|
async function mergeDocuments(pathOrApi) {
|
|
663
|
-
const documents =
|
|
664
|
-
for (const p of pathOrApi) documents.push(await parseDocument(p, {
|
|
607
|
+
const documents = await Promise.all(pathOrApi.map((p) => parseDocument(p, {
|
|
665
608
|
enablePaths: false,
|
|
666
609
|
canBundle: false
|
|
667
|
-
}));
|
|
610
|
+
})));
|
|
668
611
|
if (documents.length === 0) throw new Error("No OAS documents provided for merging.");
|
|
669
612
|
const seed = {
|
|
670
613
|
openapi: MERGE_OPENAPI_VERSION,
|
|
@@ -720,6 +663,7 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
720
663
|
}
|
|
721
664
|
//#endregion
|
|
722
665
|
//#region src/refs.ts
|
|
666
|
+
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
723
667
|
/**
|
|
724
668
|
* Resolves a local JSON pointer reference from a document.
|
|
725
669
|
*
|
|
@@ -735,10 +679,17 @@ function resolveRef(document, $ref) {
|
|
|
735
679
|
const origRef = $ref;
|
|
736
680
|
$ref = $ref.trim();
|
|
737
681
|
if ($ref === "") return null;
|
|
738
|
-
if (
|
|
739
|
-
|
|
682
|
+
if (!$ref.startsWith("#")) return null;
|
|
683
|
+
$ref = globalThis.decodeURIComponent($ref.substring(1));
|
|
684
|
+
let docCache = _refCache.get(document);
|
|
685
|
+
if (!docCache) {
|
|
686
|
+
docCache = /* @__PURE__ */ new Map();
|
|
687
|
+
_refCache.set(document, docCache);
|
|
688
|
+
}
|
|
689
|
+
if (docCache.has($ref)) return docCache.get($ref);
|
|
740
690
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
741
691
|
if (!current) throw new Error(`Could not find a definition for ${origRef}.`);
|
|
692
|
+
docCache.set($ref, current);
|
|
742
693
|
return current;
|
|
743
694
|
}
|
|
744
695
|
/**
|
|
@@ -762,6 +713,35 @@ function dereferenceWithRef(document, schema) {
|
|
|
762
713
|
return schema;
|
|
763
714
|
}
|
|
764
715
|
//#endregion
|
|
716
|
+
//#region src/dialect.ts
|
|
717
|
+
/**
|
|
718
|
+
* The OpenAPI / Swagger dialect — the default used by `@kubb/adapter-oas`.
|
|
719
|
+
*
|
|
720
|
+
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
721
|
+
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
722
|
+
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
723
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect — `type: ['null', …]`
|
|
724
|
+
* nullability, no discriminator object, binary via `contentEncoding` — and reuses
|
|
725
|
+
* the rest unchanged.
|
|
726
|
+
*
|
|
727
|
+
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
728
|
+
* JSON Schema vocabulary, so the converters keep that common case.
|
|
729
|
+
*
|
|
730
|
+
* @example
|
|
731
|
+
* ```ts
|
|
732
|
+
* const parser = createSchemaParser(context) // uses oasDialect
|
|
733
|
+
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
734
|
+
* ```
|
|
735
|
+
*/
|
|
736
|
+
const oasDialect = ast.defineSchemaDialect({
|
|
737
|
+
name: "oas",
|
|
738
|
+
isNullable,
|
|
739
|
+
isReference,
|
|
740
|
+
isDiscriminator,
|
|
741
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
742
|
+
resolveRef
|
|
743
|
+
});
|
|
744
|
+
//#endregion
|
|
765
745
|
//#region src/resolvers.ts
|
|
766
746
|
/**
|
|
767
747
|
* Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
|
|
@@ -805,12 +785,6 @@ function getPrimitiveType(type) {
|
|
|
805
785
|
return "string";
|
|
806
786
|
}
|
|
807
787
|
/**
|
|
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
788
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
815
789
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
816
790
|
* `$ref` parameters resolve via `dereferenceWithRef` for backward compatibility.
|
|
@@ -953,21 +927,22 @@ function extractSchemaFromContent(content, preferredContentType) {
|
|
|
953
927
|
/**
|
|
954
928
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
955
929
|
*/
|
|
956
|
-
function collectRefs(schema
|
|
930
|
+
function* collectRefs(schema) {
|
|
957
931
|
if (Array.isArray(schema)) {
|
|
958
|
-
for (const item of schema) collectRefs(item
|
|
959
|
-
return
|
|
932
|
+
for (const item of schema) yield* collectRefs(item);
|
|
933
|
+
return;
|
|
960
934
|
}
|
|
961
935
|
if (schema && typeof schema === "object") for (const key in schema) {
|
|
962
936
|
const value = schema[key];
|
|
963
|
-
if (key === "$ref" && typeof value === "string") {
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
937
|
+
if (!(key === "$ref" && typeof value === "string")) {
|
|
938
|
+
yield* collectRefs(value);
|
|
939
|
+
continue;
|
|
940
|
+
}
|
|
941
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
942
|
+
const name = value.slice(21);
|
|
943
|
+
if (name) yield name;
|
|
944
|
+
}
|
|
969
945
|
}
|
|
970
|
-
return refs;
|
|
971
946
|
}
|
|
972
947
|
/**
|
|
973
948
|
* Returns a copy of `schemas` topologically sorted by `$ref` dependency.
|
|
@@ -983,7 +958,7 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
983
958
|
*/
|
|
984
959
|
function sortSchemas(schemas) {
|
|
985
960
|
const deps = /* @__PURE__ */ new Map();
|
|
986
|
-
for (const [name, schema] of Object.entries(schemas)) deps.set(name,
|
|
961
|
+
for (const [name, schema] of Object.entries(schemas)) deps.set(name, [...new Set(collectRefs(schema))]);
|
|
987
962
|
const sorted = [];
|
|
988
963
|
const visited = /* @__PURE__ */ new Set();
|
|
989
964
|
function visit(name, stack) {
|
|
@@ -1118,7 +1093,8 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1118
1093
|
readOnly: schema.readOnly,
|
|
1119
1094
|
writeOnly: schema.writeOnly,
|
|
1120
1095
|
default: defaultValue,
|
|
1121
|
-
example: schema.example
|
|
1096
|
+
example: schema.example,
|
|
1097
|
+
format: schema.format
|
|
1122
1098
|
};
|
|
1123
1099
|
}
|
|
1124
1100
|
/**
|
|
@@ -1140,6 +1116,31 @@ function getRequestBodyContentTypes(document, operation) {
|
|
|
1140
1116
|
if (!body) return [];
|
|
1141
1117
|
return body.content ? Object.keys(body.content) : [];
|
|
1142
1118
|
}
|
|
1119
|
+
/**
|
|
1120
|
+
* Returns all response content type keys for an operation at a given status code.
|
|
1121
|
+
*
|
|
1122
|
+
* Response `$ref`s are resolved in-place first — the same mutation `getResponseSchema` performs —
|
|
1123
|
+
* so the returned list reflects the available content types even for referenced responses.
|
|
1124
|
+
*
|
|
1125
|
+
* @example
|
|
1126
|
+
* ```ts
|
|
1127
|
+
* getResponseBodyContentTypes(document, operation, 200)
|
|
1128
|
+
* // ['application/json', 'application/xml']
|
|
1129
|
+
* ```
|
|
1130
|
+
*/
|
|
1131
|
+
function getResponseBodyContentTypes(document, operation, statusCode) {
|
|
1132
|
+
if (operation.schema.responses) {
|
|
1133
|
+
const responses = operation.schema.responses;
|
|
1134
|
+
for (const key in responses) {
|
|
1135
|
+
const schema = responses[key];
|
|
1136
|
+
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1140
|
+
if (!responseObj || typeof responseObj !== "object" || isReference(responseObj)) return [];
|
|
1141
|
+
const body = responseObj;
|
|
1142
|
+
return body.content ? Object.keys(body.content) : [];
|
|
1143
|
+
}
|
|
1143
1144
|
//#endregion
|
|
1144
1145
|
//#region src/parser.ts
|
|
1145
1146
|
/**
|
|
@@ -1168,9 +1169,9 @@ function normalizeArrayEnum(schema) {
|
|
|
1168
1169
|
* Each converter branch (`convertRef`, `convertAllOf`, etc.) mutually recursively calls `parseSchema`,
|
|
1169
1170
|
* made possible by hoisting of function declarations.
|
|
1170
1171
|
*
|
|
1171
|
-
* @
|
|
1172
|
+
* @internal
|
|
1172
1173
|
*/
|
|
1173
|
-
function createSchemaParser(ctx) {
|
|
1174
|
+
function createSchemaParser(ctx, dialect = oasDialect) {
|
|
1174
1175
|
const document = ctx.document;
|
|
1175
1176
|
/**
|
|
1176
1177
|
* Tracks `$ref` paths that are currently being resolved to prevent infinite
|
|
@@ -1178,6 +1179,19 @@ function createSchemaParser(ctx) {
|
|
|
1178
1179
|
*/
|
|
1179
1180
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1180
1181
|
/**
|
|
1182
|
+
* Cache of already-resolved `$ref` schemas within this parser instance.
|
|
1183
|
+
*
|
|
1184
|
+
* Without this, the same referenced schema (e.g. `customer`) is fully re-expanded
|
|
1185
|
+
* every time it appears as a `$ref` in a different parent schema. In heavily
|
|
1186
|
+
* cross-referenced specs like Stripe (~1 400 schemas), this causes exponential
|
|
1187
|
+
* blowup — `customer` alone may be referenced from dozens of top-level schemas,
|
|
1188
|
+
* each triggering a fresh recursive expansion of its entire sub-tree.
|
|
1189
|
+
*
|
|
1190
|
+
* Memoizing by `$ref` path reduces the overall work from O(2^depth) to O(N)
|
|
1191
|
+
* where N is the number of unique schema names.
|
|
1192
|
+
*/
|
|
1193
|
+
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1194
|
+
/**
|
|
1181
1195
|
* Converts a `$ref` schema into a `RefSchemaNode`.
|
|
1182
1196
|
*
|
|
1183
1197
|
* The resolved schema is stored in `node.schema`. Usage-site sibling fields
|
|
@@ -1186,16 +1200,22 @@ function createSchemaParser(ctx) {
|
|
|
1186
1200
|
* Circular refs are detected via `resolvingRefs` and leave `schema` as `undefined`.
|
|
1187
1201
|
*/
|
|
1188
1202
|
function convertRef({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1189
|
-
let resolvedSchema;
|
|
1203
|
+
let resolvedSchema = null;
|
|
1190
1204
|
const refPath = schema.$ref;
|
|
1191
|
-
if (refPath && !resolvingRefs.has(refPath))
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1205
|
+
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1206
|
+
if (!resolvedRefCache.has(refPath)) {
|
|
1207
|
+
try {
|
|
1208
|
+
const referenced = dialect.resolveRef(document, refPath);
|
|
1209
|
+
if (referenced) {
|
|
1210
|
+
resolvingRefs.add(refPath);
|
|
1211
|
+
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
1212
|
+
resolvingRefs.delete(refPath);
|
|
1213
|
+
}
|
|
1214
|
+
} catch {}
|
|
1215
|
+
resolvedRefCache.set(refPath, resolvedSchema);
|
|
1197
1216
|
}
|
|
1198
|
-
|
|
1217
|
+
resolvedSchema = resolvedRefCache.get(refPath) ?? null;
|
|
1218
|
+
}
|
|
1199
1219
|
return ast.createSchema({
|
|
1200
1220
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1201
1221
|
type: "ref",
|
|
@@ -1212,7 +1232,7 @@ function createSchemaParser(ctx) {
|
|
|
1212
1232
|
const [memberSchema] = schema.allOf;
|
|
1213
1233
|
const memberNode = parseSchema({
|
|
1214
1234
|
schema: memberSchema,
|
|
1215
|
-
name
|
|
1235
|
+
name
|
|
1216
1236
|
}, rawOptions);
|
|
1217
1237
|
const { kind: _kind, ...memberNodeProps } = memberNode;
|
|
1218
1238
|
const mergedNullable = nullable || memberNode.nullable || void 0;
|
|
@@ -1228,18 +1248,19 @@ function createSchemaParser(ctx) {
|
|
|
1228
1248
|
writeOnly: schema.writeOnly ?? memberNode.writeOnly,
|
|
1229
1249
|
default: mergedDefault,
|
|
1230
1250
|
example: schema.example ?? memberNode.example,
|
|
1231
|
-
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0)
|
|
1251
|
+
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
1252
|
+
format: schema.format ?? memberNode.format
|
|
1232
1253
|
});
|
|
1233
1254
|
}
|
|
1234
1255
|
const filteredDiscriminantValues = [];
|
|
1235
1256
|
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;
|
|
1257
|
+
if (!dialect.isReference(item) || !name) return true;
|
|
1258
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1259
|
+
if (!deref || !dialect.isDiscriminator(deref)) return true;
|
|
1239
1260
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1240
1261
|
if (!parentUnion) return true;
|
|
1241
1262
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1242
|
-
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1263
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1243
1264
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1244
1265
|
if (inOneOf || inMapping) {
|
|
1245
1266
|
const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef);
|
|
@@ -1250,22 +1271,28 @@ function createSchemaParser(ctx) {
|
|
|
1250
1271
|
return false;
|
|
1251
1272
|
}
|
|
1252
1273
|
return true;
|
|
1253
|
-
}).map((s) => parseSchema({
|
|
1274
|
+
}).map((s) => parseSchema({
|
|
1275
|
+
schema: s,
|
|
1276
|
+
name
|
|
1277
|
+
}, rawOptions));
|
|
1254
1278
|
const syntheticStart = allOfMembers.length;
|
|
1255
1279
|
if (Array.isArray(schema.required) && schema.required.length) {
|
|
1256
1280
|
const outerKeys = schema.properties ? new Set(Object.keys(schema.properties)) : /* @__PURE__ */ new Set();
|
|
1257
1281
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1258
1282
|
if (missingRequired.length) {
|
|
1259
1283
|
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] : [];
|
|
1284
|
+
if (!dialect.isReference(item)) return [item];
|
|
1285
|
+
const deref = dialect.resolveRef(document, item.$ref);
|
|
1286
|
+
return deref && !dialect.isReference(deref) ? [deref] : [];
|
|
1263
1287
|
});
|
|
1264
1288
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1265
|
-
allOfMembers.push(parseSchema({
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1289
|
+
allOfMembers.push(parseSchema({
|
|
1290
|
+
schema: {
|
|
1291
|
+
properties: { [key]: resolved.properties[key] },
|
|
1292
|
+
required: [key]
|
|
1293
|
+
},
|
|
1294
|
+
name
|
|
1295
|
+
}, rawOptions));
|
|
1269
1296
|
break;
|
|
1270
1297
|
}
|
|
1271
1298
|
}
|
|
@@ -1280,7 +1307,7 @@ function createSchemaParser(ctx) {
|
|
|
1280
1307
|
}));
|
|
1281
1308
|
return ast.createSchema({
|
|
1282
1309
|
type: "intersection",
|
|
1283
|
-
members: [...ast.
|
|
1310
|
+
members: [...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(0, syntheticStart)), ...ast.mergeAdjacentObjectsLazy(allOfMembers.slice(syntheticStart))],
|
|
1284
1311
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1285
1312
|
});
|
|
1286
1313
|
}
|
|
@@ -1301,10 +1328,10 @@ function createSchemaParser(ctx) {
|
|
|
1301
1328
|
const strategy = schema.oneOf ? "one" : "any";
|
|
1302
1329
|
const unionBase = {
|
|
1303
1330
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1304
|
-
discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1331
|
+
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1305
1332
|
strategy
|
|
1306
1333
|
};
|
|
1307
|
-
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1334
|
+
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1308
1335
|
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1309
1336
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1310
1337
|
return parseSchema({
|
|
@@ -1314,9 +1341,12 @@ function createSchemaParser(ctx) {
|
|
|
1314
1341
|
})() : void 0;
|
|
1315
1342
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1316
1343
|
const members = unionMembers.map((s) => {
|
|
1317
|
-
const ref = isReference(s) ? s.$ref : void 0;
|
|
1344
|
+
const ref = dialect.isReference(s) ? s.$ref : void 0;
|
|
1318
1345
|
const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref);
|
|
1319
|
-
const memberNode = parseSchema({
|
|
1346
|
+
const memberNode = parseSchema({
|
|
1347
|
+
schema: s,
|
|
1348
|
+
name
|
|
1349
|
+
}, rawOptions);
|
|
1320
1350
|
if (!discriminatorValue || !discriminator) return memberNode;
|
|
1321
1351
|
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.setDiscriminatorEnum({
|
|
1322
1352
|
node: sharedPropertiesNode,
|
|
@@ -1346,7 +1376,10 @@ function createSchemaParser(ctx) {
|
|
|
1346
1376
|
return ast.createSchema({
|
|
1347
1377
|
type: "union",
|
|
1348
1378
|
...unionBase,
|
|
1349
|
-
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1379
|
+
members: ast.simplifyUnion(unionMembers.map((s) => parseSchema({
|
|
1380
|
+
schema: s,
|
|
1381
|
+
name
|
|
1382
|
+
}, rawOptions)))
|
|
1350
1383
|
});
|
|
1351
1384
|
}
|
|
1352
1385
|
/**
|
|
@@ -1360,7 +1393,8 @@ function createSchemaParser(ctx) {
|
|
|
1360
1393
|
name,
|
|
1361
1394
|
title: schema.title,
|
|
1362
1395
|
description: schema.description,
|
|
1363
|
-
deprecated: schema.deprecated
|
|
1396
|
+
deprecated: schema.deprecated,
|
|
1397
|
+
format: schema.format
|
|
1364
1398
|
});
|
|
1365
1399
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1366
1400
|
return ast.createSchema({
|
|
@@ -1464,7 +1498,8 @@ function createSchemaParser(ctx) {
|
|
|
1464
1498
|
readOnly: schema.readOnly,
|
|
1465
1499
|
writeOnly: schema.writeOnly,
|
|
1466
1500
|
default: enumDefault,
|
|
1467
|
-
example: schema.example
|
|
1501
|
+
example: schema.example,
|
|
1502
|
+
format: schema.format
|
|
1468
1503
|
};
|
|
1469
1504
|
const extensionKey = enumExtensionKeys.find((key) => key in schema);
|
|
1470
1505
|
if (extensionKey || enumPrimitive === "number" || enumPrimitive === "integer" || enumPrimitive === "boolean") {
|
|
@@ -1498,20 +1533,23 @@ function createSchemaParser(ctx) {
|
|
|
1498
1533
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1499
1534
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1500
1535
|
const resolvedPropSchema = propSchema;
|
|
1501
|
-
const propNullable = isNullable(resolvedPropSchema);
|
|
1536
|
+
const propNullable = dialect.isNullable(resolvedPropSchema);
|
|
1502
1537
|
const propNode = parseSchema({
|
|
1503
1538
|
schema: resolvedPropSchema,
|
|
1504
1539
|
name: ast.childName(name, propName)
|
|
1505
1540
|
}, rawOptions);
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1541
|
+
const schemaNode = (() => {
|
|
1542
|
+
const node = ast.setEnumName(propNode, name, propName, options.enumSuffix);
|
|
1543
|
+
const tupleNode = ast.narrowSchema(node, "tuple");
|
|
1544
|
+
if (tupleNode?.items) {
|
|
1545
|
+
const namedItems = tupleNode.items.map((item) => ast.setEnumName(item, name, propName, options.enumSuffix));
|
|
1546
|
+
if (namedItems.some((item, i) => item !== tupleNode.items[i])) return {
|
|
1547
|
+
...tupleNode,
|
|
1548
|
+
items: namedItems
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
return node;
|
|
1552
|
+
})();
|
|
1515
1553
|
return ast.createProperty({
|
|
1516
1554
|
name: propName,
|
|
1517
1555
|
schema: {
|
|
@@ -1522,11 +1560,12 @@ function createSchemaParser(ctx) {
|
|
|
1522
1560
|
});
|
|
1523
1561
|
}) : [];
|
|
1524
1562
|
const additionalProperties = schema.additionalProperties;
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1563
|
+
const additionalPropertiesNode = (() => {
|
|
1564
|
+
if (additionalProperties === true) return true;
|
|
1565
|
+
if (additionalProperties === false) return false;
|
|
1566
|
+
if (additionalProperties && Object.keys(additionalProperties).length > 0) return parseSchema({ schema: additionalProperties }, rawOptions);
|
|
1567
|
+
if (additionalProperties) return ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1568
|
+
})();
|
|
1530
1569
|
const rawPatternProperties = "patternProperties" in schema ? schema.patternProperties : void 0;
|
|
1531
1570
|
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
1571
|
const objectNode = ast.createSchema({
|
|
@@ -1539,7 +1578,7 @@ function createSchemaParser(ctx) {
|
|
|
1539
1578
|
maxProperties: schema.maxProperties,
|
|
1540
1579
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1541
1580
|
});
|
|
1542
|
-
if (isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1581
|
+
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1543
1582
|
const discPropName = schema.discriminator.propertyName;
|
|
1544
1583
|
const values = Object.keys(schema.discriminator.mapping);
|
|
1545
1584
|
const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
@@ -1573,7 +1612,7 @@ function createSchemaParser(ctx) {
|
|
|
1573
1612
|
*/
|
|
1574
1613
|
function convertArray({ schema, name, nullable, defaultValue, rawOptions, options }) {
|
|
1575
1614
|
const rawItems = schema.items;
|
|
1576
|
-
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(
|
|
1615
|
+
const itemName = rawItems?.enum?.length && name ? ast.enumPropName(null, name, options.enumSuffix) : name;
|
|
1577
1616
|
const items = rawItems ? [parseSchema({
|
|
1578
1617
|
schema: rawItems,
|
|
1579
1618
|
name: itemName
|
|
@@ -1637,15 +1676,148 @@ function createSchemaParser(ctx) {
|
|
|
1637
1676
|
title: schema.title,
|
|
1638
1677
|
description: schema.description,
|
|
1639
1678
|
deprecated: schema.deprecated,
|
|
1640
|
-
nullable
|
|
1679
|
+
nullable,
|
|
1680
|
+
format: schema.format
|
|
1681
|
+
});
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
|
|
1685
|
+
* into a `blob` node.
|
|
1686
|
+
*/
|
|
1687
|
+
function convertBlob({ schema, name, nullable, defaultValue }) {
|
|
1688
|
+
return ast.createSchema({
|
|
1689
|
+
type: "blob",
|
|
1690
|
+
primitive: "string",
|
|
1691
|
+
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1641
1692
|
});
|
|
1642
1693
|
}
|
|
1643
1694
|
/**
|
|
1695
|
+
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
1696
|
+
*
|
|
1697
|
+
* Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
|
|
1698
|
+
* falls through and handles it as that single type with nullability already folded in.
|
|
1699
|
+
*/
|
|
1700
|
+
function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
|
|
1701
|
+
const types = schema.type;
|
|
1702
|
+
const nonNullTypes = types.filter((t) => t !== "null");
|
|
1703
|
+
if (nonNullTypes.length <= 1) return null;
|
|
1704
|
+
const arrayNullable = types.includes("null") || nullable || void 0;
|
|
1705
|
+
return ast.createSchema({
|
|
1706
|
+
type: "union",
|
|
1707
|
+
members: nonNullTypes.map((t) => parseSchema({
|
|
1708
|
+
schema: {
|
|
1709
|
+
...schema,
|
|
1710
|
+
type: t
|
|
1711
|
+
},
|
|
1712
|
+
name
|
|
1713
|
+
}, rawOptions)),
|
|
1714
|
+
...buildSchemaNode(schema, name, arrayNullable, defaultValue)
|
|
1715
|
+
});
|
|
1716
|
+
}
|
|
1717
|
+
/**
|
|
1718
|
+
* Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1719
|
+
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1720
|
+
* `type`. The first matching rule that produces a node wins; see {@link SchemaRule} for the
|
|
1721
|
+
* match/convert/fall-through contract.
|
|
1722
|
+
*/
|
|
1723
|
+
const schemaRules = [
|
|
1724
|
+
{
|
|
1725
|
+
name: "ref",
|
|
1726
|
+
match: ({ schema }) => dialect.isReference(schema),
|
|
1727
|
+
convert: convertRef
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
name: "allOf",
|
|
1731
|
+
match: ({ schema }) => !!schema.allOf?.length,
|
|
1732
|
+
convert: convertAllOf
|
|
1733
|
+
},
|
|
1734
|
+
{
|
|
1735
|
+
name: "union",
|
|
1736
|
+
match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
|
|
1737
|
+
convert: convertUnion
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
name: "const",
|
|
1741
|
+
match: ({ schema }) => "const" in schema && schema.const !== void 0,
|
|
1742
|
+
convert: convertConst
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
name: "format",
|
|
1746
|
+
match: ({ schema }) => !!schema.format,
|
|
1747
|
+
convert: convertFormat
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
name: "blob",
|
|
1751
|
+
match: ({ schema }) => dialect.isBinary(schema),
|
|
1752
|
+
convert: convertBlob
|
|
1753
|
+
},
|
|
1754
|
+
{
|
|
1755
|
+
name: "multi-type",
|
|
1756
|
+
match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
|
|
1757
|
+
convert: convertMultiType
|
|
1758
|
+
},
|
|
1759
|
+
{
|
|
1760
|
+
name: "constrained-string",
|
|
1761
|
+
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
1762
|
+
convert: convertString
|
|
1763
|
+
},
|
|
1764
|
+
{
|
|
1765
|
+
name: "constrained-number",
|
|
1766
|
+
match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
|
|
1767
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1768
|
+
},
|
|
1769
|
+
{
|
|
1770
|
+
name: "enum",
|
|
1771
|
+
match: ({ schema }) => !!schema.enum?.length,
|
|
1772
|
+
convert: convertEnum
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
name: "object",
|
|
1776
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
1777
|
+
convert: convertObject
|
|
1778
|
+
},
|
|
1779
|
+
{
|
|
1780
|
+
name: "tuple",
|
|
1781
|
+
match: ({ schema }) => "prefixItems" in schema,
|
|
1782
|
+
convert: convertTuple
|
|
1783
|
+
},
|
|
1784
|
+
{
|
|
1785
|
+
name: "array",
|
|
1786
|
+
match: ({ schema, type }) => type === "array" || "items" in schema,
|
|
1787
|
+
convert: convertArray
|
|
1788
|
+
},
|
|
1789
|
+
{
|
|
1790
|
+
name: "string",
|
|
1791
|
+
match: ({ type }) => type === "string",
|
|
1792
|
+
convert: convertString
|
|
1793
|
+
},
|
|
1794
|
+
{
|
|
1795
|
+
name: "number",
|
|
1796
|
+
match: ({ type }) => type === "number",
|
|
1797
|
+
convert: (ctx) => convertNumeric(ctx, "number")
|
|
1798
|
+
},
|
|
1799
|
+
{
|
|
1800
|
+
name: "integer",
|
|
1801
|
+
match: ({ type }) => type === "integer",
|
|
1802
|
+
convert: (ctx) => convertNumeric(ctx, "integer")
|
|
1803
|
+
},
|
|
1804
|
+
{
|
|
1805
|
+
name: "boolean",
|
|
1806
|
+
match: ({ type }) => type === "boolean",
|
|
1807
|
+
convert: convertBoolean
|
|
1808
|
+
},
|
|
1809
|
+
{
|
|
1810
|
+
name: "null",
|
|
1811
|
+
match: ({ type }) => type === "null",
|
|
1812
|
+
convert: convertNull
|
|
1813
|
+
}
|
|
1814
|
+
];
|
|
1815
|
+
/**
|
|
1644
1816
|
* Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
|
|
1645
1817
|
*
|
|
1646
|
-
*
|
|
1647
|
-
*
|
|
1648
|
-
*
|
|
1818
|
+
* Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
|
|
1819
|
+
* via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
|
|
1820
|
+
* `emptySchemaType`.
|
|
1649
1821
|
*/
|
|
1650
1822
|
function parseSchema({ schema, name }, rawOptions) {
|
|
1651
1823
|
const options = {
|
|
@@ -1657,75 +1829,40 @@ function createSchemaParser(ctx) {
|
|
|
1657
1829
|
schema: flattenedSchema,
|
|
1658
1830
|
name
|
|
1659
1831
|
}, 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 = {
|
|
1832
|
+
const nullable = dialect.isNullable(schema) || void 0;
|
|
1833
|
+
const schemaCtx = {
|
|
1664
1834
|
schema,
|
|
1665
1835
|
name,
|
|
1666
1836
|
nullable,
|
|
1667
|
-
defaultValue,
|
|
1668
|
-
type,
|
|
1837
|
+
defaultValue: schema.default === null && nullable ? void 0 : schema.default,
|
|
1838
|
+
type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
|
|
1669
1839
|
rawOptions,
|
|
1670
1840
|
options
|
|
1671
1841
|
};
|
|
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);
|
|
1842
|
+
const node = ast.dispatch(schemaRules, schemaCtx);
|
|
1843
|
+
if (node) return node;
|
|
1713
1844
|
const emptyType = typeOptionMap.get(options.emptySchemaType);
|
|
1714
1845
|
return ast.createSchema({
|
|
1715
1846
|
type: emptyType,
|
|
1716
1847
|
name,
|
|
1717
1848
|
title: schema.title,
|
|
1718
|
-
description: schema.description
|
|
1849
|
+
description: schema.description,
|
|
1850
|
+
format: schema.format
|
|
1719
1851
|
});
|
|
1720
1852
|
}
|
|
1721
1853
|
/**
|
|
1722
1854
|
* Converts a dereferenced OAS parameter object into a `ParameterNode`.
|
|
1723
1855
|
*/
|
|
1724
|
-
function parseParameter(options, param) {
|
|
1856
|
+
function parseParameter(options, param, parentName) {
|
|
1725
1857
|
const required = param["required"] ?? false;
|
|
1726
|
-
const
|
|
1858
|
+
const paramName = param["name"];
|
|
1859
|
+
const schemaName = parentName && paramName ? pascalCase(`${parentName} ${paramName}`) : void 0;
|
|
1860
|
+
const schema = param["schema"] ? parseSchema({
|
|
1861
|
+
schema: param["schema"],
|
|
1862
|
+
name: schemaName
|
|
1863
|
+
}, options) : ast.createSchema({ type: typeOptionMap.get(options.unknownType) });
|
|
1727
1864
|
return ast.createParameter({
|
|
1728
|
-
name:
|
|
1865
|
+
name: paramName,
|
|
1729
1866
|
in: param["in"],
|
|
1730
1867
|
schema: {
|
|
1731
1868
|
...schema,
|
|
@@ -1762,27 +1899,33 @@ function createSchemaParser(ctx) {
|
|
|
1762
1899
|
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1763
1900
|
*/
|
|
1764
1901
|
function collectPropertyKeysByFlag(schema, flag) {
|
|
1765
|
-
if (!schema?.properties) return
|
|
1902
|
+
if (!schema?.properties) return null;
|
|
1766
1903
|
const keys = [];
|
|
1767
1904
|
for (const key in schema.properties) {
|
|
1768
1905
|
const prop = schema.properties[key];
|
|
1769
|
-
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
1906
|
+
if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
|
|
1770
1907
|
}
|
|
1771
|
-
return keys.length ? keys :
|
|
1908
|
+
return keys.length ? keys : null;
|
|
1772
1909
|
}
|
|
1773
1910
|
/**
|
|
1774
1911
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1775
1912
|
*/
|
|
1776
1913
|
function parseOperation(options, operation) {
|
|
1777
|
-
const
|
|
1914
|
+
const operationId = operation.getOperationId();
|
|
1915
|
+
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
1916
|
+
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param, operationName));
|
|
1778
1917
|
const allContentTypes = ctx.contentType ? [ctx.contentType] : getRequestBodyContentTypes(document, operation);
|
|
1779
1918
|
const requestBodyMeta = getRequestBodyMeta(operation);
|
|
1919
|
+
const requestBodyName = operationName ? `${operationName}Request` : void 0;
|
|
1780
1920
|
const content = allContentTypes.flatMap((ct) => {
|
|
1781
1921
|
const schema = getRequestSchema(document, operation, { contentType: ct });
|
|
1782
1922
|
if (!schema) return [];
|
|
1783
1923
|
return [{
|
|
1784
1924
|
contentType: ct,
|
|
1785
|
-
schema: ast.syncOptionality(parseSchema({
|
|
1925
|
+
schema: ast.syncOptionality(parseSchema({
|
|
1926
|
+
schema,
|
|
1927
|
+
name: requestBodyName
|
|
1928
|
+
}, options), requestBodyMeta.required),
|
|
1786
1929
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1787
1930
|
}];
|
|
1788
1931
|
});
|
|
@@ -1793,21 +1936,36 @@ function createSchemaParser(ctx) {
|
|
|
1793
1936
|
} : void 0;
|
|
1794
1937
|
const responses = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1795
1938
|
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1796
|
-
const
|
|
1797
|
-
const
|
|
1798
|
-
const
|
|
1799
|
-
|
|
1939
|
+
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
1940
|
+
const { description } = getResponseMeta(responseObj);
|
|
1941
|
+
const parseEntrySchema = (contentType) => {
|
|
1942
|
+
const raw = getResponseSchema(document, operation, statusCode, { contentType });
|
|
1943
|
+
return {
|
|
1944
|
+
schema: raw && Object.keys(raw).length > 0 ? parseSchema({
|
|
1945
|
+
schema: raw,
|
|
1946
|
+
name: responseName
|
|
1947
|
+
}, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) }),
|
|
1948
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
1949
|
+
};
|
|
1950
|
+
};
|
|
1951
|
+
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
|
|
1952
|
+
contentType,
|
|
1953
|
+
...parseEntrySchema(contentType)
|
|
1954
|
+
}));
|
|
1955
|
+
if (content.length === 0) content.push({
|
|
1956
|
+
contentType: operation.contentType || "application/json",
|
|
1957
|
+
...parseEntrySchema(ctx.contentType)
|
|
1958
|
+
});
|
|
1800
1959
|
return ast.createResponse({
|
|
1801
1960
|
statusCode,
|
|
1802
1961
|
description,
|
|
1803
|
-
|
|
1804
|
-
mediaType,
|
|
1805
|
-
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
1962
|
+
content
|
|
1806
1963
|
});
|
|
1807
1964
|
});
|
|
1808
1965
|
const urlPath = new URLPath(operation.path);
|
|
1809
1966
|
return ast.createOperation({
|
|
1810
|
-
operationId
|
|
1967
|
+
operationId,
|
|
1968
|
+
protocol: "http",
|
|
1811
1969
|
method: operation.method.toUpperCase(),
|
|
1812
1970
|
path: urlPath.path,
|
|
1813
1971
|
tags: operation.getTags().map((tag) => tag.name),
|
|
@@ -1825,59 +1983,216 @@ function createSchemaParser(ctx) {
|
|
|
1825
1983
|
parseParameter
|
|
1826
1984
|
};
|
|
1827
1985
|
}
|
|
1986
|
+
//#endregion
|
|
1987
|
+
//#region src/discriminator.ts
|
|
1988
|
+
/**
|
|
1989
|
+
* Builds a map of child schema names → discriminator patch data by scanning the given
|
|
1990
|
+
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
1991
|
+
*
|
|
1992
|
+
* Extracted from `applyDiscriminatorInheritance` so the streaming path can call it on a
|
|
1993
|
+
* small pre-parsed subset of schemas (only the discriminator parents) rather than on all
|
|
1994
|
+
* schemas at once.
|
|
1995
|
+
*/
|
|
1996
|
+
function buildDiscriminatorChildMap(schemas) {
|
|
1997
|
+
const childMap = /* @__PURE__ */ new Map();
|
|
1998
|
+
for (const schema of schemas) {
|
|
1999
|
+
let unionNode = ast.narrowSchema(schema, "union");
|
|
2000
|
+
if (!unionNode) {
|
|
2001
|
+
const intersectionMembers = ast.narrowSchema(schema, "intersection")?.members;
|
|
2002
|
+
if (intersectionMembers) for (const m of intersectionMembers) {
|
|
2003
|
+
const u = ast.narrowSchema(m, "union");
|
|
2004
|
+
if (u) {
|
|
2005
|
+
unionNode = u;
|
|
2006
|
+
break;
|
|
2007
|
+
}
|
|
2008
|
+
}
|
|
2009
|
+
}
|
|
2010
|
+
if (!unionNode?.discriminatorPropertyName || !unionNode.members) continue;
|
|
2011
|
+
const { discriminatorPropertyName, members } = unionNode;
|
|
2012
|
+
for (const member of members) {
|
|
2013
|
+
const intersectionNode = ast.narrowSchema(member, "intersection");
|
|
2014
|
+
if (!intersectionNode?.members) continue;
|
|
2015
|
+
let refNode = null;
|
|
2016
|
+
let objNode = null;
|
|
2017
|
+
for (const m of intersectionNode.members) {
|
|
2018
|
+
refNode ??= ast.narrowSchema(m, "ref");
|
|
2019
|
+
objNode ??= ast.narrowSchema(m, "object");
|
|
2020
|
+
}
|
|
2021
|
+
if (!refNode?.name || !objNode) continue;
|
|
2022
|
+
const prop = objNode.properties.find((p) => p.name === discriminatorPropertyName);
|
|
2023
|
+
const enumNode = prop ? ast.narrowSchema(prop.schema, "enum") : null;
|
|
2024
|
+
if (!enumNode?.enumValues?.length) continue;
|
|
2025
|
+
const enumValues = enumNode.enumValues.filter((v) => v !== null);
|
|
2026
|
+
if (!enumValues.length) continue;
|
|
2027
|
+
const existing = childMap.get(refNode.name);
|
|
2028
|
+
if (!existing) {
|
|
2029
|
+
childMap.set(refNode.name, {
|
|
2030
|
+
propertyName: discriminatorPropertyName,
|
|
2031
|
+
enumValues: [...enumValues]
|
|
2032
|
+
});
|
|
2033
|
+
continue;
|
|
2034
|
+
}
|
|
2035
|
+
existing.enumValues.push(...enumValues);
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
return childMap;
|
|
2039
|
+
}
|
|
2040
|
+
/**
|
|
2041
|
+
* Patches a single top-level `SchemaNode` with its discriminator entry (adds or replaces
|
|
2042
|
+
* the discriminant property). Used by the streaming path to apply patches inline per yield
|
|
2043
|
+
* without buffering all schemas.
|
|
2044
|
+
*/
|
|
2045
|
+
function patchDiscriminatorNode(node, entry) {
|
|
2046
|
+
const objectNode = ast.narrowSchema(node, "object");
|
|
2047
|
+
if (!objectNode) return node;
|
|
2048
|
+
const { propertyName, enumValues } = entry;
|
|
2049
|
+
const enumSchema = ast.createSchema({
|
|
2050
|
+
type: "enum",
|
|
2051
|
+
enumValues
|
|
2052
|
+
});
|
|
2053
|
+
const newProp = ast.createProperty({
|
|
2054
|
+
name: propertyName,
|
|
2055
|
+
required: true,
|
|
2056
|
+
schema: enumSchema
|
|
2057
|
+
});
|
|
2058
|
+
const existingIdx = objectNode.properties.findIndex((p) => p.name === propertyName);
|
|
2059
|
+
const newProperties = existingIdx >= 0 ? objectNode.properties.map((p, i) => i === existingIdx ? newProp : p) : [...objectNode.properties, newProp];
|
|
2060
|
+
return {
|
|
2061
|
+
...objectNode,
|
|
2062
|
+
properties: newProperties
|
|
2063
|
+
};
|
|
2064
|
+
}
|
|
2065
|
+
//#endregion
|
|
2066
|
+
//#region src/stream.ts
|
|
2067
|
+
/**
|
|
2068
|
+
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
2069
|
+
* interpolating any `serverVariables` into the URL template.
|
|
2070
|
+
*
|
|
2071
|
+
* Returns `null` when `serverIndex` is omitted or out of range.
|
|
2072
|
+
*
|
|
2073
|
+
* @example Resolve the first server
|
|
2074
|
+
* `resolveBaseUrl({ document, serverIndex: 0 })`
|
|
2075
|
+
*
|
|
2076
|
+
* @example Override a path variable
|
|
2077
|
+
* `resolveBaseUrl({ document, serverIndex: 0, serverVariables: { version: 'v2' } })`
|
|
2078
|
+
*/
|
|
2079
|
+
function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
2080
|
+
const server = serverIndex !== void 0 ? document.servers?.at(serverIndex) : void 0;
|
|
2081
|
+
return server?.url ? resolveServerUrl(server, serverVariables) : null;
|
|
2082
|
+
}
|
|
1828
2083
|
/**
|
|
1829
|
-
* Parses
|
|
2084
|
+
* Parses every schema once to build the lookup structures that streaming needs upfront.
|
|
2085
|
+
*
|
|
2086
|
+
* Three things happen in this single pass:
|
|
2087
|
+
* - `refAliasMap` records schemas that are pure `$ref` aliases so the streaming pass can inline them.
|
|
2088
|
+
* - `enumNames` collects the names of every enum schema so plugins skip re-scanning the stream.
|
|
2089
|
+
* - `circularNames` runs cycle detection, which requires all nodes in memory simultaneously.
|
|
2090
|
+
* The `allNodes` array is local and drops out of scope as soon as this function returns.
|
|
1830
2091
|
*
|
|
1831
|
-
*
|
|
1832
|
-
*
|
|
1833
|
-
* the tree is a pure data structure representing all schemas and operations.
|
|
2092
|
+
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2093
|
+
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
1834
2094
|
*
|
|
1835
|
-
*
|
|
2095
|
+
* Each schema is parsed again during the streaming pass — this is intentional.
|
|
2096
|
+
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
1836
2097
|
*
|
|
1837
2098
|
* @example
|
|
1838
2099
|
* ```ts
|
|
1839
|
-
*
|
|
1840
|
-
*
|
|
1841
|
-
*
|
|
1842
|
-
*
|
|
2100
|
+
* const { refAliasMap, enumNames, circularNames } = preScan({
|
|
2101
|
+
* schemas,
|
|
2102
|
+
* parseSchema,
|
|
2103
|
+
* parserOptions,
|
|
2104
|
+
* discriminator: 'strict',
|
|
2105
|
+
* })
|
|
1843
2106
|
* ```
|
|
1844
2107
|
*/
|
|
1845
|
-
function
|
|
1846
|
-
const
|
|
1847
|
-
const
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
}
|
|
1860
|
-
const paths = new BaseOas(document).getPaths();
|
|
1861
|
-
const operations = Object.entries(paths).flatMap(([_path, methods]) => Object.entries(methods).map(([, operation]) => operation ? _parseOperation(mergedOptions, operation) : null).filter((op) => op !== null));
|
|
2108
|
+
function preScan({ schemas, parseSchema, parserOptions, discriminator }) {
|
|
2109
|
+
const allNodes = [];
|
|
2110
|
+
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2111
|
+
const enumNames = [];
|
|
2112
|
+
const discriminatorParentNodes = [];
|
|
2113
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2114
|
+
const node = parseSchema({
|
|
2115
|
+
schema,
|
|
2116
|
+
name
|
|
2117
|
+
}, parserOptions);
|
|
2118
|
+
allNodes.push(node);
|
|
2119
|
+
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2120
|
+
if (ast.narrowSchema(node, ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2121
|
+
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
2122
|
+
}
|
|
1862
2123
|
return {
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
nameMapping
|
|
2124
|
+
refAliasMap,
|
|
2125
|
+
enumNames,
|
|
2126
|
+
circularNames: [...ast.findCircularSchemas(allNodes)],
|
|
2127
|
+
discriminatorChildMap: discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null
|
|
1868
2128
|
};
|
|
1869
2129
|
}
|
|
2130
|
+
/**
|
|
2131
|
+
* Creates a lazy `InputStreamNode` from already-resolved adapter state.
|
|
2132
|
+
*
|
|
2133
|
+
* The schema and operation iterables each start a fresh parse pass on every
|
|
2134
|
+
* `[Symbol.asyncIterator]()` call. This lets multiple plugins consume the same
|
|
2135
|
+
* stream object independently without sharing a cursor or holding all nodes in memory.
|
|
2136
|
+
*
|
|
2137
|
+
* Ref aliases in `refAliasMap` are inlined during iteration: an alias entry is replaced
|
|
2138
|
+
* with its target's parsed node (but keeps the alias name) so plugins never receive bare `ref` nodes.
|
|
2139
|
+
*
|
|
2140
|
+
* @example
|
|
2141
|
+
* ```ts
|
|
2142
|
+
* const streamNode = createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta })
|
|
2143
|
+
* for await (const schema of streamNode.schemas) {
|
|
2144
|
+
* // each call to for-await restarts from the first schema
|
|
2145
|
+
* }
|
|
2146
|
+
* ```
|
|
2147
|
+
*/
|
|
2148
|
+
function createInputStream({ schemas, parseSchema, parseOperation, baseOas, parserOptions, refAliasMap, discriminatorChildMap, meta }) {
|
|
2149
|
+
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2150
|
+
return (async function* () {
|
|
2151
|
+
for (const [name, schema] of Object.entries(schemas)) {
|
|
2152
|
+
const alias = refAliasMap.get(name);
|
|
2153
|
+
if (alias?.name && schemas[alias.name]) {
|
|
2154
|
+
yield {
|
|
2155
|
+
...parseSchema({
|
|
2156
|
+
schema: schemas[alias.name],
|
|
2157
|
+
name: alias.name
|
|
2158
|
+
}, parserOptions),
|
|
2159
|
+
name
|
|
2160
|
+
};
|
|
2161
|
+
continue;
|
|
2162
|
+
}
|
|
2163
|
+
const parsed = parseSchema({
|
|
2164
|
+
schema,
|
|
2165
|
+
name
|
|
2166
|
+
}, parserOptions);
|
|
2167
|
+
yield discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
2168
|
+
}
|
|
2169
|
+
})();
|
|
2170
|
+
} };
|
|
2171
|
+
const operationsIterable = { [Symbol.asyncIterator]() {
|
|
2172
|
+
return (async function* () {
|
|
2173
|
+
for (const methods of Object.values(baseOas.getPaths())) for (const operation of Object.values(methods)) {
|
|
2174
|
+
if (!operation) continue;
|
|
2175
|
+
const node = parseOperation(parserOptions, operation);
|
|
2176
|
+
if (node) yield node;
|
|
2177
|
+
}
|
|
2178
|
+
})();
|
|
2179
|
+
} };
|
|
2180
|
+
return ast.createStreamInput(schemasIterable, operationsIterable, meta);
|
|
2181
|
+
}
|
|
1870
2182
|
//#endregion
|
|
1871
2183
|
//#region src/adapter.ts
|
|
1872
2184
|
/**
|
|
1873
|
-
*
|
|
2185
|
+
* Canonical adapter name for `@kubb/adapter-oas`. Used for driver lookups.
|
|
1874
2186
|
*/
|
|
1875
2187
|
const adapterOasName = "oas";
|
|
1876
2188
|
/**
|
|
1877
|
-
*
|
|
2189
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
|
|
2190
|
+
* file at `input.path`, validates it, resolves the base URL, and converts every
|
|
2191
|
+
* schema and operation into the universal AST that every downstream plugin
|
|
2192
|
+
* consumes.
|
|
1878
2193
|
*
|
|
1879
|
-
*
|
|
1880
|
-
*
|
|
2194
|
+
* Configure once on `defineConfig`. The adapter's choices (date representation,
|
|
2195
|
+
* integer width, server URL) apply to every plugin in the build.
|
|
1881
2196
|
*
|
|
1882
2197
|
* @example
|
|
1883
2198
|
* ```ts
|
|
@@ -1886,17 +2201,78 @@ const adapterOasName = "oas";
|
|
|
1886
2201
|
* import { pluginTs } from '@kubb/plugin-ts'
|
|
1887
2202
|
*
|
|
1888
2203
|
* export default defineConfig({
|
|
1889
|
-
*
|
|
1890
|
-
*
|
|
2204
|
+
* input: { path: './petStore.yaml' },
|
|
2205
|
+
* output: { path: './src/gen' },
|
|
2206
|
+
* adapter: adapterOas({
|
|
2207
|
+
* serverIndex: 0,
|
|
2208
|
+
* discriminator: 'inherit',
|
|
2209
|
+
* dateType: 'date',
|
|
2210
|
+
* }),
|
|
1891
2211
|
* plugins: [pluginTs()],
|
|
1892
2212
|
* })
|
|
1893
2213
|
* ```
|
|
1894
2214
|
*/
|
|
1895
2215
|
const adapterOas = createAdapter((options) => {
|
|
1896
2216
|
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;
|
|
2217
|
+
const parserOptions = {
|
|
2218
|
+
...DEFAULT_PARSER_OPTIONS,
|
|
2219
|
+
dateType,
|
|
2220
|
+
integerType,
|
|
2221
|
+
unknownType,
|
|
2222
|
+
emptySchemaType,
|
|
2223
|
+
enumSuffix
|
|
2224
|
+
};
|
|
1897
2225
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
1898
|
-
let parsedDocument;
|
|
1899
|
-
|
|
2226
|
+
let parsedDocument = null;
|
|
2227
|
+
const ensureDocument = once(async (source) => {
|
|
2228
|
+
const fresh = await parseFromConfig(source);
|
|
2229
|
+
if (validate) await validateDocument(fresh);
|
|
2230
|
+
parsedDocument = fresh;
|
|
2231
|
+
return fresh;
|
|
2232
|
+
});
|
|
2233
|
+
const ensureSchemas = once(async (document) => {
|
|
2234
|
+
const result = getSchemas(document, { contentType });
|
|
2235
|
+
nameMapping = result.nameMapping;
|
|
2236
|
+
return result.schemas;
|
|
2237
|
+
});
|
|
2238
|
+
const ensureBaseOas = once((document) => new BaseOas(document));
|
|
2239
|
+
const ensureSchemaParser = once((document) => createSchemaParser({
|
|
2240
|
+
document,
|
|
2241
|
+
contentType
|
|
2242
|
+
}));
|
|
2243
|
+
const ensurePreScan = once((schemas, parseSchema) => preScan({
|
|
2244
|
+
schemas,
|
|
2245
|
+
parseSchema,
|
|
2246
|
+
parserOptions,
|
|
2247
|
+
discriminator
|
|
2248
|
+
}));
|
|
2249
|
+
async function createStream(source) {
|
|
2250
|
+
const document = await ensureDocument(source);
|
|
2251
|
+
const schemas = await ensureSchemas(document);
|
|
2252
|
+
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2253
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap } = ensurePreScan(schemas, parseSchema);
|
|
2254
|
+
return createInputStream({
|
|
2255
|
+
schemas,
|
|
2256
|
+
parseSchema,
|
|
2257
|
+
parseOperation,
|
|
2258
|
+
baseOas: ensureBaseOas(document),
|
|
2259
|
+
parserOptions,
|
|
2260
|
+
refAliasMap,
|
|
2261
|
+
discriminatorChildMap,
|
|
2262
|
+
meta: {
|
|
2263
|
+
title: document.info?.title,
|
|
2264
|
+
description: document.info?.description,
|
|
2265
|
+
version: document.info?.version,
|
|
2266
|
+
baseURL: resolveBaseUrl({
|
|
2267
|
+
document,
|
|
2268
|
+
serverIndex,
|
|
2269
|
+
serverVariables
|
|
2270
|
+
}),
|
|
2271
|
+
circularNames,
|
|
2272
|
+
enumNames
|
|
2273
|
+
}
|
|
2274
|
+
});
|
|
2275
|
+
}
|
|
1900
2276
|
return {
|
|
1901
2277
|
name: "oas",
|
|
1902
2278
|
get options() {
|
|
@@ -1917,8 +2293,8 @@ const adapterOas = createAdapter((options) => {
|
|
|
1917
2293
|
get document() {
|
|
1918
2294
|
return parsedDocument;
|
|
1919
2295
|
},
|
|
1920
|
-
|
|
1921
|
-
|
|
2296
|
+
async validate(input, options) {
|
|
2297
|
+
await validateDocument(await parseDocument(input), options);
|
|
1922
2298
|
},
|
|
1923
2299
|
getImports(node, resolve) {
|
|
1924
2300
|
return ast.collectImports({
|
|
@@ -1926,7 +2302,7 @@ const adapterOas = createAdapter((options) => {
|
|
|
1926
2302
|
nameMapping,
|
|
1927
2303
|
resolve: (schemaName) => {
|
|
1928
2304
|
const result = resolve(schemaName);
|
|
1929
|
-
if (!result) return;
|
|
2305
|
+
if (!result) return null;
|
|
1930
2306
|
return ast.createImport({
|
|
1931
2307
|
name: [result.name],
|
|
1932
2308
|
path: result.path
|
|
@@ -1935,32 +2311,20 @@ const adapterOas = createAdapter((options) => {
|
|
|
1935
2311
|
});
|
|
1936
2312
|
},
|
|
1937
2313
|
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
|
-
}
|
|
2314
|
+
const streamNode = await createStream(source);
|
|
2315
|
+
const collect = async (iter) => {
|
|
2316
|
+
const out = [];
|
|
2317
|
+
for await (const item of iter) out.push(item);
|
|
2318
|
+
return out;
|
|
2319
|
+
};
|
|
2320
|
+
const [schemas, operations] = await Promise.all([collect(streamNode.schemas), collect(streamNode.operations)]);
|
|
2321
|
+
return ast.createInput({
|
|
2322
|
+
schemas,
|
|
2323
|
+
operations,
|
|
2324
|
+
meta: streamNode.meta
|
|
1961
2325
|
});
|
|
1962
|
-
|
|
1963
|
-
|
|
2326
|
+
},
|
|
2327
|
+
stream: createStream
|
|
1964
2328
|
};
|
|
1965
2329
|
});
|
|
1966
2330
|
//#endregion
|
|
@@ -1976,6 +2340,6 @@ const adapterOas = createAdapter((options) => {
|
|
|
1976
2340
|
*/
|
|
1977
2341
|
const HttpMethods = Object.fromEntries(Object.entries(ast.httpMethods).map(([lower, upper]) => [upper, lower]));
|
|
1978
2342
|
//#endregion
|
|
1979
|
-
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments
|
|
2343
|
+
export { HttpMethods, adapterOas, adapterOasName, mergeDocuments };
|
|
1980
2344
|
|
|
1981
2345
|
//# sourceMappingURL=index.js.map
|