@kubb/adapter-oas 5.0.0-beta.36 → 5.0.0-beta.38
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/dist/index.cjs +353 -201
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +353 -201
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/adapter.ts +73 -29
- package/src/constants.ts +8 -0
- package/src/dialect.ts +3 -3
- package/src/factory.ts +37 -8
- package/src/parser.ts +11 -16
- package/src/refs.ts +15 -1
- package/src/resolvers.ts +27 -10
- package/src/schemaDiagnostics.ts +76 -0
- package/src/stream.ts +5 -3
- package/src/types.ts +5 -5
package/dist/index.cjs
CHANGED
|
@@ -21,11 +21,12 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
21
21
|
enumerable: true
|
|
22
22
|
}) : target, mod));
|
|
23
23
|
//#endregion
|
|
24
|
-
let node_path = require("node:path");
|
|
25
|
-
node_path = __toESM(node_path, 1);
|
|
26
24
|
let _kubb_core = require("@kubb/core");
|
|
27
25
|
let oas = require("oas");
|
|
28
26
|
oas = __toESM(oas, 1);
|
|
27
|
+
let node_path = require("node:path");
|
|
28
|
+
node_path = __toESM(node_path, 1);
|
|
29
|
+
let node_fs_promises = require("node:fs/promises");
|
|
29
30
|
let _redocly_openapi_core = require("@redocly/openapi-core");
|
|
30
31
|
let oas_normalize = require("oas-normalize");
|
|
31
32
|
oas_normalize = __toESM(oas_normalize, 1);
|
|
@@ -33,6 +34,138 @@ let swagger2openapi = require("swagger2openapi");
|
|
|
33
34
|
swagger2openapi = __toESM(swagger2openapi, 1);
|
|
34
35
|
let oas_types = require("oas/types");
|
|
35
36
|
let oas_utils = require("oas/utils");
|
|
37
|
+
//#region src/constants.ts
|
|
38
|
+
/**
|
|
39
|
+
* Default parser options applied when no explicit options are provided.
|
|
40
|
+
*
|
|
41
|
+
* @example
|
|
42
|
+
* ```ts
|
|
43
|
+
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
44
|
+
*
|
|
45
|
+
* const parser = createOasParser(oas)
|
|
46
|
+
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
const DEFAULT_PARSER_OPTIONS = {
|
|
50
|
+
dateType: "string",
|
|
51
|
+
integerType: "bigint",
|
|
52
|
+
unknownType: "any",
|
|
53
|
+
emptySchemaType: "any",
|
|
54
|
+
enumSuffix: "enum"
|
|
55
|
+
};
|
|
56
|
+
/**
|
|
57
|
+
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
58
|
+
*
|
|
59
|
+
* Used when building or parsing `$ref` strings.
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```ts
|
|
63
|
+
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
64
|
+
* ```
|
|
65
|
+
*/
|
|
66
|
+
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
67
|
+
/**
|
|
68
|
+
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
69
|
+
*/
|
|
70
|
+
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
71
|
+
/**
|
|
72
|
+
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
73
|
+
*/
|
|
74
|
+
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
75
|
+
/**
|
|
76
|
+
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
77
|
+
*/
|
|
78
|
+
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
79
|
+
/**
|
|
80
|
+
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
81
|
+
*
|
|
82
|
+
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
83
|
+
* intersection member rather than being merged into the parent.
|
|
84
|
+
*
|
|
85
|
+
* @example
|
|
86
|
+
* ```ts
|
|
87
|
+
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
88
|
+
*
|
|
89
|
+
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
90
|
+
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
const structuralKeys = new Set([
|
|
94
|
+
"properties",
|
|
95
|
+
"items",
|
|
96
|
+
"additionalProperties",
|
|
97
|
+
"oneOf",
|
|
98
|
+
"anyOf",
|
|
99
|
+
"allOf",
|
|
100
|
+
"not"
|
|
101
|
+
]);
|
|
102
|
+
/**
|
|
103
|
+
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
104
|
+
*
|
|
105
|
+
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
106
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
107
|
+
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
108
|
+
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```ts
|
|
112
|
+
* import { formatMap } from '@kubb/adapter-oas'
|
|
113
|
+
*
|
|
114
|
+
* formatMap['uuid'] // 'uuid'
|
|
115
|
+
* formatMap['binary'] // 'blob'
|
|
116
|
+
* formatMap['float'] // 'number'
|
|
117
|
+
* ```
|
|
118
|
+
*/
|
|
119
|
+
/**
|
|
120
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
121
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
122
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
123
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
124
|
+
*/
|
|
125
|
+
const specialCasedFormats = new Set([
|
|
126
|
+
"int64",
|
|
127
|
+
"date-time",
|
|
128
|
+
"date",
|
|
129
|
+
"time"
|
|
130
|
+
]);
|
|
131
|
+
const formatMap = {
|
|
132
|
+
uuid: "uuid",
|
|
133
|
+
email: "email",
|
|
134
|
+
"idn-email": "email",
|
|
135
|
+
uri: "url",
|
|
136
|
+
"uri-reference": "url",
|
|
137
|
+
url: "url",
|
|
138
|
+
ipv4: "ipv4",
|
|
139
|
+
ipv6: "ipv6",
|
|
140
|
+
hostname: "url",
|
|
141
|
+
"idn-hostname": "url",
|
|
142
|
+
binary: "blob",
|
|
143
|
+
byte: "blob",
|
|
144
|
+
int32: "integer",
|
|
145
|
+
float: "number",
|
|
146
|
+
double: "number"
|
|
147
|
+
};
|
|
148
|
+
/**
|
|
149
|
+
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
150
|
+
*
|
|
151
|
+
* @example
|
|
152
|
+
* ```ts
|
|
153
|
+
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
154
|
+
*
|
|
155
|
+
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
156
|
+
* ```
|
|
157
|
+
*/
|
|
158
|
+
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
159
|
+
/**
|
|
160
|
+
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
161
|
+
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
162
|
+
*/
|
|
163
|
+
const typeOptionMap = new Map([
|
|
164
|
+
["any", _kubb_core.ast.schemaTypes.any],
|
|
165
|
+
["unknown", _kubb_core.ast.schemaTypes.unknown],
|
|
166
|
+
["void", _kubb_core.ast.schemaTypes.void]
|
|
167
|
+
]);
|
|
168
|
+
//#endregion
|
|
36
169
|
//#region ../../internals/utils/src/casing.ts
|
|
37
170
|
/**
|
|
38
171
|
* Shared implementation for camelCase and PascalCase conversion.
|
|
@@ -97,6 +230,23 @@ function pascalCase(text, { isFile, prefix = "", suffix = "" } = {}) {
|
|
|
97
230
|
return toCamelOrPascal(`${prefix} ${text} ${suffix}`, true);
|
|
98
231
|
}
|
|
99
232
|
//#endregion
|
|
233
|
+
//#region ../../internals/utils/src/fs.ts
|
|
234
|
+
/**
|
|
235
|
+
* Resolves to `true` when the file or directory at `path` exists.
|
|
236
|
+
* Uses `Bun.file().exists()` when running under Bun, `fs.access` otherwise.
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```ts
|
|
240
|
+
* if (await exists('./kubb.config.ts')) {
|
|
241
|
+
* const content = await read('./kubb.config.ts')
|
|
242
|
+
* }
|
|
243
|
+
* ```
|
|
244
|
+
*/
|
|
245
|
+
async function exists(path) {
|
|
246
|
+
if (typeof Bun !== "undefined") return Bun.file(path).exists();
|
|
247
|
+
return (0, node_fs_promises.access)(path).then(() => true, () => false);
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
100
250
|
//#region ../../internals/utils/src/object.ts
|
|
101
251
|
/**
|
|
102
252
|
* Returns `true` when `value` is a plain (non-null, non-array) object.
|
|
@@ -131,30 +281,6 @@ function mergeDeep(target, source) {
|
|
|
131
281
|
return result;
|
|
132
282
|
}
|
|
133
283
|
//#endregion
|
|
134
|
-
//#region ../../internals/utils/src/promise.ts
|
|
135
|
-
/**
|
|
136
|
-
* Returns a wrapper that caches the result of the first invocation and replays
|
|
137
|
-
* it for every subsequent call, ignoring later arguments.
|
|
138
|
-
*
|
|
139
|
-
* Works for sync and async factories — for async, the cached value is the
|
|
140
|
-
* promise itself, so concurrent callers share one in-flight execution and
|
|
141
|
-
* cannot race each other.
|
|
142
|
-
*
|
|
143
|
-
* @example
|
|
144
|
-
* ```ts
|
|
145
|
-
* const loadDocument = once(async (path: string) => parse(await readFile(path)))
|
|
146
|
-
* const a = loadDocument('./a.yaml') // parses
|
|
147
|
-
* const b = loadDocument('./b.yaml') // returns the cached promise from the first call
|
|
148
|
-
* ```
|
|
149
|
-
*/
|
|
150
|
-
function once(factory) {
|
|
151
|
-
let cache;
|
|
152
|
-
return (...args) => {
|
|
153
|
-
if (!cache) cache = { value: factory(...args) };
|
|
154
|
-
return cache.value;
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
//#endregion
|
|
158
284
|
//#region ../../internals/utils/src/reserved.ts
|
|
159
285
|
/**
|
|
160
286
|
* JavaScript and Java reserved words.
|
|
@@ -405,126 +531,6 @@ var URLPath = class {
|
|
|
405
531
|
}
|
|
406
532
|
};
|
|
407
533
|
//#endregion
|
|
408
|
-
//#region src/constants.ts
|
|
409
|
-
/**
|
|
410
|
-
* Default parser options applied when no explicit options are provided.
|
|
411
|
-
*
|
|
412
|
-
* @example
|
|
413
|
-
* ```ts
|
|
414
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
415
|
-
*
|
|
416
|
-
* const parser = createOasParser(oas)
|
|
417
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
418
|
-
* ```
|
|
419
|
-
*/
|
|
420
|
-
const DEFAULT_PARSER_OPTIONS = {
|
|
421
|
-
dateType: "string",
|
|
422
|
-
integerType: "bigint",
|
|
423
|
-
unknownType: "any",
|
|
424
|
-
emptySchemaType: "any",
|
|
425
|
-
enumSuffix: "enum"
|
|
426
|
-
};
|
|
427
|
-
/**
|
|
428
|
-
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
429
|
-
*
|
|
430
|
-
* Used when building or parsing `$ref` strings.
|
|
431
|
-
*
|
|
432
|
-
* @example
|
|
433
|
-
* ```ts
|
|
434
|
-
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
435
|
-
* ```
|
|
436
|
-
*/
|
|
437
|
-
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
438
|
-
/**
|
|
439
|
-
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
440
|
-
*/
|
|
441
|
-
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
442
|
-
/**
|
|
443
|
-
* Fallback `info.title` placed in the stub document when merging multiple API files.
|
|
444
|
-
*/
|
|
445
|
-
const MERGE_DEFAULT_TITLE = "Merged API";
|
|
446
|
-
/**
|
|
447
|
-
* Fallback `info.version` placed in the stub document when merging multiple API files.
|
|
448
|
-
*/
|
|
449
|
-
const MERGE_DEFAULT_VERSION = "1.0.0";
|
|
450
|
-
/**
|
|
451
|
-
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
452
|
-
*
|
|
453
|
-
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
454
|
-
* intersection member rather than being merged into the parent.
|
|
455
|
-
*
|
|
456
|
-
* @example
|
|
457
|
-
* ```ts
|
|
458
|
-
* import { structuralKeys } from '@kubb/adapter-oas'
|
|
459
|
-
*
|
|
460
|
-
* const isStructural = Object.keys(fragment).some((key) => structuralKeys.has(key))
|
|
461
|
-
* // true when fragment has e.g. 'properties' or 'oneOf'
|
|
462
|
-
* ```
|
|
463
|
-
*/
|
|
464
|
-
const structuralKeys = new Set([
|
|
465
|
-
"properties",
|
|
466
|
-
"items",
|
|
467
|
-
"additionalProperties",
|
|
468
|
-
"oneOf",
|
|
469
|
-
"anyOf",
|
|
470
|
-
"allOf",
|
|
471
|
-
"not"
|
|
472
|
-
]);
|
|
473
|
-
/**
|
|
474
|
-
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
475
|
-
*
|
|
476
|
-
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
477
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled separately
|
|
478
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types; `hostname` and
|
|
479
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
480
|
-
*
|
|
481
|
-
* @example
|
|
482
|
-
* ```ts
|
|
483
|
-
* import { formatMap } from '@kubb/adapter-oas'
|
|
484
|
-
*
|
|
485
|
-
* formatMap['uuid'] // 'uuid'
|
|
486
|
-
* formatMap['binary'] // 'blob'
|
|
487
|
-
* formatMap['float'] // 'number'
|
|
488
|
-
* ```
|
|
489
|
-
*/
|
|
490
|
-
const formatMap = {
|
|
491
|
-
uuid: "uuid",
|
|
492
|
-
email: "email",
|
|
493
|
-
"idn-email": "email",
|
|
494
|
-
uri: "url",
|
|
495
|
-
"uri-reference": "url",
|
|
496
|
-
url: "url",
|
|
497
|
-
ipv4: "ipv4",
|
|
498
|
-
ipv6: "ipv6",
|
|
499
|
-
hostname: "url",
|
|
500
|
-
"idn-hostname": "url",
|
|
501
|
-
binary: "blob",
|
|
502
|
-
byte: "blob",
|
|
503
|
-
int32: "integer",
|
|
504
|
-
float: "number",
|
|
505
|
-
double: "number"
|
|
506
|
-
};
|
|
507
|
-
/**
|
|
508
|
-
* Vendor extension keys that attach human-readable labels to enum values, checked in priority order.
|
|
509
|
-
*
|
|
510
|
-
* @example
|
|
511
|
-
* ```ts
|
|
512
|
-
* import { enumExtensionKeys } from '@kubb/adapter-oas'
|
|
513
|
-
*
|
|
514
|
-
* const key = enumExtensionKeys.find((k) => k in schema) // 'x-enumNames' | 'x-enum-varnames' | undefined
|
|
515
|
-
* ```
|
|
516
|
-
*/
|
|
517
|
-
const enumExtensionKeys = ["x-enumNames", "x-enum-varnames"];
|
|
518
|
-
/**
|
|
519
|
-
* Maps `'any' | 'unknown' | 'void'` option strings to their `ScalarSchemaType` constant.
|
|
520
|
-
* Replaces a plain object lookup with a `Map` for explicit key membership testing via `.has()`.
|
|
521
|
-
*/
|
|
522
|
-
const typeOptionMap = new Map([
|
|
523
|
-
["any", _kubb_core.ast.schemaTypes.any],
|
|
524
|
-
["unknown", _kubb_core.ast.schemaTypes.unknown],
|
|
525
|
-
["void", _kubb_core.ast.schemaTypes.void]
|
|
526
|
-
]);
|
|
527
|
-
//#endregion
|
|
528
534
|
//#region src/guards.ts
|
|
529
535
|
/**
|
|
530
536
|
* Returns `true` when `doc` is a Swagger 2.0 document (no `openapi` key).
|
|
@@ -634,7 +640,13 @@ async function mergeDocuments(pathOrApi) {
|
|
|
634
640
|
enablePaths: false,
|
|
635
641
|
canBundle: false
|
|
636
642
|
})));
|
|
637
|
-
if (documents.length === 0) throw new
|
|
643
|
+
if (documents.length === 0) throw new _kubb_core.DiagnosticError({
|
|
644
|
+
code: _kubb_core.diagnosticCode.inputRequired,
|
|
645
|
+
severity: "error",
|
|
646
|
+
message: "No OAS documents were provided for merging.",
|
|
647
|
+
help: "Pass at least one path or document to `input.path`.",
|
|
648
|
+
location: { kind: "config" }
|
|
649
|
+
});
|
|
638
650
|
const seed = {
|
|
639
651
|
openapi: MERGE_OPENAPI_VERSION,
|
|
640
652
|
info: {
|
|
@@ -650,9 +662,9 @@ async function mergeDocuments(pathOrApi) {
|
|
|
650
662
|
* Creates a `Document` from an `AdapterSource`.
|
|
651
663
|
*
|
|
652
664
|
* Handles all three source types:
|
|
653
|
-
* - `{ type: 'path' }`
|
|
654
|
-
* - `{ type: 'paths' }`
|
|
655
|
-
* - `{ type: 'data' }`
|
|
665
|
+
* - `{ type: 'path' }` resolves and bundles a local file path or remote URL.
|
|
666
|
+
* - `{ type: 'paths' }` merges multiple file paths into a single document.
|
|
667
|
+
* - `{ type: 'data' }` parses an inline string (YAML/JSON) or raw object.
|
|
656
668
|
*
|
|
657
669
|
* @example
|
|
658
670
|
* ```ts
|
|
@@ -660,14 +672,31 @@ async function mergeDocuments(pathOrApi) {
|
|
|
660
672
|
* const document = await parseFromConfig({ type: 'data', data: '{"openapi":"3.0.0",...}' })
|
|
661
673
|
* ```
|
|
662
674
|
*/
|
|
663
|
-
function parseFromConfig(source) {
|
|
675
|
+
async function parseFromConfig(source) {
|
|
664
676
|
if (source.type === "data") {
|
|
665
677
|
if (typeof source.data === "object") return parseDocument(structuredClone(source.data));
|
|
666
678
|
return parseDocument(source.data, { canBundle: false });
|
|
667
679
|
}
|
|
668
680
|
if (source.type === "paths") return mergeDocuments(source.paths);
|
|
669
681
|
if (new URLPath(source.path).isURL) return parseDocument(source.path);
|
|
670
|
-
|
|
682
|
+
const resolved = node_path.default.resolve(node_path.default.dirname(source.path), source.path);
|
|
683
|
+
await assertInputExists(resolved);
|
|
684
|
+
return parseDocument(resolved);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Throws a coded `KUBB_INPUT_NOT_FOUND` diagnostic when a local input path does not exist.
|
|
688
|
+
* URLs are skipped, and a malformed but readable file is left for `parseDocument` to surface
|
|
689
|
+
* its parse error instead.
|
|
690
|
+
*/
|
|
691
|
+
async function assertInputExists(input) {
|
|
692
|
+
if (new URLPath(input).isURL) return;
|
|
693
|
+
if (!await exists(input)) throw new _kubb_core.DiagnosticError({
|
|
694
|
+
code: _kubb_core.diagnosticCode.inputNotFound,
|
|
695
|
+
severity: "error",
|
|
696
|
+
message: `Cannot read the file set in \`input.path\` (or via \`kubb generate PATH\`): ${input}`,
|
|
697
|
+
help: "Check that the path exists and is readable, then set it in `input.path` or pass it as `kubb generate PATH`.",
|
|
698
|
+
location: { kind: "config" }
|
|
699
|
+
});
|
|
671
700
|
}
|
|
672
701
|
/**
|
|
673
702
|
* Validates an OpenAPI document using `oas-normalize` with colorized error output.
|
|
@@ -714,7 +743,21 @@ function resolveRef(document, $ref) {
|
|
|
714
743
|
}
|
|
715
744
|
if (docCache.has($ref)) return docCache.get($ref);
|
|
716
745
|
const current = $ref.split("/").filter(Boolean).reduce((obj, key) => obj?.[key], document);
|
|
717
|
-
if (!current)
|
|
746
|
+
if (!current) {
|
|
747
|
+
const diagnostic = {
|
|
748
|
+
code: _kubb_core.diagnosticCode.refNotFound,
|
|
749
|
+
severity: "error",
|
|
750
|
+
message: `Could not find a definition for ${origRef}.`,
|
|
751
|
+
help: "Add the schema under `components.schemas`, or fix the `$ref`. Run `kubb validate` to check the spec.",
|
|
752
|
+
location: {
|
|
753
|
+
kind: "schema",
|
|
754
|
+
pointer: origRef,
|
|
755
|
+
ref: origRef
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
if (!_kubb_core.Diagnostics.report(diagnostic)) throw new _kubb_core.DiagnosticError(diagnostic);
|
|
759
|
+
return null;
|
|
760
|
+
}
|
|
718
761
|
docCache.set($ref, current);
|
|
719
762
|
return current;
|
|
720
763
|
}
|
|
@@ -741,13 +784,13 @@ function dereferenceWithRef(document, schema) {
|
|
|
741
784
|
//#endregion
|
|
742
785
|
//#region src/dialect.ts
|
|
743
786
|
/**
|
|
744
|
-
* The OpenAPI / Swagger dialect
|
|
787
|
+
* The OpenAPI / Swagger dialect, the default used by `@kubb/adapter-oas`.
|
|
745
788
|
*
|
|
746
789
|
* Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
|
|
747
790
|
* decisions that differ between specs (nullability, `$ref`, discriminator, binary,
|
|
748
791
|
* ref resolution) so the converter pipeline and dispatch rules stay shared. A
|
|
749
|
-
* future adapter (e.g. AsyncAPI) ships its own dialect
|
|
750
|
-
* nullability, no discriminator object, binary via `contentEncoding`
|
|
792
|
+
* future adapter (e.g. AsyncAPI) ships its own dialect, `type: ['null', …]`
|
|
793
|
+
* nullability, no discriminator object, binary via `contentEncoding` and reuses
|
|
751
794
|
* the rest unchanged.
|
|
752
795
|
*
|
|
753
796
|
* Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
|
|
@@ -789,7 +832,16 @@ function resolveServerUrl(server, overrides) {
|
|
|
789
832
|
for (const [key, variable] of Object.entries(server.variables)) {
|
|
790
833
|
const value = overrides?.[key] ?? (variable.default != null ? String(variable.default) : void 0);
|
|
791
834
|
if (value === void 0) continue;
|
|
792
|
-
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new
|
|
835
|
+
if (variable.enum?.length && !variable.enum.some((e) => String(e) === value)) throw new _kubb_core.DiagnosticError({
|
|
836
|
+
code: _kubb_core.diagnosticCode.invalidServerVariable,
|
|
837
|
+
severity: "error",
|
|
838
|
+
message: `Invalid server variable value '${value}' for '${key}' when resolving ${server.url}. Valid values are: ${variable.enum.join(", ")}.`,
|
|
839
|
+
help: `Use one of the allowed enum values, or drop the enum on the '${key}' server variable.`,
|
|
840
|
+
location: {
|
|
841
|
+
kind: "document",
|
|
842
|
+
pointer: "#/servers"
|
|
843
|
+
}
|
|
844
|
+
});
|
|
793
845
|
url = url.replaceAll(`{${key}}`, value);
|
|
794
846
|
}
|
|
795
847
|
return url;
|
|
@@ -802,6 +854,15 @@ function getSchemaType(format) {
|
|
|
802
854
|
return formatMap[format] ?? null;
|
|
803
855
|
}
|
|
804
856
|
/**
|
|
857
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry and the
|
|
858
|
+
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to the
|
|
859
|
+
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
860
|
+
* diagnostic in step with the parser as `formatMap` grows.
|
|
861
|
+
*/
|
|
862
|
+
function isHandledFormat(format) {
|
|
863
|
+
return getSchemaType(format) !== null || specialCasedFormats.has(format);
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
805
866
|
* Converts an OAS primitive type string to its `PrimitiveSchemaType` equivalent.
|
|
806
867
|
* Numeric types (`number`, `integer`, `bigint`) pass through unchanged. `boolean` maps to `'boolean'`. Everything else becomes `'string'`.
|
|
807
868
|
*/
|
|
@@ -898,7 +959,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
898
959
|
/**
|
|
899
960
|
* Flattens a keyword-only `allOf` into its parent schema.
|
|
900
961
|
*
|
|
901
|
-
* Only flattens when every member is a plain fragment
|
|
962
|
+
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
902
963
|
* (see `structuralKeys`). Outer schema values take precedence over fragment values.
|
|
903
964
|
* Returns `null` for a `null` input, and the original schema unchanged when flattening is unsafe.
|
|
904
965
|
*
|
|
@@ -908,7 +969,7 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
908
969
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
909
970
|
*
|
|
910
971
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
911
|
-
* // returned unchanged
|
|
972
|
+
* // returned unchanged, contains a $ref
|
|
912
973
|
* ```
|
|
913
974
|
*/
|
|
914
975
|
/**
|
|
@@ -934,7 +995,7 @@ function flattenSchema(schema) {
|
|
|
934
995
|
/**
|
|
935
996
|
* Extracts the inline schema from a media-type `content` map.
|
|
936
997
|
*
|
|
937
|
-
* Prefers `preferredContentType` when given
|
|
998
|
+
* Prefers `preferredContentType` when given, otherwise uses the first key in the map.
|
|
938
999
|
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
939
1000
|
*
|
|
940
1001
|
* @example
|
|
@@ -1126,9 +1187,9 @@ function buildSchemaNode(schema, name, nullable, defaultValue) {
|
|
|
1126
1187
|
/**
|
|
1127
1188
|
* Returns all request body content type keys for an operation.
|
|
1128
1189
|
*
|
|
1129
|
-
* The requestBody is dereferenced
|
|
1130
|
-
*
|
|
1131
|
-
*
|
|
1190
|
+
* The requestBody is dereferenced in place when it is a `$ref` (the same mutation that
|
|
1191
|
+
* `getRequestSchema` already performs), so the returned list accurately reflects the
|
|
1192
|
+
* available content types even for referenced bodies.
|
|
1132
1193
|
*
|
|
1133
1194
|
* @example
|
|
1134
1195
|
* ```ts
|
|
@@ -1145,7 +1206,7 @@ function getRequestBodyContentTypes(document, operation) {
|
|
|
1145
1206
|
/**
|
|
1146
1207
|
* Returns all response content type keys for an operation at a given status code.
|
|
1147
1208
|
*
|
|
1148
|
-
* Response `$ref`s are resolved in
|
|
1209
|
+
* Response `$ref`s are resolved in place first (the same mutation `getResponseSchema` performs),
|
|
1149
1210
|
* so the returned list reflects the available content types even for referenced responses.
|
|
1150
1211
|
*
|
|
1151
1212
|
* @example
|
|
@@ -1205,16 +1266,12 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1205
1266
|
*/
|
|
1206
1267
|
const resolvingRefs = /* @__PURE__ */ new Set();
|
|
1207
1268
|
/**
|
|
1208
|
-
* Cache of
|
|
1269
|
+
* Cache of `$ref` schemas already resolved in this parser instance, keyed by ref path.
|
|
1209
1270
|
*
|
|
1210
|
-
* Without
|
|
1211
|
-
*
|
|
1212
|
-
*
|
|
1213
|
-
*
|
|
1214
|
-
* each triggering a fresh recursive expansion of its entire sub-tree.
|
|
1215
|
-
*
|
|
1216
|
-
* Memoizing by `$ref` path reduces the overall work from O(2^depth) to O(N)
|
|
1217
|
-
* where N is the number of unique schema names.
|
|
1271
|
+
* Without it, a shared schema (e.g. `customer`) is re-expanded for every `$ref` that points at
|
|
1272
|
+
* it. In cross-referenced specs like Stripe (~1400 schemas) that becomes exponential blowup,
|
|
1273
|
+
* since one schema can be referenced from dozens of parents, each re-walking its whole subtree.
|
|
1274
|
+
* Memoizing by ref path drops the work from O(2^depth) to O(N) unique schema names.
|
|
1218
1275
|
*/
|
|
1219
1276
|
const resolvedRefCache = /* @__PURE__ */ new Map();
|
|
1220
1277
|
/**
|
|
@@ -1752,7 +1809,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1752
1809
|
/**
|
|
1753
1810
|
* Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
|
|
1754
1811
|
* `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
|
|
1755
|
-
* `type`. The first matching rule that produces a node wins
|
|
1812
|
+
* `type`. The first matching rule that produces a node wins. See {@link SchemaRule} for the
|
|
1756
1813
|
* match/convert/fall-through contract.
|
|
1757
1814
|
*/
|
|
1758
1815
|
const schemaRules = [
|
|
@@ -2097,6 +2154,61 @@ function patchDiscriminatorNode(node, entry) {
|
|
|
2097
2154
|
};
|
|
2098
2155
|
}
|
|
2099
2156
|
//#endregion
|
|
2157
|
+
//#region src/schemaDiagnostics.ts
|
|
2158
|
+
/**
|
|
2159
|
+
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one
|
|
2160
|
+
* top-level schema. Walks the node the parser produced during `preScan`, threading the RFC 6901
|
|
2161
|
+
* pointer as it descends so a nested field reports against its full path
|
|
2162
|
+
* (`#/components/schemas/Pet/properties/owner/properties/name`). Refs are not followed, so the
|
|
2163
|
+
* resolved schema is reported under its own walk. Reports land in the active build run, are a
|
|
2164
|
+
* no-op outside one, and repeats are deduped by the build.
|
|
2165
|
+
*/
|
|
2166
|
+
function reportSchemaDiagnostics({ node, name }) {
|
|
2167
|
+
visit(node, `#/components/schemas/${escapePointerToken(name)}`);
|
|
2168
|
+
}
|
|
2169
|
+
/**
|
|
2170
|
+
* Escapes a single JSON pointer reference token per RFC 6901 (`~` → `~0`, `/` → `~1`), so a
|
|
2171
|
+
* property name with those characters maps to a distinct pointer instead of colliding in the dedupe.
|
|
2172
|
+
*/
|
|
2173
|
+
function escapePointerToken(token) {
|
|
2174
|
+
return token.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
2175
|
+
}
|
|
2176
|
+
function visit(node, pointer) {
|
|
2177
|
+
if (node.deprecated) _kubb_core.Diagnostics.report({
|
|
2178
|
+
code: _kubb_core.diagnosticCode.deprecated,
|
|
2179
|
+
severity: "info",
|
|
2180
|
+
message: "This schema is marked as deprecated.",
|
|
2181
|
+
location: {
|
|
2182
|
+
kind: "schema",
|
|
2183
|
+
pointer
|
|
2184
|
+
}
|
|
2185
|
+
});
|
|
2186
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_core.Diagnostics.report({
|
|
2187
|
+
code: _kubb_core.diagnosticCode.unsupportedFormat,
|
|
2188
|
+
severity: "warning",
|
|
2189
|
+
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
2190
|
+
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
2191
|
+
location: {
|
|
2192
|
+
kind: "schema",
|
|
2193
|
+
pointer
|
|
2194
|
+
}
|
|
2195
|
+
});
|
|
2196
|
+
if (node.type === "object") {
|
|
2197
|
+
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
2198
|
+
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
2199
|
+
return;
|
|
2200
|
+
}
|
|
2201
|
+
if (node.type === "array") {
|
|
2202
|
+
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
2203
|
+
return;
|
|
2204
|
+
}
|
|
2205
|
+
if (node.type === "tuple") {
|
|
2206
|
+
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
2207
|
+
return;
|
|
2208
|
+
}
|
|
2209
|
+
if (node.type === "union" || node.type === "intersection") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2210
|
+
}
|
|
2211
|
+
//#endregion
|
|
2100
2212
|
//#region src/stream.ts
|
|
2101
2213
|
/**
|
|
2102
2214
|
* Builds the deduplication plan from the already-parsed top-level schema nodes plus a
|
|
@@ -2156,7 +2268,7 @@ function resolveBaseUrl({ document, serverIndex, serverVariables }) {
|
|
|
2156
2268
|
* After this call, only `refAliasMap` and `discriminatorChildMap` stay alive in the adapter closure.
|
|
2157
2269
|
* Both are proportional to the number of aliases or discriminator parents, not total schema count.
|
|
2158
2270
|
*
|
|
2159
|
-
* Each schema is parsed again during the streaming pass
|
|
2271
|
+
* Each schema is parsed again during the streaming pass. This is intentional.
|
|
2160
2272
|
* Holding the parsed nodes in memory here would defeat the streaming memory benefit.
|
|
2161
2273
|
*
|
|
2162
2274
|
* @example
|
|
@@ -2180,6 +2292,10 @@ function preScan({ schemas, parseSchema, parseOperation, baseOas, parserOptions,
|
|
|
2180
2292
|
name
|
|
2181
2293
|
}, parserOptions);
|
|
2182
2294
|
allNodes.push(node);
|
|
2295
|
+
reportSchemaDiagnostics({
|
|
2296
|
+
node,
|
|
2297
|
+
name
|
|
2298
|
+
});
|
|
2183
2299
|
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2184
2300
|
if (_kubb_core.ast.narrowSchema(node, _kubb_core.ast.schemaTypes.enum) && node.name) enumNames.push(node.name);
|
|
2185
2301
|
if (discriminator === "inherit" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
@@ -2320,37 +2436,72 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
2320
2436
|
};
|
|
2321
2437
|
let nameMapping = /* @__PURE__ */ new Map();
|
|
2322
2438
|
let parsedDocument = null;
|
|
2323
|
-
const
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
|
|
2342
|
-
|
|
2343
|
-
|
|
2344
|
-
|
|
2345
|
-
|
|
2346
|
-
|
|
2347
|
-
|
|
2439
|
+
const documentCache = /* @__PURE__ */ new WeakMap();
|
|
2440
|
+
const schemasCache = /* @__PURE__ */ new WeakMap();
|
|
2441
|
+
const baseOasCache = /* @__PURE__ */ new WeakMap();
|
|
2442
|
+
const schemaParserCache = /* @__PURE__ */ new WeakMap();
|
|
2443
|
+
const preScanCache = /* @__PURE__ */ new WeakMap();
|
|
2444
|
+
function ensureDocument(source) {
|
|
2445
|
+
const cached = documentCache.get(source);
|
|
2446
|
+
if (cached) return cached;
|
|
2447
|
+
const promise = (async () => {
|
|
2448
|
+
const fresh = await parseFromConfig(source);
|
|
2449
|
+
if (validate) await validateDocument(fresh);
|
|
2450
|
+
parsedDocument = fresh;
|
|
2451
|
+
return fresh;
|
|
2452
|
+
})();
|
|
2453
|
+
documentCache.set(source, promise);
|
|
2454
|
+
return promise;
|
|
2455
|
+
}
|
|
2456
|
+
function ensureSchemas(document) {
|
|
2457
|
+
const cached = schemasCache.get(document);
|
|
2458
|
+
if (cached) return cached;
|
|
2459
|
+
const promise = Promise.resolve().then(() => {
|
|
2460
|
+
const result = getSchemas(document, { contentType });
|
|
2461
|
+
nameMapping = result.nameMapping;
|
|
2462
|
+
return result.schemas;
|
|
2463
|
+
});
|
|
2464
|
+
schemasCache.set(document, promise);
|
|
2465
|
+
return promise;
|
|
2466
|
+
}
|
|
2467
|
+
function ensureBaseOas(document) {
|
|
2468
|
+
const cached = baseOasCache.get(document);
|
|
2469
|
+
if (cached) return cached;
|
|
2470
|
+
const baseOas = new oas.default(document);
|
|
2471
|
+
baseOasCache.set(document, baseOas);
|
|
2472
|
+
return baseOas;
|
|
2473
|
+
}
|
|
2474
|
+
function ensureSchemaParser(document) {
|
|
2475
|
+
const cached = schemaParserCache.get(document);
|
|
2476
|
+
if (cached) return cached;
|
|
2477
|
+
const parser = createSchemaParser({
|
|
2478
|
+
document,
|
|
2479
|
+
contentType
|
|
2480
|
+
});
|
|
2481
|
+
schemaParserCache.set(document, parser);
|
|
2482
|
+
return parser;
|
|
2483
|
+
}
|
|
2484
|
+
function ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas) {
|
|
2485
|
+
const cached = preScanCache.get(document);
|
|
2486
|
+
if (cached) return cached;
|
|
2487
|
+
const result = preScan({
|
|
2488
|
+
schemas,
|
|
2489
|
+
parseSchema,
|
|
2490
|
+
parseOperation,
|
|
2491
|
+
baseOas,
|
|
2492
|
+
parserOptions,
|
|
2493
|
+
discriminator,
|
|
2494
|
+
dedupe
|
|
2495
|
+
});
|
|
2496
|
+
preScanCache.set(document, result);
|
|
2497
|
+
return result;
|
|
2498
|
+
}
|
|
2348
2499
|
async function createStream(source) {
|
|
2349
2500
|
const document = await ensureDocument(source);
|
|
2350
2501
|
const schemas = await ensureSchemas(document);
|
|
2351
2502
|
const { parseSchema, parseOperation } = ensureSchemaParser(document);
|
|
2352
2503
|
const baseOas = ensureBaseOas(document);
|
|
2353
|
-
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(schemas, parseSchema, parseOperation, baseOas);
|
|
2504
|
+
const { refAliasMap, enumNames, circularNames, discriminatorChildMap, dedupePlan } = ensurePreScan(document, schemas, parseSchema, parseOperation, baseOas);
|
|
2354
2505
|
return createInputStream({
|
|
2355
2506
|
schemas,
|
|
2356
2507
|
parseSchema,
|
|
@@ -2396,6 +2547,7 @@ const adapterOas = (0, _kubb_core.createAdapter)((options) => {
|
|
|
2396
2547
|
return parsedDocument;
|
|
2397
2548
|
},
|
|
2398
2549
|
async validate(input, options) {
|
|
2550
|
+
await assertInputExists(input);
|
|
2399
2551
|
await validateDocument(await parseDocument(input), options);
|
|
2400
2552
|
},
|
|
2401
2553
|
getImports(node, resolve) {
|