@kubb/adapter-oas 5.17.0 → 5.18.0
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 +514 -99
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -6
- package/dist/index.d.ts +22 -6
- package/dist/index.js +515 -100
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineExtension } from "@kubb/core";
|
|
2
2
|
import { Diagnostics, ast, childName, enumPrimitive, enumPropName, extractRefName, macroDedupeUnion, macroDiscriminatorEnum, macroEnumName, mergeAdjacentObjectsLazy } from "@kubb/kit";
|
|
3
|
-
import { join } from "node:path";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { access, readFile } from "node:fs/promises";
|
|
5
6
|
import { upgrade } from "@scalar/openapi-upgrader";
|
|
6
7
|
import { bundle } from "api-ref-bundler";
|
|
@@ -19,7 +20,7 @@ var compatibility = {
|
|
|
19
20
|
"node": ">=22.22"
|
|
20
21
|
};
|
|
21
22
|
// package.json
|
|
22
|
-
var version = "5.
|
|
23
|
+
var version = "5.18.0";
|
|
23
24
|
// src/constants.ts
|
|
24
25
|
/**
|
|
25
26
|
* Default parser options applied when no explicit options are provided.
|
|
@@ -401,6 +402,21 @@ function isOpenApi32(document) {
|
|
|
401
402
|
const [major = 0, minor = 0] = String(document.openapi).split(".").map(Number);
|
|
402
403
|
return major > 3 || major === 3 && minor >= 2;
|
|
403
404
|
}
|
|
405
|
+
function pickStrings(source, keys) {
|
|
406
|
+
if (!source || typeof source !== "object") return void 0;
|
|
407
|
+
const entries = keys.flatMap((key) => {
|
|
408
|
+
const value = source[key];
|
|
409
|
+
return typeof value === "string" ? [[key, value]] : [];
|
|
410
|
+
});
|
|
411
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
412
|
+
}
|
|
413
|
+
function getExternalDocs(source) {
|
|
414
|
+
const docs = pickStrings(source, ["url", "description"]);
|
|
415
|
+
return docs?.url ? {
|
|
416
|
+
url: docs.url,
|
|
417
|
+
...docs.description !== void 0 && { description: docs.description }
|
|
418
|
+
} : void 0;
|
|
419
|
+
}
|
|
404
420
|
/**
|
|
405
421
|
* Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
|
|
406
422
|
*/
|
|
@@ -415,12 +431,12 @@ function isReference(obj) {
|
|
|
415
431
|
return !!obj && typeof obj === "object" && "$ref" in obj;
|
|
416
432
|
}
|
|
417
433
|
/**
|
|
418
|
-
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object
|
|
419
|
-
* excluding the Swagger 2 string form.
|
|
434
|
+
* Returns `true` when `obj` is a schema with a structured OAS 3.x `discriminator` object that names a
|
|
435
|
+
* `propertyName`, excluding the Swagger 2 string form and a 3.2 discriminator that only sets `defaultMapping`.
|
|
420
436
|
*/
|
|
421
437
|
function isDiscriminator(obj) {
|
|
422
|
-
const
|
|
423
|
-
return !!
|
|
438
|
+
const discriminator = obj?.["discriminator"];
|
|
439
|
+
return !!discriminator && typeof discriminator === "object" && typeof discriminator.propertyName === "string";
|
|
424
440
|
}
|
|
425
441
|
/**
|
|
426
442
|
* Returns `true` when a schema is a raw binary payload.
|
|
@@ -429,17 +445,32 @@ function isBinary(schema) {
|
|
|
429
445
|
return schema.contentMediaType === "application/octet-stream" && schema.contentEncoding === void 0 && (schema.type === void 0 || schema.type === "string");
|
|
430
446
|
}
|
|
431
447
|
/**
|
|
448
|
+
* Keywords that only annotate a schema. A schema with nothing else does not constrain the value.
|
|
449
|
+
*/
|
|
450
|
+
const annotationKeywords = /* @__PURE__ */ new Set([
|
|
451
|
+
"title",
|
|
452
|
+
"description",
|
|
453
|
+
"default",
|
|
454
|
+
"example",
|
|
455
|
+
"examples",
|
|
456
|
+
"deprecated",
|
|
457
|
+
"readOnly",
|
|
458
|
+
"writeOnly",
|
|
459
|
+
"externalDocs"
|
|
460
|
+
]);
|
|
461
|
+
/**
|
|
432
462
|
* Returns the binary schema for a non-JSON entry the OAS 3.1 upgrade emptied out, or `undefined`
|
|
433
463
|
* when the entry carries a schema of its own. The upgrade rewrites `format: 'binary'` into
|
|
434
464
|
* `contentMediaType` and drops it whenever it only repeats the media type key, for any binary
|
|
435
465
|
* media type (`application/octet-stream`, `application/pdf`, `image/png`, ...), not just
|
|
436
466
|
* `application/octet-stream`. Without the fallback such a body resolves to the empty schema type
|
|
437
|
-
* instead of a blob.
|
|
467
|
+
* instead of a blob. Annotations such as `description` survive the upgrade and are kept.
|
|
438
468
|
*/
|
|
439
469
|
function getBinaryFallbackSchema(mediaType, schema) {
|
|
440
470
|
if (!mediaType || isJsonMimeType(mediaType)) return void 0;
|
|
441
|
-
if (schema && Object.keys(schema).
|
|
471
|
+
if (schema && Object.keys(schema).some((key) => !annotationKeywords.has(key) && !key.startsWith("x-"))) return void 0;
|
|
442
472
|
return {
|
|
473
|
+
...schema,
|
|
443
474
|
type: "string",
|
|
444
475
|
contentMediaType: "application/octet-stream"
|
|
445
476
|
};
|
|
@@ -578,6 +609,65 @@ function createRefs(document) {
|
|
|
578
609
|
derefKeepingRef
|
|
579
610
|
};
|
|
580
611
|
}
|
|
612
|
+
// src/load/schemaIds.ts
|
|
613
|
+
const DATA_KEYS = /* @__PURE__ */ new Set([
|
|
614
|
+
"example",
|
|
615
|
+
"examples",
|
|
616
|
+
"const",
|
|
617
|
+
"enum",
|
|
618
|
+
"default"
|
|
619
|
+
]);
|
|
620
|
+
const NAMED_ENTRY_KEYS = /* @__PURE__ */ new Set([
|
|
621
|
+
"properties",
|
|
622
|
+
"patternProperties",
|
|
623
|
+
"$defs",
|
|
624
|
+
"definitions",
|
|
625
|
+
"schemas",
|
|
626
|
+
"dependentSchemas"
|
|
627
|
+
]);
|
|
628
|
+
function walk(node, pointer, visit, parentKey) {
|
|
629
|
+
if (!node || typeof node !== "object") return;
|
|
630
|
+
if (!Array.isArray(node)) visit(node, pointer);
|
|
631
|
+
const named = parentKey !== void 0 && NAMED_ENTRY_KEYS.has(parentKey);
|
|
632
|
+
for (const [key, value] of Object.entries(node)) {
|
|
633
|
+
if (!named && (DATA_KEYS.has(key) || key.startsWith("x-"))) continue;
|
|
634
|
+
walk(value, `${pointer}/${escapePointerToken(key)}`, visit, Array.isArray(node) ? parentKey : key);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
/**
|
|
638
|
+
* The declaration in the nearest enclosing schema, else the only one, else `undefined`.
|
|
639
|
+
*/
|
|
640
|
+
function pick(declarations, pointer) {
|
|
641
|
+
if (!declarations) return void 0;
|
|
642
|
+
const enclosing = declarations.filter((target) => pointer === target || pointer.startsWith(`${target}/`));
|
|
643
|
+
if (enclosing.length) return enclosing.reduce((a, b) => b.length > a.length ? b : a);
|
|
644
|
+
return declarations.length === 1 ? declarations[0] : void 0;
|
|
645
|
+
}
|
|
646
|
+
/**
|
|
647
|
+
* Rewrites, in place, each `$ref` to an `$id`, `$anchor` or `$dynamicAnchor`, and each `$dynamicRef`, into a
|
|
648
|
+
* `#/...` pointer, so it is not fetched. Instance data and `x-` extensions are left as written.
|
|
649
|
+
*
|
|
650
|
+
* shortcut: `$id` matches the `$ref` string exactly and `$dynamicRef` resolves statically to the nearest
|
|
651
|
+
* enclosing anchor, so a relative `$id` base or a recursively extended schema is not followed, upgrade when a spec needs that.
|
|
652
|
+
*/
|
|
653
|
+
function resolveSchemaIds(document) {
|
|
654
|
+
const targets = /* @__PURE__ */ new Map();
|
|
655
|
+
const declare = (key, pointer) => targets.set(key, [...targets.get(key) ?? [], pointer]);
|
|
656
|
+
walk(document, "#", (node, pointer) => {
|
|
657
|
+
if (typeof node["$id"] === "string") declare(node["$id"], pointer);
|
|
658
|
+
for (const key of ["$anchor", "$dynamicAnchor"]) if (typeof node[key] === "string") declare(`#${node[key]}`, pointer);
|
|
659
|
+
});
|
|
660
|
+
if (!targets.size) return;
|
|
661
|
+
walk(document, "#", (node, pointer) => {
|
|
662
|
+
const ref = typeof node["$ref"] === "string" ? pick(targets.get(node["$ref"]), pointer) : void 0;
|
|
663
|
+
if (ref) node["$ref"] = ref;
|
|
664
|
+
const dynamicRef = typeof node["$dynamicRef"] === "string" ? pick(targets.get(node["$dynamicRef"]), pointer) : void 0;
|
|
665
|
+
if (dynamicRef && node["$ref"] === void 0) {
|
|
666
|
+
node["$ref"] = dynamicRef;
|
|
667
|
+
delete node["$dynamicRef"];
|
|
668
|
+
}
|
|
669
|
+
});
|
|
670
|
+
}
|
|
581
671
|
// src/load/source.ts
|
|
582
672
|
const urlRegExp = /^https?:\/+/i;
|
|
583
673
|
/**
|
|
@@ -747,6 +837,21 @@ function isExtensionPath(path) {
|
|
|
747
837
|
return path.some((token, index) => String(token).startsWith("x-") && !namedEntryKeys.has(String(path[index - 1])));
|
|
748
838
|
}
|
|
749
839
|
/**
|
|
840
|
+
* Returns the base for relative `$ref`s: the 3.2 `$self` resolved against `pathOrUrl`, else `pathOrUrl`.
|
|
841
|
+
*/
|
|
842
|
+
function getBaseUri(pathOrUrl, root) {
|
|
843
|
+
const self = root && typeof root === "object" ? root.$self : void 0;
|
|
844
|
+
if (typeof self !== "string" || !self) return pathOrUrl;
|
|
845
|
+
try {
|
|
846
|
+
if (urlRegExp.test(self)) return new URL(self).href;
|
|
847
|
+
if (/^file:/i.test(self)) return fileURLToPath(self);
|
|
848
|
+
if (/^[a-z][a-z\d+.-]*:/i.test(self)) return pathOrUrl;
|
|
849
|
+
return urlRegExp.test(pathOrUrl) ? new URL(self, pathOrUrl).href : resolve(dirname(pathOrUrl), self);
|
|
850
|
+
} catch {
|
|
851
|
+
return pathOrUrl;
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
/**
|
|
750
855
|
* Bundles a multi-file OpenAPI document into a single document via `api-ref-bundler`.
|
|
751
856
|
*
|
|
752
857
|
* External file schemas are hoisted into named `components.schemas` entries, so a property
|
|
@@ -759,8 +864,9 @@ function isExtensionPath(path) {
|
|
|
759
864
|
* all-internal document it is a no-op that still walks the whole tree to confirm that, which
|
|
760
865
|
* costs real time on a large spec.
|
|
761
866
|
*
|
|
762
|
-
* `document` stands in for the file at `pathOrUrl`, such as inline input.
|
|
763
|
-
*
|
|
867
|
+
* `document` stands in for the file at `pathOrUrl`, such as inline input. Relative `$ref`s resolve
|
|
868
|
+
* against the OpenAPI 3.2 `$self` when the document sets one. Every other file or URL read,
|
|
869
|
+
* including one that fails, lands in `sources`. A `$ref` that cannot be read reports
|
|
764
870
|
* `KUBB_REF_NOT_FOUND` at the key that holds it, as a warning when it sits inside an `x-` extension.
|
|
765
871
|
*
|
|
766
872
|
* @example Local file
|
|
@@ -772,8 +878,10 @@ function isExtensionPath(path) {
|
|
|
772
878
|
async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
|
|
773
879
|
const cache = /* @__PURE__ */ new Map();
|
|
774
880
|
const toKey = (sourcePath) => urlRegExp.test(sourcePath) ? new URL(sourcePath).href : sourcePath;
|
|
881
|
+
let entry = pathOrUrl;
|
|
882
|
+
let entryDocument = document;
|
|
775
883
|
const resolver = (sourcePath) => {
|
|
776
|
-
if (
|
|
884
|
+
if (entryDocument !== void 0 && toKey(sourcePath) === toKey(entry)) return Promise.resolve(entryDocument);
|
|
777
885
|
const key = toKey(sourcePath);
|
|
778
886
|
const cached = cache.get(key);
|
|
779
887
|
if (cached) return cached;
|
|
@@ -783,9 +891,12 @@ async function bundleDocument(pathOrUrl, { document, sources = [] } = {}) {
|
|
|
783
891
|
return result;
|
|
784
892
|
};
|
|
785
893
|
const root = await resolver(pathOrUrl);
|
|
894
|
+
resolveSchemaIds(root);
|
|
786
895
|
if (typeof root === "object" && root !== null && !hasExternalRef(root)) return root;
|
|
787
|
-
|
|
788
|
-
|
|
896
|
+
entry = getBaseUri(pathOrUrl, root);
|
|
897
|
+
entryDocument = root;
|
|
898
|
+
return await bundle(entry, resolver, { hooks: { onError: (message, { path, state }) => {
|
|
899
|
+
const pointer = toKey(state.baseFile) === toKey(entry) ? `#/${path.map((token) => escapePointerToken(String(token))).join("/")}` : void 0;
|
|
789
900
|
const diagnostic = {
|
|
790
901
|
code: Diagnostics.code.refNotFound,
|
|
791
902
|
severity: isExtensionPath(path) ? "warning" : "error",
|
|
@@ -824,10 +935,12 @@ async function parseFromConfig(source, sources = []) {
|
|
|
824
935
|
origin: "the inline `input` string",
|
|
825
936
|
help: "Fix the syntax at the reported line. Kubb reads `input` as an inline spec, not a file path, because it starts with `{`, `[`, `openapi:`, or `swagger:`, or spans several lines."
|
|
826
937
|
}) : structuredClone(source.data);
|
|
827
|
-
|
|
938
|
+
if (source.root && hasExternalRef(data)) return bundleDocument(join(source.root, inlineInputName), {
|
|
828
939
|
document: data,
|
|
829
940
|
sources
|
|
830
|
-
})
|
|
941
|
+
});
|
|
942
|
+
resolveSchemaIds(data);
|
|
943
|
+
return data;
|
|
831
944
|
}
|
|
832
945
|
await assertInputExists(source.path);
|
|
833
946
|
return bundleDocument(source.path, { sources });
|
|
@@ -1152,6 +1265,80 @@ function getSchemas(document, { contentType }, refs) {
|
|
|
1152
1265
|
pointers
|
|
1153
1266
|
};
|
|
1154
1267
|
}
|
|
1268
|
+
// src/model/security.ts
|
|
1269
|
+
const SCHEME_POINTER = "#/components/securitySchemes/";
|
|
1270
|
+
const API_KEY_LOCATIONS = /* @__PURE__ */ new Set([
|
|
1271
|
+
"header",
|
|
1272
|
+
"query",
|
|
1273
|
+
"cookie"
|
|
1274
|
+
]);
|
|
1275
|
+
function getCredential(scheme) {
|
|
1276
|
+
if (scheme.type === "apiKey") return scheme.name && scheme.in && API_KEY_LOCATIONS.has(scheme.in) ? {
|
|
1277
|
+
type: "apiKey",
|
|
1278
|
+
in: scheme.in,
|
|
1279
|
+
name: scheme.name
|
|
1280
|
+
} : void 0;
|
|
1281
|
+
if (scheme.type === "http") return scheme.scheme ? {
|
|
1282
|
+
type: "http",
|
|
1283
|
+
scheme: scheme.scheme.toLowerCase(),
|
|
1284
|
+
...scheme.bearerFormat && { bearerFormat: scheme.bearerFormat }
|
|
1285
|
+
} : void 0;
|
|
1286
|
+
if (scheme.type === "openIdConnect") return {
|
|
1287
|
+
type: "openIdConnect",
|
|
1288
|
+
...scheme.openIdConnectUrl && { url: scheme.openIdConnectUrl }
|
|
1289
|
+
};
|
|
1290
|
+
if (scheme.type === "oauth2") {
|
|
1291
|
+
const flows = Object.entries(scheme.flows ?? {}).map(([name, flow]) => {
|
|
1292
|
+
const scopes = flow && typeof flow === "object" && flow.scopes && typeof flow.scopes === "object" ? { scopes: flow.scopes } : {};
|
|
1293
|
+
return [name, {
|
|
1294
|
+
...pickStrings(flow, [
|
|
1295
|
+
"authorizationUrl",
|
|
1296
|
+
"deviceAuthorizationUrl",
|
|
1297
|
+
"tokenUrl",
|
|
1298
|
+
"refreshUrl"
|
|
1299
|
+
]),
|
|
1300
|
+
...scopes
|
|
1301
|
+
}];
|
|
1302
|
+
});
|
|
1303
|
+
return {
|
|
1304
|
+
type: "oauth2",
|
|
1305
|
+
flows: Object.fromEntries(flows),
|
|
1306
|
+
...scheme.oauth2MetadataUrl && { metadataUrl: scheme.oauth2MetadataUrl }
|
|
1307
|
+
};
|
|
1308
|
+
}
|
|
1309
|
+
return scheme.type === "mutualTLS" ? { type: "mutualTLS" } : void 0;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Maps `components.securitySchemes` to `AuthScheme` entries, leaving out a scheme a client cannot place.
|
|
1313
|
+
*/
|
|
1314
|
+
function toAuthSchemes(schemes, refs) {
|
|
1315
|
+
return Object.entries(schemes ?? {}).flatMap(([name, value]) => {
|
|
1316
|
+
const scheme = refs.deref(value);
|
|
1317
|
+
const credential = scheme && getCredential(scheme);
|
|
1318
|
+
if (!scheme || !credential) return [];
|
|
1319
|
+
return [{
|
|
1320
|
+
name,
|
|
1321
|
+
...scheme.description && { description: scheme.description },
|
|
1322
|
+
...scheme.deprecated && { deprecated: true },
|
|
1323
|
+
credential
|
|
1324
|
+
}];
|
|
1325
|
+
});
|
|
1326
|
+
}
|
|
1327
|
+
/**
|
|
1328
|
+
* Maps security requirements to `AuthRequirement` entries. A 3.2 URI key becomes the scheme name.
|
|
1329
|
+
*/
|
|
1330
|
+
function toAuthRequirements(requirements, document) {
|
|
1331
|
+
if (!Array.isArray(requirements)) return void 0;
|
|
1332
|
+
const schemes = document.components?.securitySchemes ?? {};
|
|
1333
|
+
const toName = (key) => {
|
|
1334
|
+
const index = key.indexOf(SCHEME_POINTER);
|
|
1335
|
+
return Object.hasOwn(schemes, key) || index === -1 ? key : unescapePointerToken(key.slice(index + 29));
|
|
1336
|
+
};
|
|
1337
|
+
return requirements.filter((requirement) => !!requirement && typeof requirement === "object").map((requirement) => Object.entries(requirement).map(([key, scopes]) => ({
|
|
1338
|
+
scheme: toName(key),
|
|
1339
|
+
...Array.isArray(scopes) && scopes.length && { scopes: scopes.map(String) }
|
|
1340
|
+
})));
|
|
1341
|
+
}
|
|
1155
1342
|
// src/model/server.ts
|
|
1156
1343
|
/**
|
|
1157
1344
|
* Reads the server URL from the document's `servers` array at `server.index`,
|
|
@@ -1208,8 +1395,11 @@ function resolveServerUrl(server, overrides) {
|
|
|
1208
1395
|
* Maps OpenAPI servers to `InputServer` entries: drops entries without a `url` and fills `{variable}` defaults.
|
|
1209
1396
|
*/
|
|
1210
1397
|
function toInputServers(servers) {
|
|
1211
|
-
return servers.filter((entry) => entry.url).map(({ url, description, variables }) => {
|
|
1212
|
-
const declared =
|
|
1398
|
+
return servers.filter((entry) => entry.url).map(({ url, name, description, variables }) => {
|
|
1399
|
+
const declared = {
|
|
1400
|
+
...name !== void 0 && { name },
|
|
1401
|
+
...variables && Object.keys(variables).length > 0 && { variables }
|
|
1402
|
+
};
|
|
1213
1403
|
try {
|
|
1214
1404
|
return {
|
|
1215
1405
|
url: resolveServerUrl({
|
|
@@ -1228,6 +1418,20 @@ function toInputServers(servers) {
|
|
|
1228
1418
|
}
|
|
1229
1419
|
});
|
|
1230
1420
|
}
|
|
1421
|
+
// src/model/tag.ts
|
|
1422
|
+
function toInputTags(tags) {
|
|
1423
|
+
return tags.filter((tag) => typeof tag.name === "string").map(({ name, summary, description, parent, kind, externalDocs }) => {
|
|
1424
|
+
const docs = getExternalDocs(externalDocs);
|
|
1425
|
+
return {
|
|
1426
|
+
name,
|
|
1427
|
+
...typeof summary === "string" && { summary },
|
|
1428
|
+
...typeof description === "string" && { description },
|
|
1429
|
+
...typeof parent === "string" && { parent },
|
|
1430
|
+
...typeof kind === "string" && { kind },
|
|
1431
|
+
...docs && { externalDocs: docs }
|
|
1432
|
+
};
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1231
1435
|
// src/model/mediaType.ts
|
|
1232
1436
|
/**
|
|
1233
1437
|
* The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
|
|
@@ -1409,6 +1613,39 @@ function flattenSchema(schema) {
|
|
|
1409
1613
|
function scanSchema(node, pointer = node.pointer) {
|
|
1410
1614
|
if (pointer) visit(node, pointer);
|
|
1411
1615
|
}
|
|
1616
|
+
let formatTally;
|
|
1617
|
+
/**
|
|
1618
|
+
* Runs `fn` and reports `KUBB_UNSUPPORTED_FORMAT` once per format with the number of schemas that use it,
|
|
1619
|
+
* instead of once per schema. `format` is an open value in OpenAPI, so a large spec would otherwise print
|
|
1620
|
+
* thousands of warnings that say the same thing. `fn` must be synchronous.
|
|
1621
|
+
*/
|
|
1622
|
+
function groupUnsupportedFormats(fn) {
|
|
1623
|
+
formatTally = /* @__PURE__ */ new Map();
|
|
1624
|
+
try {
|
|
1625
|
+
return fn();
|
|
1626
|
+
} finally {
|
|
1627
|
+
const tally = formatTally;
|
|
1628
|
+
formatTally = void 0;
|
|
1629
|
+
for (const [format, pointers] of tally) reportUnsupportedFormat(format, [...pointers]);
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
function tallyFormat(format, pointer) {
|
|
1633
|
+
if (!formatTally) return reportUnsupportedFormat(format, [pointer]);
|
|
1634
|
+
formatTally.set(format, (formatTally.get(format) ?? /* @__PURE__ */ new Set()).add(pointer));
|
|
1635
|
+
}
|
|
1636
|
+
function reportUnsupportedFormat(format, pointers) {
|
|
1637
|
+
const count = pointers.length;
|
|
1638
|
+
Diagnostics.report({
|
|
1639
|
+
code: Diagnostics.code.unsupportedFormat,
|
|
1640
|
+
severity: "warning",
|
|
1641
|
+
message: count === 1 ? `Kubb does not map the format "${format}" to a specific type, so it falls back to the base type.` : `Kubb does not map the format "${format}" to a specific type, so ${count} schemas fall back to the base type.`,
|
|
1642
|
+
help: `Use a format Kubb supports, or handle "${format}" with a custom parser or plugin.`,
|
|
1643
|
+
location: {
|
|
1644
|
+
kind: "schema",
|
|
1645
|
+
pointer: pointers[0]
|
|
1646
|
+
}
|
|
1647
|
+
});
|
|
1648
|
+
}
|
|
1412
1649
|
function visit(node, pointer) {
|
|
1413
1650
|
node.pointer ??= pointer;
|
|
1414
1651
|
if (node.deprecated) Diagnostics.report({
|
|
@@ -1420,16 +1657,7 @@ function visit(node, pointer) {
|
|
|
1420
1657
|
pointer
|
|
1421
1658
|
}
|
|
1422
1659
|
});
|
|
1423
|
-
if (typeof node.format === "string" && !isHandledFormat(node.format))
|
|
1424
|
-
code: Diagnostics.code.unsupportedFormat,
|
|
1425
|
-
severity: "warning",
|
|
1426
|
-
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
1427
|
-
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
1428
|
-
location: {
|
|
1429
|
-
kind: "schema",
|
|
1430
|
-
pointer
|
|
1431
|
-
}
|
|
1432
|
-
});
|
|
1660
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) tallyFormat(node.format, pointer);
|
|
1433
1661
|
if (node.type === "object") {
|
|
1434
1662
|
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
1435
1663
|
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
@@ -1538,18 +1766,6 @@ function getResponseByStatusCode({ operation, refs, statusCode }) {
|
|
|
1538
1766
|
function getRequestBody({ operation, refs }) {
|
|
1539
1767
|
return refs.deref(operation.schema.requestBody);
|
|
1540
1768
|
}
|
|
1541
|
-
/**
|
|
1542
|
-
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
1543
|
-
* when several are declared), then the first declared one, defaulting to `'application/json'`.
|
|
1544
|
-
*/
|
|
1545
|
-
function getRequestContentType({ operation, refs }) {
|
|
1546
|
-
const content = getRequestBody({
|
|
1547
|
-
operation,
|
|
1548
|
-
refs
|
|
1549
|
-
})?.content;
|
|
1550
|
-
const mediaTypes = content ? Object.keys(content) : [];
|
|
1551
|
-
return mediaTypes.findLast(isJsonMimeType) ?? mediaTypes[0] ?? "application/json";
|
|
1552
|
-
}
|
|
1553
1769
|
/** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
|
|
1554
1770
|
function getParameters({ operation, refs }) {
|
|
1555
1771
|
const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
|
|
@@ -1573,7 +1789,8 @@ const PARAMETER_STYLES = /* @__PURE__ */ new Set([
|
|
|
1573
1789
|
"simple",
|
|
1574
1790
|
"spaceDelimited",
|
|
1575
1791
|
"pipeDelimited",
|
|
1576
|
-
"deepObject"
|
|
1792
|
+
"deepObject",
|
|
1793
|
+
"cookie"
|
|
1577
1794
|
]);
|
|
1578
1795
|
function pickStyleFields({ style, explode, allowReserved }) {
|
|
1579
1796
|
return {
|
|
@@ -1582,16 +1799,45 @@ function pickStyleFields({ style, explode, allowReserved }) {
|
|
|
1582
1799
|
...allowReserved !== void 0 ? { allowReserved } : {}
|
|
1583
1800
|
};
|
|
1584
1801
|
}
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
const node = {
|
|
1589
|
-
...contentType !== void 0 ? { contentType } : {},
|
|
1590
|
-
...pickStyleFields(rest)
|
|
1591
|
-
};
|
|
1802
|
+
function getEncodingFields({ encoding, prefixEncoding, itemEncoding }) {
|
|
1803
|
+
const entries = Object.entries(encoding ?? {}).flatMap(([property, entry]) => {
|
|
1804
|
+
const node = toEncodingNode(entry);
|
|
1592
1805
|
return Object.keys(node).length ? [[property, node]] : [];
|
|
1593
1806
|
});
|
|
1594
|
-
|
|
1807
|
+
const item = itemEncoding && toEncodingNode(itemEncoding);
|
|
1808
|
+
return {
|
|
1809
|
+
...entries.length && { encoding: Object.fromEntries(entries) },
|
|
1810
|
+
...prefixEncoding?.length && { prefixEncoding: prefixEncoding.map(toEncodingNode) },
|
|
1811
|
+
...item && Object.keys(item).length && { itemEncoding: item }
|
|
1812
|
+
};
|
|
1813
|
+
}
|
|
1814
|
+
function toEncodingNode({ contentType, encoding, prefixEncoding, itemEncoding, ...rest }) {
|
|
1815
|
+
return {
|
|
1816
|
+
...contentType !== void 0 && { contentType },
|
|
1817
|
+
...pickStyleFields(rest),
|
|
1818
|
+
...getEncodingFields({
|
|
1819
|
+
encoding,
|
|
1820
|
+
prefixEncoding,
|
|
1821
|
+
itemEncoding
|
|
1822
|
+
})
|
|
1823
|
+
};
|
|
1824
|
+
}
|
|
1825
|
+
function getExamples({ example, examples }, refs) {
|
|
1826
|
+
const named = Object.entries(examples ?? {}).flatMap(([name, entry]) => {
|
|
1827
|
+
const resolved = refs.deref(entry);
|
|
1828
|
+
if (!resolved) return [];
|
|
1829
|
+
const { summary, description, dataValue, value, serializedValue } = resolved;
|
|
1830
|
+
const data = dataValue !== void 0 ? dataValue : value;
|
|
1831
|
+
return [{
|
|
1832
|
+
name,
|
|
1833
|
+
...typeof summary === "string" && { summary },
|
|
1834
|
+
...typeof description === "string" && { description },
|
|
1835
|
+
...data !== void 0 && { value: data },
|
|
1836
|
+
...typeof serializedValue === "string" && { serializedValue }
|
|
1837
|
+
}];
|
|
1838
|
+
});
|
|
1839
|
+
if (named.length) return { examples: named };
|
|
1840
|
+
return example !== void 0 ? { examples: [{ value: example }] } : {};
|
|
1595
1841
|
}
|
|
1596
1842
|
/**
|
|
1597
1843
|
* Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
|
|
@@ -1620,6 +1866,24 @@ function getBodySchema({ content, contentType, refs, document }) {
|
|
|
1620
1866
|
if (binary) return binary;
|
|
1621
1867
|
return schema ? refs.derefKeepingRef(schema) : null;
|
|
1622
1868
|
}
|
|
1869
|
+
function getLinks(links, refs) {
|
|
1870
|
+
const entries = Object.entries(links ?? {}).flatMap(([name, value]) => {
|
|
1871
|
+
const link = refs.deref(value);
|
|
1872
|
+
if (!link) return [];
|
|
1873
|
+
const server = link.server ? toInputServers([link.server])[0] : void 0;
|
|
1874
|
+
return [[name, {
|
|
1875
|
+
...pickStrings(link, [
|
|
1876
|
+
"operationId",
|
|
1877
|
+
"operationRef",
|
|
1878
|
+
"description"
|
|
1879
|
+
]),
|
|
1880
|
+
...link.parameters && { parameters: link.parameters },
|
|
1881
|
+
..."requestBody" in link && { requestBody: link.requestBody },
|
|
1882
|
+
...server && { server }
|
|
1883
|
+
}]];
|
|
1884
|
+
});
|
|
1885
|
+
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
1886
|
+
}
|
|
1623
1887
|
/** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
|
|
1624
1888
|
function getStreamField({ contentType, mediaType, refs, document, node }) {
|
|
1625
1889
|
if (!isStream(contentType, mediaType)) return {};
|
|
@@ -1690,11 +1954,12 @@ function collectPropertyKeysByFlag(schema, flag) {
|
|
|
1690
1954
|
function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
|
|
1691
1955
|
const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
|
|
1692
1956
|
const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
|
|
1693
|
-
const
|
|
1957
|
+
const mediaType = getMediaType({
|
|
1694
1958
|
content: param.content,
|
|
1695
1959
|
contentType,
|
|
1696
1960
|
refs
|
|
1697
|
-
})
|
|
1961
|
+
});
|
|
1962
|
+
const rawSchema = param.schema ?? mediaType?.schema;
|
|
1698
1963
|
const schema = rawSchema ? parseSchema({
|
|
1699
1964
|
schema: rawSchema,
|
|
1700
1965
|
name: schemaName
|
|
@@ -1710,7 +1975,10 @@ function parseParameter({ param, pointer, parentName, options, parseSchema, refs
|
|
|
1710
1975
|
},
|
|
1711
1976
|
required: param.required ?? false,
|
|
1712
1977
|
...pickStyleFields(param),
|
|
1713
|
-
...contentType !== void 0 ? { contentType } : {}
|
|
1978
|
+
...contentType !== void 0 ? { contentType } : {},
|
|
1979
|
+
...getExamples(mediaType ?? param, refs),
|
|
1980
|
+
...param.deprecated && { deprecated: true },
|
|
1981
|
+
...param.allowEmptyValue && { allowEmptyValue: true }
|
|
1714
1982
|
});
|
|
1715
1983
|
}
|
|
1716
1984
|
/**
|
|
@@ -1755,12 +2023,12 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1755
2023
|
name: requestBodyName
|
|
1756
2024
|
}), bodyRequired);
|
|
1757
2025
|
scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
|
|
1758
|
-
const encoding = getEncoding(mediaType?.encoding);
|
|
1759
2026
|
return [ast.factory.createContent({
|
|
1760
2027
|
contentType: ct,
|
|
1761
2028
|
schema: node,
|
|
1762
2029
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
|
|
1763
|
-
...
|
|
2030
|
+
...mediaType && getEncodingFields(mediaType),
|
|
2031
|
+
...mediaType && getExamples(mediaType, refs),
|
|
1764
2032
|
...getStreamField({
|
|
1765
2033
|
contentType: ct,
|
|
1766
2034
|
mediaType,
|
|
@@ -1803,6 +2071,8 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1803
2071
|
return {
|
|
1804
2072
|
schema: node,
|
|
1805
2073
|
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
|
|
2074
|
+
...mediaType && getEncodingFields(mediaType),
|
|
2075
|
+
...mediaType && getExamples(mediaType, refs),
|
|
1806
2076
|
...ct ? getStreamField({
|
|
1807
2077
|
contentType: ct,
|
|
1808
2078
|
mediaType,
|
|
@@ -1812,22 +2082,41 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1812
2082
|
}) : {}
|
|
1813
2083
|
};
|
|
1814
2084
|
};
|
|
1815
|
-
const
|
|
2085
|
+
const declaredContentTypes = Object.keys(response?.content ?? {});
|
|
2086
|
+
const responseContent = (contentType && declaredContentTypes.length ? [contentType] : declaredContentTypes).map((ct) => ast.factory.createContent({
|
|
1816
2087
|
contentType: ct,
|
|
1817
2088
|
...parseEntrySchema(ct)
|
|
1818
2089
|
}));
|
|
1819
2090
|
if (responseContent.length === 0) responseContent.push(ast.factory.createContent({
|
|
1820
|
-
contentType:
|
|
1821
|
-
operation,
|
|
1822
|
-
refs
|
|
1823
|
-
}),
|
|
2091
|
+
contentType: "",
|
|
1824
2092
|
...parseEntrySchema(contentType)
|
|
1825
2093
|
}));
|
|
2094
|
+
const responsePointer = `${pointer}/responses/${statusCode}`;
|
|
2095
|
+
const headers = Object.entries(response?.headers ?? {}).flatMap(([name, value]) => {
|
|
2096
|
+
const header = name.toLowerCase() === "content-type" ? null : refs.deref(value);
|
|
2097
|
+
if (!header) return [];
|
|
2098
|
+
return [parseParameter({
|
|
2099
|
+
param: {
|
|
2100
|
+
...header,
|
|
2101
|
+
name,
|
|
2102
|
+
in: "header"
|
|
2103
|
+
},
|
|
2104
|
+
pointer: `${responsePointer}/headers/${escapePointerToken(name)}`,
|
|
2105
|
+
parentName: responseName,
|
|
2106
|
+
options,
|
|
2107
|
+
parseSchema,
|
|
2108
|
+
refs
|
|
2109
|
+
})];
|
|
2110
|
+
});
|
|
2111
|
+
const links = getLinks(response?.links, refs);
|
|
1826
2112
|
return ast.factory.createResponse({
|
|
1827
|
-
pointer:
|
|
2113
|
+
pointer: responsePointer,
|
|
1828
2114
|
statusCode,
|
|
2115
|
+
summary: response?.summary,
|
|
1829
2116
|
description: response?.description,
|
|
1830
|
-
content: responseContent
|
|
2117
|
+
content: responseContent,
|
|
2118
|
+
...headers.length && { headers },
|
|
2119
|
+
...links && { links }
|
|
1831
2120
|
});
|
|
1832
2121
|
});
|
|
1833
2122
|
const pickDoc = (key) => {
|
|
@@ -1837,6 +2126,7 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1837
2126
|
return typeof fallback === "string" ? fallback : void 0;
|
|
1838
2127
|
};
|
|
1839
2128
|
const servers = operation.schema.servers ?? operation.pathItem.servers;
|
|
2129
|
+
const auth = toAuthRequirements(operation.schema.security ?? document.security, document);
|
|
1840
2130
|
return ast.factory.createOperation({
|
|
1841
2131
|
pointer,
|
|
1842
2132
|
operationId,
|
|
@@ -1847,11 +2137,13 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1847
2137
|
summary: pickDoc("summary") || void 0,
|
|
1848
2138
|
description: pickDoc("description") || void 0,
|
|
1849
2139
|
deprecated: operation.schema.deprecated || void 0,
|
|
2140
|
+
externalDocs: getExternalDocs(operation.schema.externalDocs),
|
|
1850
2141
|
parameters,
|
|
1851
2142
|
requestBody,
|
|
1852
2143
|
responses,
|
|
1853
2144
|
extensions: pickExtensions(operation.schema),
|
|
1854
|
-
...servers && { servers: toInputServers(servers) }
|
|
2145
|
+
...servers && { servers: toInputServers(servers) },
|
|
2146
|
+
...auth && { auth }
|
|
1855
2147
|
});
|
|
1856
2148
|
}
|
|
1857
2149
|
// src/emit/createNode.ts
|
|
@@ -1872,6 +2164,7 @@ function createNode({ schema, name, nullable, defaultValue }, extras) {
|
|
|
1872
2164
|
default: defaultValue,
|
|
1873
2165
|
examples: extractExamples(schema),
|
|
1874
2166
|
format: schema.format,
|
|
2167
|
+
externalDocs: getExternalDocs(schema.externalDocs),
|
|
1875
2168
|
...extras
|
|
1876
2169
|
});
|
|
1877
2170
|
}
|
|
@@ -2000,6 +2293,15 @@ function removeInheritedDiscriminator({ members, refs }) {
|
|
|
2000
2293
|
});
|
|
2001
2294
|
}
|
|
2002
2295
|
/**
|
|
2296
|
+
* Whether a member can carry the discriminator property. A discriminator is only a hint, so an array
|
|
2297
|
+
* or primitive member keeps its own type instead of an intersection no value can satisfy.
|
|
2298
|
+
*/
|
|
2299
|
+
function isObjectLike(member, refs) {
|
|
2300
|
+
const type = (isReference(member) ? refs.resolve(member.$ref, { report: false }) : member)?.type;
|
|
2301
|
+
if (type === void 0) return true;
|
|
2302
|
+
return Array.isArray(type) ? type.includes("object") : type === "object";
|
|
2303
|
+
}
|
|
2304
|
+
/**
|
|
2003
2305
|
* Narrows each `oneOf`/`anyOf` member with its discriminant value, intersecting the member's own
|
|
2004
2306
|
* node with either the shared-properties slice carrying that value, or a synthetic discriminant
|
|
2005
2307
|
* literal. The referenced child schema's own definition is left untouched — narrowing happens only
|
|
@@ -2062,7 +2364,7 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
|
|
|
2062
2364
|
schema: s,
|
|
2063
2365
|
name
|
|
2064
2366
|
});
|
|
2065
|
-
if (!discriminatorValues.length || !discriminator) return memberNode;
|
|
2367
|
+
if (!discriminatorValues.length || !discriminator || !isObjectLike(s, refs)) return memberNode;
|
|
2066
2368
|
const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
|
|
2067
2369
|
propertyName: discriminator.propertyName,
|
|
2068
2370
|
values: discriminatorValues
|
|
@@ -2327,13 +2629,15 @@ const annotationKeys = /* @__PURE__ */ new Set([
|
|
|
2327
2629
|
]);
|
|
2328
2630
|
/**
|
|
2329
2631
|
* Moves `allOf` members that hold only annotations or `if`/`then`/`else` onto the parent, so they do
|
|
2330
|
-
* not become `unknown`. The parent's own values win
|
|
2632
|
+
* not become `unknown`. The parent's own values win. The parser reads their `if`/`then`/`else` into `conditionals`.
|
|
2331
2633
|
*/
|
|
2634
|
+
function isAnnotationMember(member) {
|
|
2635
|
+
return !!member && typeof member === "object" && !isReference(member) && Object.keys(member).every((key) => annotationKeys.has(key));
|
|
2636
|
+
}
|
|
2332
2637
|
function mergeAnnotations(schema) {
|
|
2333
2638
|
const members = schema.allOf;
|
|
2334
|
-
const
|
|
2335
|
-
const
|
|
2336
|
-
const rest = members.filter((member) => !isAnnotation(member));
|
|
2639
|
+
const annotations = members.filter(isAnnotationMember);
|
|
2640
|
+
const rest = members.filter((member) => !isAnnotationMember(member));
|
|
2337
2641
|
return annotations.length && rest.length ? {
|
|
2338
2642
|
...Object.assign({}, ...annotations),
|
|
2339
2643
|
...schema,
|
|
@@ -2366,7 +2670,8 @@ function convertAllOf({ schema: original, name, nullable, defaultValue, parse, r
|
|
|
2366
2670
|
default: mergedDefault,
|
|
2367
2671
|
examples: extractExamples(schema) ?? memberNode.examples,
|
|
2368
2672
|
pattern: schema.pattern ?? ("pattern" in memberNode ? memberNode.pattern : void 0),
|
|
2369
|
-
format: schema.format ?? memberNode.format
|
|
2673
|
+
format: schema.format ?? memberNode.format,
|
|
2674
|
+
externalDocs: getExternalDocs(schema.externalDocs) ?? memberNode.externalDocs
|
|
2370
2675
|
});
|
|
2371
2676
|
}
|
|
2372
2677
|
const { members: discriminatedAllOf, discriminantValues } = extractDiscriminatedAllOfMembers({
|
|
@@ -2441,7 +2746,7 @@ function convertUnion(context) {
|
|
|
2441
2746
|
};
|
|
2442
2747
|
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
2443
2748
|
const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
2444
|
-
const defaultRef = getDefaultMappingRef(discriminator);
|
|
2749
|
+
const defaultRef = getDefaultMappingRef(typeof schema.discriminator === "object" ? schema.discriminator : void 0);
|
|
2445
2750
|
const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
|
|
2446
2751
|
const strategy = schema.oneOf ? "one" : "any";
|
|
2447
2752
|
const explicitDiscriminatorPropertyName = discriminator?.propertyName;
|
|
@@ -2553,11 +2858,18 @@ function getPrimitiveType(type) {
|
|
|
2553
2858
|
}
|
|
2554
2859
|
/** Shared by number, integer and bigint nodes; the exclusive bounds keep only the OAS 3.1 numeric form. */
|
|
2555
2860
|
function getNumericConstraints(schema) {
|
|
2861
|
+
const exclusiveMinimum = typeof schema.exclusiveMinimum === "number" ? schema.exclusiveMinimum : void 0;
|
|
2862
|
+
const exclusiveMaximum = typeof schema.exclusiveMaximum === "number" ? schema.exclusiveMaximum : void 0;
|
|
2863
|
+
const hasExclusive = exclusiveMinimum !== void 0 || exclusiveMaximum !== void 0;
|
|
2556
2864
|
return {
|
|
2557
2865
|
min: schema.minimum,
|
|
2558
2866
|
max: schema.maximum,
|
|
2559
|
-
|
|
2560
|
-
|
|
2867
|
+
...hasExclusive && { exclusive: {
|
|
2868
|
+
...exclusiveMinimum !== void 0 && { min: exclusiveMinimum },
|
|
2869
|
+
...exclusiveMaximum !== void 0 && { max: exclusiveMaximum }
|
|
2870
|
+
} },
|
|
2871
|
+
exclusiveMinimum,
|
|
2872
|
+
exclusiveMaximum,
|
|
2561
2873
|
multipleOf: schema.multipleOf
|
|
2562
2874
|
};
|
|
2563
2875
|
}
|
|
@@ -2566,11 +2878,11 @@ function getNumericConstraints(schema) {
|
|
|
2566
2878
|
*/
|
|
2567
2879
|
function isWithinBounds({ value, schema }) {
|
|
2568
2880
|
if (typeof value !== "number") return true;
|
|
2569
|
-
const { min, max,
|
|
2881
|
+
const { min, max, exclusive, multipleOf } = getNumericConstraints(schema);
|
|
2570
2882
|
if (min !== void 0 && value < min) return false;
|
|
2571
2883
|
if (max !== void 0 && value > max) return false;
|
|
2572
|
-
if (
|
|
2573
|
-
if (
|
|
2884
|
+
if (exclusive?.min !== void 0 && value <= exclusive.min) return false;
|
|
2885
|
+
if (exclusive?.max !== void 0 && value >= exclusive.max) return false;
|
|
2574
2886
|
if (multipleOf !== void 0 && Math.abs(value / multipleOf - Math.round(value / multipleOf)) > 1e-9) return false;
|
|
2575
2887
|
return true;
|
|
2576
2888
|
}
|
|
@@ -2728,6 +3040,15 @@ function convertFormattedEnum(context) {
|
|
|
2728
3040
|
}))
|
|
2729
3041
|
});
|
|
2730
3042
|
}
|
|
3043
|
+
function getStringContent({ schema, parse }) {
|
|
3044
|
+
const { contentMediaType: mediaType, contentEncoding: encoding, contentSchema } = schema;
|
|
3045
|
+
if (mediaType === void 0 && encoding === void 0 && !contentSchema) return {};
|
|
3046
|
+
return { content: {
|
|
3047
|
+
...mediaType !== void 0 && { mediaType },
|
|
3048
|
+
...encoding !== void 0 && { encoding },
|
|
3049
|
+
...contentSchema && { schema: parse({ schema: contentSchema }) }
|
|
3050
|
+
} };
|
|
3051
|
+
}
|
|
2731
3052
|
/**
|
|
2732
3053
|
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
2733
3054
|
*/
|
|
@@ -2738,7 +3059,8 @@ function convertString(context) {
|
|
|
2738
3059
|
primitive: "string",
|
|
2739
3060
|
min: schema.minLength,
|
|
2740
3061
|
max: schema.maxLength,
|
|
2741
|
-
pattern: schema.pattern
|
|
3062
|
+
pattern: schema.pattern,
|
|
3063
|
+
...getStringContent(context)
|
|
2742
3064
|
});
|
|
2743
3065
|
}
|
|
2744
3066
|
/**
|
|
@@ -2752,6 +3074,15 @@ function convertNumeric(context, type) {
|
|
|
2752
3074
|
});
|
|
2753
3075
|
}
|
|
2754
3076
|
// src/emit/converters/structural.ts
|
|
3077
|
+
function getContains(schema, parse) {
|
|
3078
|
+
const { contains, minContains, maxContains } = schema;
|
|
3079
|
+
if (!contains || typeof contains !== "object") return {};
|
|
3080
|
+
return { contains: {
|
|
3081
|
+
schema: parse({ schema: contains }),
|
|
3082
|
+
...typeof minContains === "number" && { min: minContains },
|
|
3083
|
+
...typeof maxContains === "number" && { max: maxContains }
|
|
3084
|
+
} };
|
|
3085
|
+
}
|
|
2755
3086
|
/**
|
|
2756
3087
|
* Resolves a `true` or empty-object map schema (`additionalProperties`/`patternProperties`) to
|
|
2757
3088
|
* `options.unknownType`, otherwise parses it as a regular schema.
|
|
@@ -2804,7 +3135,7 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
|
|
|
2804
3135
|
required
|
|
2805
3136
|
});
|
|
2806
3137
|
}) : [];
|
|
2807
|
-
const additionalProperties = schema.additionalProperties;
|
|
3138
|
+
const additionalProperties = schema.additionalProperties ?? schema.unevaluatedProperties;
|
|
2808
3139
|
const additionalPropertiesNode = (() => {
|
|
2809
3140
|
if (additionalProperties === true) return true;
|
|
2810
3141
|
if (additionalProperties) return resolveMapSchema(additionalProperties, {
|
|
@@ -2834,6 +3165,8 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
|
|
|
2834
3165
|
additionalProperties: additionalPropertiesNode,
|
|
2835
3166
|
patternProperties,
|
|
2836
3167
|
propertyNames,
|
|
3168
|
+
min: schema.minProperties,
|
|
3169
|
+
max: schema.maxProperties,
|
|
2837
3170
|
minProperties: schema.minProperties,
|
|
2838
3171
|
maxProperties: schema.maxProperties
|
|
2839
3172
|
});
|
|
@@ -2864,7 +3197,8 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse, r
|
|
|
2864
3197
|
*/
|
|
2865
3198
|
function convertTuple({ schema, name, nullable, defaultValue, options, parse }) {
|
|
2866
3199
|
const tupleItems = (schema.prefixItems ?? []).map((item) => parse({ schema: item }));
|
|
2867
|
-
const
|
|
3200
|
+
const tail = schema.items ?? schema.unevaluatedItems;
|
|
3201
|
+
const rest = tail === false ? void 0 : !tail || tail === true ? ast.factory.createSchema({ type: options.unknownType }) : parse({ schema: tail });
|
|
2868
3202
|
return createNode({
|
|
2869
3203
|
schema,
|
|
2870
3204
|
name,
|
|
@@ -2876,7 +3210,8 @@ function convertTuple({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2876
3210
|
items: tupleItems,
|
|
2877
3211
|
rest,
|
|
2878
3212
|
min: schema.minItems,
|
|
2879
|
-
max: schema.maxItems
|
|
3213
|
+
max: schema.maxItems,
|
|
3214
|
+
...getContains(schema, parse)
|
|
2880
3215
|
});
|
|
2881
3216
|
}
|
|
2882
3217
|
/**
|
|
@@ -2900,7 +3235,8 @@ function convertArray({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2900
3235
|
items,
|
|
2901
3236
|
min: schema.minItems,
|
|
2902
3237
|
max: schema.maxItems,
|
|
2903
|
-
unique: schema.uniqueItems ?? void 0
|
|
3238
|
+
unique: schema.uniqueItems ?? void 0,
|
|
3239
|
+
...getContains(schema, parse)
|
|
2904
3240
|
});
|
|
2905
3241
|
}
|
|
2906
3242
|
// src/emit/parseSchema.ts
|
|
@@ -3028,6 +3364,20 @@ const schemaRules = [
|
|
|
3028
3364
|
];
|
|
3029
3365
|
// src/parser.ts
|
|
3030
3366
|
/**
|
|
3367
|
+
* `unevaluated*` also sees what composition siblings evaluate, so with such a sibling it is dropped to keep the schema open.
|
|
3368
|
+
*/
|
|
3369
|
+
function hasUnevaluatedSiblings(schema) {
|
|
3370
|
+
if (schema.unevaluatedProperties === void 0 && schema.unevaluatedItems === void 0) return false;
|
|
3371
|
+
return [
|
|
3372
|
+
schema.allOf,
|
|
3373
|
+
schema.anyOf,
|
|
3374
|
+
schema.oneOf,
|
|
3375
|
+
schema.if,
|
|
3376
|
+
schema.dependentSchemas,
|
|
3377
|
+
schema.$ref
|
|
3378
|
+
].some((value) => value !== void 0);
|
|
3379
|
+
}
|
|
3380
|
+
/**
|
|
3031
3381
|
* Creates the schema converter bound to one OpenAPI document: `parseSchema` dispatches each
|
|
3032
3382
|
* schema through the ordered `schemaRules` table, with the parser options merged once.
|
|
3033
3383
|
*
|
|
@@ -3045,6 +3395,13 @@ function createSchemaParser(ctx) {
|
|
|
3045
3395
|
schema: flattenedSchema,
|
|
3046
3396
|
name
|
|
3047
3397
|
});
|
|
3398
|
+
if (hasUnevaluatedSiblings(schema)) {
|
|
3399
|
+
const { unevaluatedProperties: _properties, unevaluatedItems: _items, ...open } = schema;
|
|
3400
|
+
return parseSchema({
|
|
3401
|
+
schema: open,
|
|
3402
|
+
name
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3048
3405
|
const nullable = isNullable(schema) || void 0;
|
|
3049
3406
|
const context = {
|
|
3050
3407
|
schema,
|
|
@@ -3058,8 +3415,62 @@ function createSchemaParser(ctx) {
|
|
|
3058
3415
|
refs,
|
|
3059
3416
|
renames
|
|
3060
3417
|
};
|
|
3061
|
-
|
|
3062
|
-
return createNode(context, { type: options.emptySchemaType });
|
|
3418
|
+
const rule = schemaRules.find((entry) => entry.match(context));
|
|
3419
|
+
return withConditionals(rule ? rule.convert(context) : createNode(context, { type: options.emptySchemaType }), schema);
|
|
3420
|
+
}
|
|
3421
|
+
function requireKeys(keys) {
|
|
3422
|
+
const unknown = ast.factory.createSchema({ type: ast.schemaTypes.unknown });
|
|
3423
|
+
return ast.factory.createSchema({
|
|
3424
|
+
type: ast.schemaTypes.object,
|
|
3425
|
+
primitive: "object",
|
|
3426
|
+
properties: keys.map((name) => ast.factory.createProperty({
|
|
3427
|
+
name,
|
|
3428
|
+
required: true,
|
|
3429
|
+
schema: unknown
|
|
3430
|
+
}))
|
|
3431
|
+
});
|
|
3432
|
+
}
|
|
3433
|
+
/**
|
|
3434
|
+
* Adds `if`/`then`/`else`, `dependentRequired` and `dependentSchemas` to the node as `conditionals`.
|
|
3435
|
+
*/
|
|
3436
|
+
function withConditionals(node, schema) {
|
|
3437
|
+
const conditionals = [schema, ...Array.isArray(schema.allOf) ? schema.allOf.filter(isAnnotationMember) : []].flatMap(toConditionals);
|
|
3438
|
+
return conditionals.length ? {
|
|
3439
|
+
...node,
|
|
3440
|
+
conditionals
|
|
3441
|
+
} : node;
|
|
3442
|
+
}
|
|
3443
|
+
function toConditionals(schema) {
|
|
3444
|
+
const branch = (value) => {
|
|
3445
|
+
if (!value || typeof value !== "object") return void 0;
|
|
3446
|
+
const { required, type, ...rest } = value;
|
|
3447
|
+
if (Array.isArray(required) && required.length && (type === void 0 || type === "object") && !Object.keys(rest).length) return requireKeys(required);
|
|
3448
|
+
return parseSchema({ schema: value });
|
|
3449
|
+
};
|
|
3450
|
+
const entries = (value) => value && typeof value === "object" ? Object.entries(value) : [];
|
|
3451
|
+
const conditionals = [];
|
|
3452
|
+
const condition = branch(schema.if);
|
|
3453
|
+
if (condition) {
|
|
3454
|
+
const thenNode = branch(schema.then);
|
|
3455
|
+
const elseNode = branch(schema.else);
|
|
3456
|
+
conditionals.push({
|
|
3457
|
+
if: condition,
|
|
3458
|
+
...thenNode && { then: thenNode },
|
|
3459
|
+
...elseNode && { else: elseNode }
|
|
3460
|
+
});
|
|
3461
|
+
}
|
|
3462
|
+
for (const [key, required] of entries(schema.dependentRequired)) if (Array.isArray(required) && required.length && required.every((name) => typeof name === "string")) conditionals.push({
|
|
3463
|
+
if: requireKeys([key]),
|
|
3464
|
+
then: requireKeys(required)
|
|
3465
|
+
});
|
|
3466
|
+
for (const [key, dependent] of entries(schema.dependentSchemas)) {
|
|
3467
|
+
const thenNode = branch(dependent);
|
|
3468
|
+
if (thenNode) conditionals.push({
|
|
3469
|
+
if: requireKeys([key]),
|
|
3470
|
+
then: thenNode
|
|
3471
|
+
});
|
|
3472
|
+
}
|
|
3473
|
+
return conditionals;
|
|
3063
3474
|
}
|
|
3064
3475
|
return parseSchema;
|
|
3065
3476
|
}
|
|
@@ -3399,6 +3810,7 @@ const adapterOas = defineExtension({
|
|
|
3399
3810
|
schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
|
|
3400
3811
|
}
|
|
3401
3812
|
const operations = promotedEnums ? operationNodes.map((node) => refPromotedEnums(node, promotedEnums)) : operationNodes;
|
|
3813
|
+
const authSchemes = toAuthSchemes(document.components?.securitySchemes, refs);
|
|
3402
3814
|
return ast.factory.createInput({
|
|
3403
3815
|
schemas: schemaNodes,
|
|
3404
3816
|
operations,
|
|
@@ -3406,23 +3818,25 @@ const adapterOas = defineExtension({
|
|
|
3406
3818
|
title: document.info?.title,
|
|
3407
3819
|
description: document.info?.description,
|
|
3408
3820
|
version: document.info?.version,
|
|
3821
|
+
summary: document.info?.summary,
|
|
3822
|
+
termsOfService: document.info?.termsOfService,
|
|
3823
|
+
contact: pickStrings(document.info?.contact, [
|
|
3824
|
+
"name",
|
|
3825
|
+
"url",
|
|
3826
|
+
"email"
|
|
3827
|
+
]),
|
|
3828
|
+
license: typeof document.info?.license?.name === "string" ? {
|
|
3829
|
+
...pickStrings(document.info.license, ["identifier", "url"]),
|
|
3830
|
+
name: document.info.license.name
|
|
3831
|
+
} : void 0,
|
|
3832
|
+
externalDocs: getExternalDocs(document.externalDocs),
|
|
3409
3833
|
baseURL: resolveBaseUrl({
|
|
3410
3834
|
document,
|
|
3411
3835
|
server
|
|
3412
3836
|
}),
|
|
3413
3837
|
servers: toInputServers(document.servers ?? []),
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
return scheme?.type ? [{
|
|
3417
|
-
name,
|
|
3418
|
-
type: scheme.type,
|
|
3419
|
-
description: scheme.description,
|
|
3420
|
-
scheme: scheme.scheme,
|
|
3421
|
-
bearerFormat: scheme.bearerFormat,
|
|
3422
|
-
in: scheme.in,
|
|
3423
|
-
parameterName: scheme.name
|
|
3424
|
-
}] : [];
|
|
3425
|
-
}),
|
|
3838
|
+
...document.tags?.length && { tags: toInputTags(document.tags) },
|
|
3839
|
+
...authSchemes.length && { auth: authSchemes },
|
|
3426
3840
|
extensions: pickExtensions(document)
|
|
3427
3841
|
}
|
|
3428
3842
|
});
|
|
@@ -3463,18 +3877,19 @@ const adapterOas = defineExtension({
|
|
|
3463
3877
|
parsedDocument = document;
|
|
3464
3878
|
const refs = createRefs(document);
|
|
3465
3879
|
const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
|
|
3466
|
-
|
|
3880
|
+
const parseSchema = createSchemaParser({
|
|
3881
|
+
document,
|
|
3882
|
+
refs,
|
|
3883
|
+
renames,
|
|
3884
|
+
options: parserOptions
|
|
3885
|
+
});
|
|
3886
|
+
return groupUnsupportedFormats(() => parseInput({
|
|
3467
3887
|
document,
|
|
3468
3888
|
refs,
|
|
3469
3889
|
schemas,
|
|
3470
3890
|
pointers,
|
|
3471
|
-
parseSchema
|
|
3472
|
-
|
|
3473
|
-
refs,
|
|
3474
|
-
renames,
|
|
3475
|
-
options: parserOptions
|
|
3476
|
-
})
|
|
3477
|
-
});
|
|
3891
|
+
parseSchema
|
|
3892
|
+
}));
|
|
3478
3893
|
})();
|
|
3479
3894
|
inputCache.set(source, {
|
|
3480
3895
|
promise,
|