@kubb/adapter-oas 5.15.3 → 5.16.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/README.md +1 -1
- package/dist/index.cjs +377 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +20 -8
- package/dist/index.d.ts +20 -8
- package/dist/index.js +378 -47
- package/dist/index.js.map +1 -1
- package/docs/options.md +1 -1
- package/kubb-extension.json +1 -1
- package/package.json +3 -3
package/dist/index.cjs
CHANGED
|
@@ -35,7 +35,7 @@ var compatibility = {
|
|
|
35
35
|
"node": ">=22.22"
|
|
36
36
|
};
|
|
37
37
|
// package.json
|
|
38
|
-
var version = "5.
|
|
38
|
+
var version = "5.16.0";
|
|
39
39
|
// src/constants.ts
|
|
40
40
|
/**
|
|
41
41
|
* Default parser options applied when no explicit options are provided.
|
|
@@ -75,6 +75,17 @@ const SUPPORTED_METHODS = /* @__PURE__ */ new Set([
|
|
|
75
75
|
"query"
|
|
76
76
|
]);
|
|
77
77
|
/**
|
|
78
|
+
* Media types OpenAPI 3.2 reads as a stream of items (its "sequential media types"), each described by `itemSchema`.
|
|
79
|
+
*/
|
|
80
|
+
const STREAM_MEDIA_TYPES = /* @__PURE__ */ new Set([
|
|
81
|
+
"application/jsonl",
|
|
82
|
+
"application/x-ndjson",
|
|
83
|
+
"application/json-seq",
|
|
84
|
+
"application/geo+json-seq",
|
|
85
|
+
"text/event-stream",
|
|
86
|
+
"multipart/mixed"
|
|
87
|
+
]);
|
|
88
|
+
/**
|
|
78
89
|
* Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
|
|
79
90
|
*
|
|
80
91
|
* A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
|
|
@@ -399,6 +410,14 @@ function isNullable(schema) {
|
|
|
399
410
|
return false;
|
|
400
411
|
}
|
|
401
412
|
/**
|
|
413
|
+
* Returns `true` for an OpenAPI 3.2 or later document. Swagger 2.0 and OpenAPI 3.0 documents are
|
|
414
|
+
* upgraded to 3.1 before parsing, so `openapi` is always set here.
|
|
415
|
+
*/
|
|
416
|
+
function isOpenApi32(document) {
|
|
417
|
+
const [major = 0, minor = 0] = String(document.openapi).split(".").map(Number);
|
|
418
|
+
return major > 3 || major === 3 && minor >= 2;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
402
421
|
* Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
|
|
403
422
|
*/
|
|
404
423
|
function pickExtensions(source) {
|
|
@@ -465,7 +484,8 @@ const jsonMimeFragments = [
|
|
|
465
484
|
* ```
|
|
466
485
|
*/
|
|
467
486
|
function isJsonMimeType(mimeType) {
|
|
468
|
-
|
|
487
|
+
const lower = mimeType.toLowerCase();
|
|
488
|
+
return jsonMimeFragments.some((fragment) => lower.includes(fragment));
|
|
469
489
|
}
|
|
470
490
|
// src/refs.ts
|
|
471
491
|
/**
|
|
@@ -631,8 +651,10 @@ async function readSource(sourcePath) {
|
|
|
631
651
|
async function resolveSource(sourcePath) {
|
|
632
652
|
const data = await readSource(sourcePath);
|
|
633
653
|
if (sourcePath.toLowerCase().endsWith(".md")) return data;
|
|
654
|
+
const pathname = urlRegExp.test(sourcePath) ? new URL(sourcePath).pathname : sourcePath;
|
|
634
655
|
return parseYaml(data, {
|
|
635
656
|
origin: sourcePath,
|
|
657
|
+
json: !/\.ya?ml$/i.test(pathname),
|
|
636
658
|
help: "Fix the syntax at the reported line, or check that `input` points at the spec and not an HTML page."
|
|
637
659
|
});
|
|
638
660
|
}
|
|
@@ -640,11 +662,19 @@ async function resolveSource(sourcePath) {
|
|
|
640
662
|
* Parses a YAML (or JSON) spec. A syntax error throws `KUBB_INVALID_DOCUMENT` naming `origin`,
|
|
641
663
|
* with the parser message, which keeps the line and column.
|
|
642
664
|
*
|
|
643
|
-
*
|
|
644
|
-
* `yaml
|
|
645
|
-
*
|
|
665
|
+
* With `json` (the default), a document that starts with `{` or `[` goes through `JSON.parse`
|
|
666
|
+
* first, about ten times faster than `js-yaml` on a JSON spec. A duplicate key then keeps the last
|
|
667
|
+
* value, as every JSON parser does. YAML flow syntax also starts with `{`, so a parse failure falls
|
|
668
|
+
* through to YAML. Pass `json: false` for a source known to be YAML.
|
|
669
|
+
*
|
|
670
|
+
* `js-yaml` parses about ten times faster than `yaml`, and rejects duplicate keys instead of
|
|
671
|
+
* keeping the last one. Its core schema keeps timestamps as strings and `<<` as a plain key, as
|
|
672
|
+
* `yaml` does for YAML 1.2.
|
|
646
673
|
*/
|
|
647
|
-
function parseYaml(data, { origin, help }) {
|
|
674
|
+
function parseYaml(data, { origin, help, json = true }) {
|
|
675
|
+
if (json && /^\s*[[{]/.test(data)) try {
|
|
676
|
+
return JSON.parse(data);
|
|
677
|
+
} catch {}
|
|
648
678
|
try {
|
|
649
679
|
const document = (0, js_yaml.load)(data, { schema: js_yaml.CORE_SCHEMA });
|
|
650
680
|
if (document !== void 0) return document;
|
|
@@ -879,6 +909,17 @@ function reportInvalidSpec(message) {
|
|
|
879
909
|
* ```
|
|
880
910
|
*/
|
|
881
911
|
async function validateDocument(document, { throwOnError = false } = {}) {
|
|
912
|
+
if (isOpenApi32(document)) {
|
|
913
|
+
const diagnostic = {
|
|
914
|
+
code: _kubb_kit.Diagnostics.code.invalidSpec,
|
|
915
|
+
severity: "warning",
|
|
916
|
+
message: "The document was not validated: `@readme/openapi-parser` does not support OpenAPI 3.2 yet.",
|
|
917
|
+
help: "Set `validate: false` on the adapter to skip this check until the validator supports OpenAPI 3.2."
|
|
918
|
+
};
|
|
919
|
+
if (throwOnError) throw new _kubb_kit.Diagnostics.Error(diagnostic);
|
|
920
|
+
_kubb_kit.Diagnostics.report(diagnostic);
|
|
921
|
+
return;
|
|
922
|
+
}
|
|
882
923
|
const { compileErrors, validate } = await import("@readme/openapi-parser");
|
|
883
924
|
let result;
|
|
884
925
|
try {
|
|
@@ -908,6 +949,7 @@ function reportValidationErrors({ errors, additionalErrors }) {
|
|
|
908
949
|
* validating in place where worker threads are missing, as in edge runtimes.
|
|
909
950
|
*/
|
|
910
951
|
async function validateDocumentInWorker(document) {
|
|
952
|
+
if (isOpenApi32(document)) return validateDocument(document);
|
|
911
953
|
let message;
|
|
912
954
|
try {
|
|
913
955
|
const { Worker } = await import("node:worker_threads");
|
|
@@ -1178,6 +1220,123 @@ function resolveServerUrl(server, overrides) {
|
|
|
1178
1220
|
}
|
|
1179
1221
|
return url;
|
|
1180
1222
|
}
|
|
1223
|
+
// src/model/mediaType.ts
|
|
1224
|
+
/**
|
|
1225
|
+
* The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
|
|
1226
|
+
*/
|
|
1227
|
+
function baseMediaType(contentType) {
|
|
1228
|
+
return contentType.split(";")[0].trim().toLowerCase();
|
|
1229
|
+
}
|
|
1230
|
+
/**
|
|
1231
|
+
* Returns `true` for a media type read as a stream of items: one that OpenAPI 3.2 lists, or any that declares `itemSchema`.
|
|
1232
|
+
*/
|
|
1233
|
+
function isStream(contentType, mediaType) {
|
|
1234
|
+
return STREAM_MEDIA_TYPES.has(baseMediaType(contentType)) || mediaType?.itemSchema !== void 0;
|
|
1235
|
+
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Whether the media type is `text/event-stream`, with or without parameters.
|
|
1238
|
+
*/
|
|
1239
|
+
function isEventStream(contentType) {
|
|
1240
|
+
return baseMediaType(contentType) === "text/event-stream";
|
|
1241
|
+
}
|
|
1242
|
+
/**
|
|
1243
|
+
* Replaces a `data` string that holds JSON with its `contentSchema`, so the event reads as parsed.
|
|
1244
|
+
*/
|
|
1245
|
+
function withParsedData(schema, refs) {
|
|
1246
|
+
const data = schema.properties?.data;
|
|
1247
|
+
const resolved = data && isReference(data) ? refs.resolve(data.$ref, { report: false }) : data;
|
|
1248
|
+
if (!resolved?.contentSchema || !resolved.contentMediaType || !isJsonMimeType(resolved.contentMediaType)) return schema;
|
|
1249
|
+
return {
|
|
1250
|
+
...schema,
|
|
1251
|
+
properties: {
|
|
1252
|
+
...schema.properties,
|
|
1253
|
+
data: resolved.contentSchema
|
|
1254
|
+
}
|
|
1255
|
+
};
|
|
1256
|
+
}
|
|
1257
|
+
/**
|
|
1258
|
+
* Keywords next to `$ref` that only annotate it, so a reference that carries nothing else stays a reference.
|
|
1259
|
+
*/
|
|
1260
|
+
const REF_ANNOTATIONS = /* @__PURE__ */ new Set([
|
|
1261
|
+
"$ref",
|
|
1262
|
+
"summary",
|
|
1263
|
+
"description",
|
|
1264
|
+
"$comment"
|
|
1265
|
+
]);
|
|
1266
|
+
/**
|
|
1267
|
+
* Applies `extra` on top of `base` the way JSON Schema does: `properties` and `required` add up, other keywords of `extra` win.
|
|
1268
|
+
*/
|
|
1269
|
+
function mergeSchemas(base, extra) {
|
|
1270
|
+
const required = [.../* @__PURE__ */ new Set([...Array.isArray(base.required) ? base.required : [], ...Array.isArray(extra.required) ? extra.required : []])];
|
|
1271
|
+
return {
|
|
1272
|
+
type: "object",
|
|
1273
|
+
...base,
|
|
1274
|
+
...extra,
|
|
1275
|
+
properties: {
|
|
1276
|
+
...base.properties,
|
|
1277
|
+
...extra.properties
|
|
1278
|
+
},
|
|
1279
|
+
...required.length ? { required } : {}
|
|
1280
|
+
};
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Normalizes the OpenAPI 3.2 `text/event-stream` item schema into one schema per event.
|
|
1284
|
+
* - Keywords next to a `$ref` apply to the referenced schema.
|
|
1285
|
+
* - Shared `properties` merge into each inline `oneOf`/`anyOf` member.
|
|
1286
|
+
* - A JSON `data` string becomes its `contentSchema`.
|
|
1287
|
+
*/
|
|
1288
|
+
function toEventSchema(schema, refs) {
|
|
1289
|
+
if (isReference(schema)) {
|
|
1290
|
+
const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => !REF_ANNOTATIONS.has(key)));
|
|
1291
|
+
const resolved = Object.keys(siblings).length ? refs.resolve(schema.$ref, { report: false }) : null;
|
|
1292
|
+
return resolved ? toEventSchema(mergeSchemas(resolved, siblings), refs) : schema;
|
|
1293
|
+
}
|
|
1294
|
+
const key = schema.oneOf?.length ? "oneOf" : schema.anyOf?.length ? "anyOf" : void 0;
|
|
1295
|
+
if (!key) return withParsedData(schema, refs);
|
|
1296
|
+
const members = schema[key];
|
|
1297
|
+
const { properties, required, ...rest } = schema;
|
|
1298
|
+
const merge = !!properties && !members.some(isReference);
|
|
1299
|
+
const shared = {
|
|
1300
|
+
properties,
|
|
1301
|
+
required
|
|
1302
|
+
};
|
|
1303
|
+
return {
|
|
1304
|
+
...merge ? rest : schema,
|
|
1305
|
+
[key]: members.map((member) => isReference(member) ? member : withParsedData(merge ? mergeSchemas(shared, member) : member, refs))
|
|
1306
|
+
};
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Whether the schema, following `$ref`s, or a composition member of it declares an `event` or `data` property.
|
|
1310
|
+
*/
|
|
1311
|
+
function declaresEventFields(schema, refs, seen) {
|
|
1312
|
+
if (isReference(schema)) {
|
|
1313
|
+
if (seen.has(schema.$ref)) return false;
|
|
1314
|
+
seen.add(schema.$ref);
|
|
1315
|
+
const resolved = refs.resolve(schema.$ref, { report: false });
|
|
1316
|
+
return !!resolved && declaresEventFields(resolved, refs, seen);
|
|
1317
|
+
}
|
|
1318
|
+
if (schema.properties && (Object.hasOwn(schema.properties, "event") || Object.hasOwn(schema.properties, "data"))) return true;
|
|
1319
|
+
return [
|
|
1320
|
+
...schema.oneOf ?? [],
|
|
1321
|
+
...schema.anyOf ?? [],
|
|
1322
|
+
...schema.allOf ?? []
|
|
1323
|
+
].some((member) => declaresEventFields(member, refs, seen));
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Returns `'event'` when the item declares `event` or `data`, directly or in a composition member, else `'data'`.
|
|
1327
|
+
*/
|
|
1328
|
+
function getItemShape(schema, refs) {
|
|
1329
|
+
return declaresEventFields(schema, refs, /* @__PURE__ */ new Set()) ? "event" : "data";
|
|
1330
|
+
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Returns the raw item schema: `itemSchema`, or `schema` on `text/event-stream` as 3.0 and 3.1 documents write one event.
|
|
1333
|
+
*/
|
|
1334
|
+
function getItemSchema({ contentType, mediaType, refs, document }) {
|
|
1335
|
+
if (!mediaType || !isStream(contentType, mediaType)) return null;
|
|
1336
|
+
const raw = mediaType.itemSchema ?? (isEventStream(contentType) && !isOpenApi32(document) ? mediaType.schema : void 0);
|
|
1337
|
+
if (!raw) return null;
|
|
1338
|
+
return isEventStream(contentType) ? toEventSchema(raw, refs) : raw;
|
|
1339
|
+
}
|
|
1181
1340
|
// src/emit/schemaShape.ts
|
|
1182
1341
|
/**
|
|
1183
1342
|
* Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
|
|
@@ -1290,6 +1449,13 @@ function slugify(value) {
|
|
|
1290
1449
|
return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
1291
1450
|
}
|
|
1292
1451
|
/**
|
|
1452
|
+
* Returns the operation's JSON pointer, under `additionalOperations` for an OpenAPI 3.2 method outside the fixed fields.
|
|
1453
|
+
*/
|
|
1454
|
+
function getOperationPointer({ path, method }) {
|
|
1455
|
+
const pathPointer = `#/paths/${escapePointerToken(path)}`;
|
|
1456
|
+
return SUPPORTED_METHODS.has(method.toLowerCase()) ? `${pathPointer}/${method.toLowerCase()}` : `${pathPointer}/additionalOperations/${escapePointerToken(method)}`;
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1293
1459
|
* Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
|
|
1294
1460
|
*/
|
|
1295
1461
|
function getOperationId({ path, method, schema }) {
|
|
@@ -1342,7 +1508,7 @@ function getParameters({ operation, refs }) {
|
|
|
1342
1508
|
}] : [];
|
|
1343
1509
|
});
|
|
1344
1510
|
const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
|
|
1345
|
-
const operationParams = resolveParams(operation.schema?.parameters || [],
|
|
1511
|
+
const operationParams = resolveParams(operation.schema?.parameters || [], getOperationPointer(operation));
|
|
1346
1512
|
const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
|
|
1347
1513
|
const paramMap = /* @__PURE__ */ new Map();
|
|
1348
1514
|
for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
|
|
@@ -1375,17 +1541,59 @@ function getEncoding(encoding) {
|
|
|
1375
1541
|
});
|
|
1376
1542
|
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
1377
1543
|
}
|
|
1378
|
-
/**
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1544
|
+
/**
|
|
1545
|
+
* Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
|
|
1546
|
+
*/
|
|
1547
|
+
function getMediaType({ content, contentType, refs }) {
|
|
1548
|
+
return contentType ? refs.deref(content?.[contentType]) : null;
|
|
1549
|
+
}
|
|
1550
|
+
/**
|
|
1551
|
+
* Returns the schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON
|
|
1552
|
+
* entry. For a stream this is the item schema, so `schema: string` next to `itemSchema` types one item.
|
|
1553
|
+
*/
|
|
1554
|
+
function getBodySchema({ content, contentType, refs, document }) {
|
|
1555
|
+
const entry = getMediaType({
|
|
1556
|
+
content,
|
|
1557
|
+
contentType,
|
|
1558
|
+
refs
|
|
1559
|
+
});
|
|
1560
|
+
if (!contentType || !entry) return null;
|
|
1561
|
+
const schema = getItemSchema({
|
|
1562
|
+
contentType,
|
|
1563
|
+
mediaType: entry,
|
|
1564
|
+
refs,
|
|
1565
|
+
document
|
|
1566
|
+
}) ?? entry.schema;
|
|
1567
|
+
const binary = entry.itemSchema ? void 0 : getBinaryFallbackSchema(contentType, schema);
|
|
1383
1568
|
if (binary) return binary;
|
|
1384
|
-
return
|
|
1569
|
+
return schema ? refs.derefKeepingRef(schema) : null;
|
|
1570
|
+
}
|
|
1571
|
+
/** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
|
|
1572
|
+
function getStreamField({ contentType, mediaType, refs, document, node }) {
|
|
1573
|
+
if (!isStream(contentType, mediaType)) return {};
|
|
1574
|
+
const raw = getItemSchema({
|
|
1575
|
+
contentType,
|
|
1576
|
+
mediaType,
|
|
1577
|
+
refs,
|
|
1578
|
+
document
|
|
1579
|
+
});
|
|
1580
|
+
return { stream: {
|
|
1581
|
+
...raw ? { schema: node } : {},
|
|
1582
|
+
...raw && isEventStream(contentType) ? { shape: getItemShape(raw, refs) } : {}
|
|
1583
|
+
} };
|
|
1584
|
+
}
|
|
1585
|
+
/** The media type keyword the body schema was read from, for the diagnostic pointer. */
|
|
1586
|
+
function schemaKeyword(mediaType) {
|
|
1587
|
+
return mediaType?.itemSchema ? "itemSchema" : "schema";
|
|
1588
|
+
}
|
|
1589
|
+
/** Pointer of a media type entry, following a `$ref` into `components.mediaTypes` so a diagnostic lands on the source. */
|
|
1590
|
+
function mediaTypePointer(content, contentType, fallback) {
|
|
1591
|
+
const entry = content?.[contentType];
|
|
1592
|
+
return isReference(entry) ? entry.$ref : fallback;
|
|
1385
1593
|
}
|
|
1386
1594
|
/**
|
|
1387
|
-
* Builds an `Operation` for every supported HTTP method
|
|
1388
|
-
* `x-` path keys and unresolvable path-item `$ref`s are skipped.
|
|
1595
|
+
* Builds an `Operation` for every supported HTTP method and every OpenAPI 3.2 `additionalOperations` entry on
|
|
1596
|
+
* every path, in document order. `x-` path keys and unresolvable path-item `$ref`s are skipped.
|
|
1389
1597
|
*/
|
|
1390
1598
|
function getOperations(document, refs) {
|
|
1391
1599
|
const operations = [];
|
|
@@ -1407,6 +1615,12 @@ function getOperations(document, refs) {
|
|
|
1407
1615
|
pathItem
|
|
1408
1616
|
});
|
|
1409
1617
|
}
|
|
1618
|
+
for (const [method, schema] of Object.entries(pathItem.additionalOperations ?? {})) if (schema && typeof schema === "object" && !SUPPORTED_METHODS.has(method.toLowerCase())) operations.push({
|
|
1619
|
+
path,
|
|
1620
|
+
method,
|
|
1621
|
+
schema,
|
|
1622
|
+
pathItem
|
|
1623
|
+
});
|
|
1410
1624
|
}
|
|
1411
1625
|
return operations;
|
|
1412
1626
|
}
|
|
@@ -1420,10 +1634,15 @@ function collectPropertyKeysByFlag(schema, flag) {
|
|
|
1420
1634
|
}
|
|
1421
1635
|
return keys.length ? keys : null;
|
|
1422
1636
|
}
|
|
1423
|
-
|
|
1637
|
+
/** Converts one parameter into a `ParameterNode`, naming an inline schema after the operation and the parameter. */
|
|
1638
|
+
function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
|
|
1424
1639
|
const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
|
|
1425
1640
|
const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
|
|
1426
|
-
const rawSchema = param.schema ?? (
|
|
1641
|
+
const rawSchema = param.schema ?? getMediaType({
|
|
1642
|
+
content: param.content,
|
|
1643
|
+
contentType,
|
|
1644
|
+
refs
|
|
1645
|
+
})?.schema;
|
|
1427
1646
|
const schema = rawSchema ? parseSchema({
|
|
1428
1647
|
schema: rawSchema,
|
|
1429
1648
|
name: schemaName
|
|
@@ -1446,16 +1665,17 @@ function parseParameter({ param, pointer, parentName, options, parseSchema }) {
|
|
|
1446
1665
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1447
1666
|
*/
|
|
1448
1667
|
function parseOperation({ operation, ...ctx }) {
|
|
1449
|
-
const { refs, contentType, options, parseSchema } = ctx;
|
|
1668
|
+
const { document, refs, contentType, options, parseSchema } = ctx;
|
|
1450
1669
|
const operationId = getOperationId(operation);
|
|
1451
1670
|
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
1452
|
-
const pointer =
|
|
1671
|
+
const pointer = getOperationPointer(operation);
|
|
1453
1672
|
const parameters = getParameters({
|
|
1454
1673
|
operation,
|
|
1455
1674
|
refs
|
|
1456
1675
|
}).map((entry) => parseParameter({
|
|
1457
1676
|
options,
|
|
1458
1677
|
parseSchema,
|
|
1678
|
+
refs,
|
|
1459
1679
|
...entry,
|
|
1460
1680
|
parentName: operationName
|
|
1461
1681
|
}));
|
|
@@ -1469,20 +1689,33 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1469
1689
|
const schema = getBodySchema({
|
|
1470
1690
|
content: body?.content,
|
|
1471
1691
|
contentType: ct,
|
|
1472
|
-
refs
|
|
1692
|
+
refs,
|
|
1693
|
+
document
|
|
1473
1694
|
});
|
|
1474
1695
|
if (!schema) return [];
|
|
1475
|
-
const
|
|
1696
|
+
const mediaType = getMediaType({
|
|
1697
|
+
content: body?.content,
|
|
1698
|
+
contentType: ct,
|
|
1699
|
+
refs
|
|
1700
|
+
});
|
|
1701
|
+
const node = _kubb_kit.ast.optionality(parseSchema({
|
|
1476
1702
|
schema,
|
|
1477
1703
|
name: requestBodyName
|
|
1478
|
-
});
|
|
1479
|
-
scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}
|
|
1480
|
-
const encoding = getEncoding(
|
|
1704
|
+
}), bodyRequired);
|
|
1705
|
+
scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
|
|
1706
|
+
const encoding = getEncoding(mediaType?.encoding);
|
|
1481
1707
|
return [_kubb_kit.ast.factory.createContent({
|
|
1482
1708
|
contentType: ct,
|
|
1483
|
-
schema:
|
|
1709
|
+
schema: node,
|
|
1484
1710
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
|
|
1485
|
-
...encoding && { encoding }
|
|
1711
|
+
...encoding && { encoding },
|
|
1712
|
+
...getStreamField({
|
|
1713
|
+
contentType: ct,
|
|
1714
|
+
mediaType,
|
|
1715
|
+
refs,
|
|
1716
|
+
document,
|
|
1717
|
+
node
|
|
1718
|
+
})
|
|
1486
1719
|
})];
|
|
1487
1720
|
});
|
|
1488
1721
|
const requestBody = content.length > 0 || body?.description ? {
|
|
@@ -1500,6 +1733,12 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1500
1733
|
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
1501
1734
|
const parseEntrySchema = (ct) => {
|
|
1502
1735
|
const raw = getBodySchema({
|
|
1736
|
+
content: response?.content,
|
|
1737
|
+
contentType: ct,
|
|
1738
|
+
refs,
|
|
1739
|
+
document
|
|
1740
|
+
});
|
|
1741
|
+
const mediaType = getMediaType({
|
|
1503
1742
|
content: response?.content,
|
|
1504
1743
|
contentType: ct,
|
|
1505
1744
|
refs
|
|
@@ -1508,10 +1747,17 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1508
1747
|
schema: raw,
|
|
1509
1748
|
name: responseName
|
|
1510
1749
|
}) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType });
|
|
1511
|
-
if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}
|
|
1750
|
+
if (raw && ct) scanSchema(node, `${mediaTypePointer(response?.content, ct, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
|
|
1512
1751
|
return {
|
|
1513
1752
|
schema: node,
|
|
1514
|
-
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
1753
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
|
|
1754
|
+
...ct ? getStreamField({
|
|
1755
|
+
contentType: ct,
|
|
1756
|
+
mediaType,
|
|
1757
|
+
refs,
|
|
1758
|
+
document,
|
|
1759
|
+
node
|
|
1760
|
+
}) : {}
|
|
1515
1761
|
};
|
|
1516
1762
|
};
|
|
1517
1763
|
const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => _kubb_kit.ast.factory.createContent({
|
|
@@ -1542,7 +1788,7 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1542
1788
|
pointer,
|
|
1543
1789
|
operationId,
|
|
1544
1790
|
protocol: "http",
|
|
1545
|
-
method: operation.method.toUpperCase(),
|
|
1791
|
+
method: SUPPORTED_METHODS.has(operation.method) ? operation.method.toUpperCase() : operation.method,
|
|
1546
1792
|
path: operation.path,
|
|
1547
1793
|
tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
|
|
1548
1794
|
summary: pickDoc("summary") || void 0,
|
|
@@ -1637,6 +1883,14 @@ function createDiscriminantNode({ propertyName, values }) {
|
|
|
1637
1883
|
});
|
|
1638
1884
|
}
|
|
1639
1885
|
/**
|
|
1886
|
+
* Returns the `$ref` of an OpenAPI 3.2 `defaultMapping`, written as a schema name or a `$ref`; `undefined` when none.
|
|
1887
|
+
*/
|
|
1888
|
+
function getDefaultMappingRef(discriminator) {
|
|
1889
|
+
const value = discriminator?.defaultMapping;
|
|
1890
|
+
if (!value) return void 0;
|
|
1891
|
+
return value.includes("/") ? value : `${SCHEMA_REF_PREFIX}${value}`;
|
|
1892
|
+
}
|
|
1893
|
+
/**
|
|
1640
1894
|
* Returns every discriminator key whose mapping value matches `ref`, in mapping order.
|
|
1641
1895
|
*
|
|
1642
1896
|
* A mapping may point several keys at the same schema, in which case the member has to be
|
|
@@ -1732,13 +1986,15 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
|
|
|
1732
1986
|
}
|
|
1733
1987
|
return constrains(variant) ? null : value;
|
|
1734
1988
|
}
|
|
1989
|
+
const defaultRef = getDefaultMappingRef(discriminator);
|
|
1735
1990
|
return unionMembers.map((s) => {
|
|
1736
1991
|
const ref = isReference(s) ? s.$ref : void 0;
|
|
1737
|
-
const
|
|
1992
|
+
const isDefault = ref !== void 0 && ref === defaultRef;
|
|
1993
|
+
const mappedValues = isDefault ? [] : findDiscriminators({
|
|
1738
1994
|
mapping: discriminator?.mapping,
|
|
1739
1995
|
ref
|
|
1740
1996
|
});
|
|
1741
|
-
const implicitValue = mappedValues.length ? null : implicitDiscriminantValue(s);
|
|
1997
|
+
const implicitValue = mappedValues.length || isDefault ? null : implicitDiscriminantValue(s);
|
|
1742
1998
|
const propertyType = propertyTypeOf({
|
|
1743
1999
|
schema: s,
|
|
1744
2000
|
propertyName: discriminator?.propertyName ?? "",
|
|
@@ -1783,7 +2039,8 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
|
|
|
1783
2039
|
const parentUnion = deref?.oneOf ?? deref?.anyOf;
|
|
1784
2040
|
if (!deref || !discriminator || isDiscriminator(deref) && !parentUnion) return true;
|
|
1785
2041
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1786
|
-
const
|
|
2042
|
+
const isDefault = childRef === getDefaultMappingRef(discriminator);
|
|
2043
|
+
const values = isDefault ? [] : findDiscriminators({
|
|
1787
2044
|
mapping: discriminator.mapping,
|
|
1788
2045
|
ref: childRef
|
|
1789
2046
|
});
|
|
@@ -1806,7 +2063,7 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
|
|
|
1806
2063
|
})
|
|
1807
2064
|
});
|
|
1808
2065
|
}
|
|
1809
|
-
return !(parentUnion && (values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
|
|
2066
|
+
return !(parentUnion && (isDefault || values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
|
|
1810
2067
|
}),
|
|
1811
2068
|
discriminantValues
|
|
1812
2069
|
};
|
|
@@ -2128,10 +2385,12 @@ function convertUnion(context) {
|
|
|
2128
2385
|
nullable,
|
|
2129
2386
|
defaultValue
|
|
2130
2387
|
};
|
|
2131
|
-
const unionMembers = resolveUnionMembers([...schema.oneOf ?? [], ...schema.anyOf ?? []], schema.properties ?? {});
|
|
2132
|
-
const strategy = schema.oneOf ? "one" : "any";
|
|
2133
|
-
const explicitDiscriminatorPropertyName = isDiscriminator(schema) ? schema.discriminator.propertyName : void 0;
|
|
2134
2388
|
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
2389
|
+
const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
2390
|
+
const defaultRef = getDefaultMappingRef(discriminator);
|
|
2391
|
+
const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
|
|
2392
|
+
const strategy = schema.oneOf ? "one" : "any";
|
|
2393
|
+
const explicitDiscriminatorPropertyName = discriminator?.propertyName;
|
|
2135
2394
|
const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
|
|
2136
2395
|
const sharedPropertiesNode = schema.properties ? parse({
|
|
2137
2396
|
schema: memberBaseSchema,
|
|
@@ -2184,7 +2443,7 @@ function convertUnion(context) {
|
|
|
2184
2443
|
members,
|
|
2185
2444
|
discriminatorPropertyName: explicitDiscriminatorPropertyName ?? _kubb_kit.ast.inferDiscriminatorPropertyName(members)
|
|
2186
2445
|
});
|
|
2187
|
-
return _kubb_kit.ast.applyMacros(unionNode, [_kubb_kit.
|
|
2446
|
+
return _kubb_kit.ast.applyMacros(unionNode, [_kubb_kit.macroDedupeUnion], { depth: "shallow" });
|
|
2188
2447
|
}
|
|
2189
2448
|
/**
|
|
2190
2449
|
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
@@ -2249,6 +2508,34 @@ function getNumericConstraints(schema) {
|
|
|
2249
2508
|
};
|
|
2250
2509
|
}
|
|
2251
2510
|
/**
|
|
2511
|
+
* Whether an enum value fits the schema's numeric bounds.
|
|
2512
|
+
*/
|
|
2513
|
+
function isWithinBounds({ value, schema }) {
|
|
2514
|
+
if (typeof value !== "number") return true;
|
|
2515
|
+
const { min, max, exclusiveMinimum, exclusiveMaximum, multipleOf } = getNumericConstraints(schema);
|
|
2516
|
+
if (min !== void 0 && value < min) return false;
|
|
2517
|
+
if (max !== void 0 && value > max) return false;
|
|
2518
|
+
if (exclusiveMinimum !== void 0 && value <= exclusiveMinimum) return false;
|
|
2519
|
+
if (exclusiveMaximum !== void 0 && value >= exclusiveMaximum) return false;
|
|
2520
|
+
if (multipleOf !== void 0 && Math.abs(value / multipleOf - Math.round(value / multipleOf)) > 1e-9) return false;
|
|
2521
|
+
return true;
|
|
2522
|
+
}
|
|
2523
|
+
/**
|
|
2524
|
+
* Builds the test for which `enum` entries stay: not `null`, and within the numeric bounds. `enum` and the bounds
|
|
2525
|
+
* apply together, so a value outside them is never valid. Bounds that exclude every value are a spec error, so
|
|
2526
|
+
* they are ignored rather than leaving an empty enum.
|
|
2527
|
+
*/
|
|
2528
|
+
function createEnumMemberFilter(schema) {
|
|
2529
|
+
const boundsApply = schema.enum.some((value) => value !== null && isWithinBounds({
|
|
2530
|
+
value,
|
|
2531
|
+
schema
|
|
2532
|
+
}));
|
|
2533
|
+
return (value) => value !== null && (!boundsApply || isWithinBounds({
|
|
2534
|
+
value,
|
|
2535
|
+
schema
|
|
2536
|
+
}));
|
|
2537
|
+
}
|
|
2538
|
+
/**
|
|
2252
2539
|
* Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
|
|
2253
2540
|
* and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
|
|
2254
2541
|
*/
|
|
@@ -2329,7 +2616,8 @@ function convertEnum({ schema, name, nullable, type, parse }) {
|
|
|
2329
2616
|
name
|
|
2330
2617
|
});
|
|
2331
2618
|
const nullInEnum = schema.enum.includes(null);
|
|
2332
|
-
const
|
|
2619
|
+
const isMember = createEnumMemberFilter(schema);
|
|
2620
|
+
const filteredValues = schema.enum.filter(isMember);
|
|
2333
2621
|
if (nullInEnum && filteredValues.length === 0) return createNullNode(schema, name);
|
|
2334
2622
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
2335
2623
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
@@ -2348,8 +2636,9 @@ function convertEnum({ schema, name, nullable, type, parse }) {
|
|
|
2348
2636
|
primitive,
|
|
2349
2637
|
enumValues: uniqueValues
|
|
2350
2638
|
});
|
|
2351
|
-
const
|
|
2352
|
-
const
|
|
2639
|
+
const memberSlots = (list) => Array.isArray(list) ? list.filter((_, index) => isMember(schema.enum[index])) : void 0;
|
|
2640
|
+
const rawEnumNames = extensionKey ? memberSlots(schema[extensionKey]) : void 0;
|
|
2641
|
+
const rawEnumDescriptions = descriptionKey ? memberSlots(schema[descriptionKey]) : void 0;
|
|
2353
2642
|
const seenNames = /* @__PURE__ */ new Set();
|
|
2354
2643
|
return createNode(ctx, {
|
|
2355
2644
|
type: "enum",
|
|
@@ -2367,6 +2656,25 @@ function convertEnum({ schema, name, nullable, type, parse }) {
|
|
|
2367
2656
|
});
|
|
2368
2657
|
}
|
|
2369
2658
|
/**
|
|
2659
|
+
* Converts an `enum` with a handled `format` into a union of its literal values. A named enum node would rename
|
|
2660
|
+
* the exported type (`Status` becomes `StatusKey`), so the union keeps the name the format type had.
|
|
2661
|
+
*/
|
|
2662
|
+
function convertFormattedEnum(context) {
|
|
2663
|
+
const { schema, nullable } = context;
|
|
2664
|
+
const values = [...new Set(schema.enum.filter(createEnumMemberFilter(schema)))];
|
|
2665
|
+
return createNode({
|
|
2666
|
+
...context,
|
|
2667
|
+
nullable: nullable || schema.enum.includes(null) || void 0
|
|
2668
|
+
}, {
|
|
2669
|
+
type: "union",
|
|
2670
|
+
members: values.map((value) => _kubb_kit.ast.factory.createSchema({
|
|
2671
|
+
type: "enum",
|
|
2672
|
+
primitive: getPrimitiveType(typeof value),
|
|
2673
|
+
enumValues: [value]
|
|
2674
|
+
}))
|
|
2675
|
+
});
|
|
2676
|
+
}
|
|
2677
|
+
/**
|
|
2370
2678
|
* Converts a `type: 'string'` schema into a `StringSchemaNode`.
|
|
2371
2679
|
*/
|
|
2372
2680
|
function convertString(context) {
|
|
@@ -2543,6 +2851,24 @@ function convertArray({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2543
2851
|
}
|
|
2544
2852
|
// src/emit/parseSchema.ts
|
|
2545
2853
|
/**
|
|
2854
|
+
* Whether the `format` rule owns the schema: a handled format, and for a date format a `dateType` that is not `false`.
|
|
2855
|
+
*/
|
|
2856
|
+
function matchesFormat({ schema, options }) {
|
|
2857
|
+
if (!schema.format) return false;
|
|
2858
|
+
if (schema.format === "date-time" || schema.format === "date" || schema.format === "time") return resolveDateTypeValue(options.dateType, schema.format) !== false;
|
|
2859
|
+
return isHandledFormat(schema.format);
|
|
2860
|
+
}
|
|
2861
|
+
/**
|
|
2862
|
+
* Whether the format prints as a plain `string` or `number`, so narrowing it to literal values cannot break callers.
|
|
2863
|
+
* `Date`, `bigint` and `Blob` outputs keep the format.
|
|
2864
|
+
*/
|
|
2865
|
+
function formatPrintsPlainType({ schema, options, type }) {
|
|
2866
|
+
const format = schema.format;
|
|
2867
|
+
if (format === "date-time" || format === "date" || format === "time") return resolveDateTypeValue(options.dateType, format) !== "date";
|
|
2868
|
+
if ((format === "int64" || format === "uint64") && type !== "string") return options.integerType !== "bigint";
|
|
2869
|
+
return formatMap[format] !== "blob";
|
|
2870
|
+
}
|
|
2871
|
+
/**
|
|
2546
2872
|
* Ordered schema rule table. Order is significant: composition keywords (`not`, `$ref`, `allOf`,
|
|
2547
2873
|
* `oneOf`/`anyOf`) take precedence over `const` (except a `$ref` with a sibling `const`, where the
|
|
2548
2874
|
* `const` wins), which takes precedence over a genuine OAS 3.1 multi-type array (more than one
|
|
@@ -2580,11 +2906,11 @@ const schemaRules = [
|
|
|
2580
2906
|
convert: convertMultiType
|
|
2581
2907
|
},
|
|
2582
2908
|
{
|
|
2583
|
-
match: (
|
|
2584
|
-
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2909
|
+
match: (ctx) => ctx.type !== "array" && !!ctx.schema.enum?.some((value) => value !== null) && matchesFormat(ctx) && formatPrintsPlainType(ctx),
|
|
2910
|
+
convert: convertFormattedEnum
|
|
2911
|
+
},
|
|
2912
|
+
{
|
|
2913
|
+
match: matchesFormat,
|
|
2588
2914
|
convert: convertFormat
|
|
2589
2915
|
},
|
|
2590
2916
|
{
|
|
@@ -2594,6 +2920,10 @@ const schemaRules = [
|
|
|
2594
2920
|
primitive: "string"
|
|
2595
2921
|
})
|
|
2596
2922
|
},
|
|
2923
|
+
{
|
|
2924
|
+
match: ({ schema, type }) => !type && !schema.enum?.length && (schema.contentMediaType !== void 0 || schema.contentSchema !== void 0),
|
|
2925
|
+
convert: convertString
|
|
2926
|
+
},
|
|
2597
2927
|
{
|
|
2598
2928
|
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
2599
2929
|
convert: convertString
|
|
@@ -2887,7 +3217,7 @@ const locate = (source, location) => {
|
|
|
2887
3217
|
*/
|
|
2888
3218
|
const adapterOasName = "oas";
|
|
2889
3219
|
/**
|
|
2890
|
-
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.
|
|
3220
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, 3.1, and 3.2 specifications. Reads the
|
|
2891
3221
|
* spec from `input` (a file path, URL, inline content, or parsed object), validates
|
|
2892
3222
|
* it, resolves the base URL, and converts every schema and operation into the
|
|
2893
3223
|
* universal AST that every downstream plugin consumes.
|
|
@@ -2961,6 +3291,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2961
3291
|
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
2962
3292
|
const operationNodes = getOperations(document, refs).map((operation) => parseOperation({
|
|
2963
3293
|
operation,
|
|
3294
|
+
document,
|
|
2964
3295
|
refs,
|
|
2965
3296
|
contentType,
|
|
2966
3297
|
options: parserOptions,
|