@kubb/adapter-oas 5.15.4 → 5.16.1
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 +333 -35
- 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 +334 -36
- 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/README.md
CHANGED
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
### OpenAPI and Swagger adapter for Kubb
|
|
26
26
|
|
|
27
|
-
Parses and validates Swagger 2.0, OpenAPI 3.0, and
|
|
27
|
+
Parses and validates Swagger 2.0, OpenAPI 3.0, 3.1, and 3.2 specifications and transforms them into a `@kubb/ast` `RootNode` for downstream code generation plugins.
|
|
28
28
|
|
|
29
29
|
## Installation
|
|
30
30
|
|
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.1";
|
|
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
|
/**
|
|
@@ -889,6 +909,17 @@ function reportInvalidSpec(message) {
|
|
|
889
909
|
* ```
|
|
890
910
|
*/
|
|
891
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
|
+
}
|
|
892
923
|
const { compileErrors, validate } = await import("@readme/openapi-parser");
|
|
893
924
|
let result;
|
|
894
925
|
try {
|
|
@@ -918,6 +949,7 @@ function reportValidationErrors({ errors, additionalErrors }) {
|
|
|
918
949
|
* validating in place where worker threads are missing, as in edge runtimes.
|
|
919
950
|
*/
|
|
920
951
|
async function validateDocumentInWorker(document) {
|
|
952
|
+
if (isOpenApi32(document)) return validateDocument(document);
|
|
921
953
|
let message;
|
|
922
954
|
try {
|
|
923
955
|
const { Worker } = await import("node:worker_threads");
|
|
@@ -1188,6 +1220,123 @@ function resolveServerUrl(server, overrides) {
|
|
|
1188
1220
|
}
|
|
1189
1221
|
return url;
|
|
1190
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
|
+
}
|
|
1191
1340
|
// src/emit/schemaShape.ts
|
|
1192
1341
|
/**
|
|
1193
1342
|
* Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
|
|
@@ -1245,7 +1394,7 @@ function flattenSchema(schema) {
|
|
|
1245
1394
|
/**
|
|
1246
1395
|
* Links one freshly converted schema and its nested schemas to their RFC 6901 pointers
|
|
1247
1396
|
* (`#/components/schemas/Pet/properties/owner/properties/name`), and reports the advisory
|
|
1248
|
-
* diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) against them.
|
|
1397
|
+
* diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`, `KUBB_DUPLICATE_DISCRIMINATOR`) against them.
|
|
1249
1398
|
* Refs are not followed, so the resolved schema is handled under its own walk.
|
|
1250
1399
|
* Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
|
|
1251
1400
|
*/
|
|
@@ -1290,16 +1439,67 @@ function visit(node, pointer) {
|
|
|
1290
1439
|
for (const member of node.members ?? []) visit(member, `${pointer}/not`);
|
|
1291
1440
|
return;
|
|
1292
1441
|
}
|
|
1442
|
+
if (node.type === "union" && node.discriminatorPropertyName) reportDuplicateDiscriminators(node, node.discriminatorPropertyName, pointer);
|
|
1293
1443
|
if (node.type === "union" || node.type === "intersection") {
|
|
1294
1444
|
const keyword = node.type === "intersection" ? "allOf" : node.strategy === "any" ? "anyOf" : "oneOf";
|
|
1295
1445
|
for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/${keyword}/${index}`);
|
|
1296
1446
|
}
|
|
1297
1447
|
}
|
|
1448
|
+
/**
|
|
1449
|
+
* Reports a declared discriminator whose value pins more than one member. The spec cannot tell
|
|
1450
|
+
* those members apart, and Zod's `z.discriminatedUnion` throws on the first parse.
|
|
1451
|
+
* One diagnostic per union, since the build dedupes by code and pointer.
|
|
1452
|
+
*/
|
|
1453
|
+
function reportDuplicateDiscriminators(node, propertyName, pointer) {
|
|
1454
|
+
const keyword = node.strategy === "any" ? "anyOf" : "oneOf";
|
|
1455
|
+
const membersByValue = /* @__PURE__ */ new Map();
|
|
1456
|
+
for (const [index, member] of (node.members ?? []).entries()) {
|
|
1457
|
+
const label = memberName(member) ?? `${keyword}/${index}`;
|
|
1458
|
+
for (const value of memberValues(member, propertyName)) membersByValue.set(value, [...membersByValue.get(value) ?? [], label]);
|
|
1459
|
+
}
|
|
1460
|
+
const duplicates = [...membersByValue].filter(([, members]) => members.length > 1);
|
|
1461
|
+
if (!duplicates.length) return;
|
|
1462
|
+
_kubb_kit.Diagnostics.report({
|
|
1463
|
+
code: _kubb_kit.Diagnostics.code.duplicateDiscriminator,
|
|
1464
|
+
severity: "warning",
|
|
1465
|
+
message: duplicates.map(([value, members]) => `Discriminator "${propertyName}" value ${JSON.stringify(value)} matches more than one member (${members.join(", ")}).`).join(" "),
|
|
1466
|
+
help: `Give each member its own "${propertyName}" value, or drop the discriminator. Zod's z.discriminatedUnion throws on the first parse of this union.`,
|
|
1467
|
+
location: {
|
|
1468
|
+
kind: "schema",
|
|
1469
|
+
pointer
|
|
1470
|
+
}
|
|
1471
|
+
});
|
|
1472
|
+
}
|
|
1473
|
+
function memberValues(node, propertyName, seen = /* @__PURE__ */ new Set()) {
|
|
1474
|
+
if (seen.has(node)) return [];
|
|
1475
|
+
seen.add(node);
|
|
1476
|
+
const target = node.type === "ref" && node.schema ? node.schema : node;
|
|
1477
|
+
if (target.type === "union") return [...new Set(target.members?.flatMap((member) => memberValues(member, propertyName, seen)))];
|
|
1478
|
+
return intersect(_kubb_kit.ast.resolveSchemaProperties({
|
|
1479
|
+
node,
|
|
1480
|
+
propertyName
|
|
1481
|
+
}).map((property) => [..._kubb_kit.ast.getSchemaLiteralValues(property.schema)]));
|
|
1482
|
+
}
|
|
1483
|
+
function intersect(slices) {
|
|
1484
|
+
const pinned = slices.filter((values) => values.length > 0);
|
|
1485
|
+
return pinned.reduce((acc, values) => acc.filter((value) => values.includes(value)), pinned[0] ?? []);
|
|
1486
|
+
}
|
|
1487
|
+
function memberName(member) {
|
|
1488
|
+
if (member.type === "ref") return member.name ?? void 0;
|
|
1489
|
+
if (member.type === "intersection") return member.members?.map(memberName).find(Boolean);
|
|
1490
|
+
}
|
|
1298
1491
|
// src/operation.ts
|
|
1299
1492
|
function slugify(value) {
|
|
1300
1493
|
return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
1301
1494
|
}
|
|
1302
1495
|
/**
|
|
1496
|
+
* Returns the operation's JSON pointer, under `additionalOperations` for an OpenAPI 3.2 method outside the fixed fields.
|
|
1497
|
+
*/
|
|
1498
|
+
function getOperationPointer({ path, method }) {
|
|
1499
|
+
const pathPointer = `#/paths/${escapePointerToken(path)}`;
|
|
1500
|
+
return SUPPORTED_METHODS.has(method.toLowerCase()) ? `${pathPointer}/${method.toLowerCase()}` : `${pathPointer}/additionalOperations/${escapePointerToken(method)}`;
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1303
1503
|
* Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
|
|
1304
1504
|
*/
|
|
1305
1505
|
function getOperationId({ path, method, schema }) {
|
|
@@ -1352,7 +1552,7 @@ function getParameters({ operation, refs }) {
|
|
|
1352
1552
|
}] : [];
|
|
1353
1553
|
});
|
|
1354
1554
|
const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
|
|
1355
|
-
const operationParams = resolveParams(operation.schema?.parameters || [],
|
|
1555
|
+
const operationParams = resolveParams(operation.schema?.parameters || [], getOperationPointer(operation));
|
|
1356
1556
|
const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
|
|
1357
1557
|
const paramMap = /* @__PURE__ */ new Map();
|
|
1358
1558
|
for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
|
|
@@ -1385,17 +1585,59 @@ function getEncoding(encoding) {
|
|
|
1385
1585
|
});
|
|
1386
1586
|
return entries.length ? Object.fromEntries(entries) : void 0;
|
|
1387
1587
|
}
|
|
1388
|
-
/**
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1588
|
+
/**
|
|
1589
|
+
* Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
|
|
1590
|
+
*/
|
|
1591
|
+
function getMediaType({ content, contentType, refs }) {
|
|
1592
|
+
return contentType ? refs.deref(content?.[contentType]) : null;
|
|
1593
|
+
}
|
|
1594
|
+
/**
|
|
1595
|
+
* Returns the schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON
|
|
1596
|
+
* entry. For a stream this is the item schema, so `schema: string` next to `itemSchema` types one item.
|
|
1597
|
+
*/
|
|
1598
|
+
function getBodySchema({ content, contentType, refs, document }) {
|
|
1599
|
+
const entry = getMediaType({
|
|
1600
|
+
content,
|
|
1601
|
+
contentType,
|
|
1602
|
+
refs
|
|
1603
|
+
});
|
|
1604
|
+
if (!contentType || !entry) return null;
|
|
1605
|
+
const schema = getItemSchema({
|
|
1606
|
+
contentType,
|
|
1607
|
+
mediaType: entry,
|
|
1608
|
+
refs,
|
|
1609
|
+
document
|
|
1610
|
+
}) ?? entry.schema;
|
|
1611
|
+
const binary = entry.itemSchema ? void 0 : getBinaryFallbackSchema(contentType, schema);
|
|
1393
1612
|
if (binary) return binary;
|
|
1394
|
-
return
|
|
1613
|
+
return schema ? refs.derefKeepingRef(schema) : null;
|
|
1614
|
+
}
|
|
1615
|
+
/** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
|
|
1616
|
+
function getStreamField({ contentType, mediaType, refs, document, node }) {
|
|
1617
|
+
if (!isStream(contentType, mediaType)) return {};
|
|
1618
|
+
const raw = getItemSchema({
|
|
1619
|
+
contentType,
|
|
1620
|
+
mediaType,
|
|
1621
|
+
refs,
|
|
1622
|
+
document
|
|
1623
|
+
});
|
|
1624
|
+
return { stream: {
|
|
1625
|
+
...raw ? { schema: node } : {},
|
|
1626
|
+
...raw && isEventStream(contentType) ? { shape: getItemShape(raw, refs) } : {}
|
|
1627
|
+
} };
|
|
1628
|
+
}
|
|
1629
|
+
/** The media type keyword the body schema was read from, for the diagnostic pointer. */
|
|
1630
|
+
function schemaKeyword(mediaType) {
|
|
1631
|
+
return mediaType?.itemSchema ? "itemSchema" : "schema";
|
|
1632
|
+
}
|
|
1633
|
+
/** Pointer of a media type entry, following a `$ref` into `components.mediaTypes` so a diagnostic lands on the source. */
|
|
1634
|
+
function mediaTypePointer(content, contentType, fallback) {
|
|
1635
|
+
const entry = content?.[contentType];
|
|
1636
|
+
return isReference(entry) ? entry.$ref : fallback;
|
|
1395
1637
|
}
|
|
1396
1638
|
/**
|
|
1397
|
-
* Builds an `Operation` for every supported HTTP method
|
|
1398
|
-
* `x-` path keys and unresolvable path-item `$ref`s are skipped.
|
|
1639
|
+
* Builds an `Operation` for every supported HTTP method and every OpenAPI 3.2 `additionalOperations` entry on
|
|
1640
|
+
* every path, in document order. `x-` path keys and unresolvable path-item `$ref`s are skipped.
|
|
1399
1641
|
*/
|
|
1400
1642
|
function getOperations(document, refs) {
|
|
1401
1643
|
const operations = [];
|
|
@@ -1417,6 +1659,12 @@ function getOperations(document, refs) {
|
|
|
1417
1659
|
pathItem
|
|
1418
1660
|
});
|
|
1419
1661
|
}
|
|
1662
|
+
for (const [method, schema] of Object.entries(pathItem.additionalOperations ?? {})) if (schema && typeof schema === "object" && !SUPPORTED_METHODS.has(method.toLowerCase())) operations.push({
|
|
1663
|
+
path,
|
|
1664
|
+
method,
|
|
1665
|
+
schema,
|
|
1666
|
+
pathItem
|
|
1667
|
+
});
|
|
1420
1668
|
}
|
|
1421
1669
|
return operations;
|
|
1422
1670
|
}
|
|
@@ -1430,10 +1678,15 @@ function collectPropertyKeysByFlag(schema, flag) {
|
|
|
1430
1678
|
}
|
|
1431
1679
|
return keys.length ? keys : null;
|
|
1432
1680
|
}
|
|
1433
|
-
|
|
1681
|
+
/** Converts one parameter into a `ParameterNode`, naming an inline schema after the operation and the parameter. */
|
|
1682
|
+
function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
|
|
1434
1683
|
const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
|
|
1435
1684
|
const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
|
|
1436
|
-
const rawSchema = param.schema ?? (
|
|
1685
|
+
const rawSchema = param.schema ?? getMediaType({
|
|
1686
|
+
content: param.content,
|
|
1687
|
+
contentType,
|
|
1688
|
+
refs
|
|
1689
|
+
})?.schema;
|
|
1437
1690
|
const schema = rawSchema ? parseSchema({
|
|
1438
1691
|
schema: rawSchema,
|
|
1439
1692
|
name: schemaName
|
|
@@ -1456,16 +1709,17 @@ function parseParameter({ param, pointer, parentName, options, parseSchema }) {
|
|
|
1456
1709
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1457
1710
|
*/
|
|
1458
1711
|
function parseOperation({ operation, ...ctx }) {
|
|
1459
|
-
const { refs, contentType, options, parseSchema } = ctx;
|
|
1712
|
+
const { document, refs, contentType, options, parseSchema } = ctx;
|
|
1460
1713
|
const operationId = getOperationId(operation);
|
|
1461
1714
|
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
1462
|
-
const pointer =
|
|
1715
|
+
const pointer = getOperationPointer(operation);
|
|
1463
1716
|
const parameters = getParameters({
|
|
1464
1717
|
operation,
|
|
1465
1718
|
refs
|
|
1466
1719
|
}).map((entry) => parseParameter({
|
|
1467
1720
|
options,
|
|
1468
1721
|
parseSchema,
|
|
1722
|
+
refs,
|
|
1469
1723
|
...entry,
|
|
1470
1724
|
parentName: operationName
|
|
1471
1725
|
}));
|
|
@@ -1479,20 +1733,33 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1479
1733
|
const schema = getBodySchema({
|
|
1480
1734
|
content: body?.content,
|
|
1481
1735
|
contentType: ct,
|
|
1482
|
-
refs
|
|
1736
|
+
refs,
|
|
1737
|
+
document
|
|
1483
1738
|
});
|
|
1484
1739
|
if (!schema) return [];
|
|
1485
|
-
const
|
|
1740
|
+
const mediaType = getMediaType({
|
|
1741
|
+
content: body?.content,
|
|
1742
|
+
contentType: ct,
|
|
1743
|
+
refs
|
|
1744
|
+
});
|
|
1745
|
+
const node = _kubb_kit.ast.optionality(parseSchema({
|
|
1486
1746
|
schema,
|
|
1487
1747
|
name: requestBodyName
|
|
1488
|
-
});
|
|
1489
|
-
scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}
|
|
1490
|
-
const encoding = getEncoding(
|
|
1748
|
+
}), bodyRequired);
|
|
1749
|
+
scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
|
|
1750
|
+
const encoding = getEncoding(mediaType?.encoding);
|
|
1491
1751
|
return [_kubb_kit.ast.factory.createContent({
|
|
1492
1752
|
contentType: ct,
|
|
1493
|
-
schema:
|
|
1753
|
+
schema: node,
|
|
1494
1754
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
|
|
1495
|
-
...encoding && { encoding }
|
|
1755
|
+
...encoding && { encoding },
|
|
1756
|
+
...getStreamField({
|
|
1757
|
+
contentType: ct,
|
|
1758
|
+
mediaType,
|
|
1759
|
+
refs,
|
|
1760
|
+
document,
|
|
1761
|
+
node
|
|
1762
|
+
})
|
|
1496
1763
|
})];
|
|
1497
1764
|
});
|
|
1498
1765
|
const requestBody = content.length > 0 || body?.description ? {
|
|
@@ -1510,6 +1777,12 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1510
1777
|
const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
|
|
1511
1778
|
const parseEntrySchema = (ct) => {
|
|
1512
1779
|
const raw = getBodySchema({
|
|
1780
|
+
content: response?.content,
|
|
1781
|
+
contentType: ct,
|
|
1782
|
+
refs,
|
|
1783
|
+
document
|
|
1784
|
+
});
|
|
1785
|
+
const mediaType = getMediaType({
|
|
1513
1786
|
content: response?.content,
|
|
1514
1787
|
contentType: ct,
|
|
1515
1788
|
refs
|
|
@@ -1518,10 +1791,17 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1518
1791
|
schema: raw,
|
|
1519
1792
|
name: responseName
|
|
1520
1793
|
}) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType });
|
|
1521
|
-
if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}
|
|
1794
|
+
if (raw && ct) scanSchema(node, `${mediaTypePointer(response?.content, ct, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
|
|
1522
1795
|
return {
|
|
1523
1796
|
schema: node,
|
|
1524
|
-
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
1797
|
+
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
|
|
1798
|
+
...ct ? getStreamField({
|
|
1799
|
+
contentType: ct,
|
|
1800
|
+
mediaType,
|
|
1801
|
+
refs,
|
|
1802
|
+
document,
|
|
1803
|
+
node
|
|
1804
|
+
}) : {}
|
|
1525
1805
|
};
|
|
1526
1806
|
};
|
|
1527
1807
|
const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => _kubb_kit.ast.factory.createContent({
|
|
@@ -1552,7 +1832,7 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1552
1832
|
pointer,
|
|
1553
1833
|
operationId,
|
|
1554
1834
|
protocol: "http",
|
|
1555
|
-
method: operation.method.toUpperCase(),
|
|
1835
|
+
method: SUPPORTED_METHODS.has(operation.method) ? operation.method.toUpperCase() : operation.method,
|
|
1556
1836
|
path: operation.path,
|
|
1557
1837
|
tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
|
|
1558
1838
|
summary: pickDoc("summary") || void 0,
|
|
@@ -1647,6 +1927,14 @@ function createDiscriminantNode({ propertyName, values }) {
|
|
|
1647
1927
|
});
|
|
1648
1928
|
}
|
|
1649
1929
|
/**
|
|
1930
|
+
* Returns the `$ref` of an OpenAPI 3.2 `defaultMapping`, written as a schema name or a `$ref`; `undefined` when none.
|
|
1931
|
+
*/
|
|
1932
|
+
function getDefaultMappingRef(discriminator) {
|
|
1933
|
+
const value = discriminator?.defaultMapping;
|
|
1934
|
+
if (!value) return void 0;
|
|
1935
|
+
return value.includes("/") ? value : `${SCHEMA_REF_PREFIX}${value}`;
|
|
1936
|
+
}
|
|
1937
|
+
/**
|
|
1650
1938
|
* Returns every discriminator key whose mapping value matches `ref`, in mapping order.
|
|
1651
1939
|
*
|
|
1652
1940
|
* A mapping may point several keys at the same schema, in which case the member has to be
|
|
@@ -1742,13 +2030,15 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
|
|
|
1742
2030
|
}
|
|
1743
2031
|
return constrains(variant) ? null : value;
|
|
1744
2032
|
}
|
|
2033
|
+
const defaultRef = getDefaultMappingRef(discriminator);
|
|
1745
2034
|
return unionMembers.map((s) => {
|
|
1746
2035
|
const ref = isReference(s) ? s.$ref : void 0;
|
|
1747
|
-
const
|
|
2036
|
+
const isDefault = ref !== void 0 && ref === defaultRef;
|
|
2037
|
+
const mappedValues = isDefault ? [] : findDiscriminators({
|
|
1748
2038
|
mapping: discriminator?.mapping,
|
|
1749
2039
|
ref
|
|
1750
2040
|
});
|
|
1751
|
-
const implicitValue = mappedValues.length ? null : implicitDiscriminantValue(s);
|
|
2041
|
+
const implicitValue = mappedValues.length || isDefault ? null : implicitDiscriminantValue(s);
|
|
1752
2042
|
const propertyType = propertyTypeOf({
|
|
1753
2043
|
schema: s,
|
|
1754
2044
|
propertyName: discriminator?.propertyName ?? "",
|
|
@@ -1793,7 +2083,8 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
|
|
|
1793
2083
|
const parentUnion = deref?.oneOf ?? deref?.anyOf;
|
|
1794
2084
|
if (!deref || !discriminator || isDiscriminator(deref) && !parentUnion) return true;
|
|
1795
2085
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1796
|
-
const
|
|
2086
|
+
const isDefault = childRef === getDefaultMappingRef(discriminator);
|
|
2087
|
+
const values = isDefault ? [] : findDiscriminators({
|
|
1797
2088
|
mapping: discriminator.mapping,
|
|
1798
2089
|
ref: childRef
|
|
1799
2090
|
});
|
|
@@ -1816,7 +2107,7 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
|
|
|
1816
2107
|
})
|
|
1817
2108
|
});
|
|
1818
2109
|
}
|
|
1819
|
-
return !(parentUnion && (values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
|
|
2110
|
+
return !(parentUnion && (isDefault || values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
|
|
1820
2111
|
}),
|
|
1821
2112
|
discriminantValues
|
|
1822
2113
|
};
|
|
@@ -2138,10 +2429,12 @@ function convertUnion(context) {
|
|
|
2138
2429
|
nullable,
|
|
2139
2430
|
defaultValue
|
|
2140
2431
|
};
|
|
2141
|
-
const unionMembers = resolveUnionMembers([...schema.oneOf ?? [], ...schema.anyOf ?? []], schema.properties ?? {});
|
|
2142
|
-
const strategy = schema.oneOf ? "one" : "any";
|
|
2143
|
-
const explicitDiscriminatorPropertyName = isDiscriminator(schema) ? schema.discriminator.propertyName : void 0;
|
|
2144
2432
|
const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
2433
|
+
const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
|
|
2434
|
+
const defaultRef = getDefaultMappingRef(discriminator);
|
|
2435
|
+
const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
|
|
2436
|
+
const strategy = schema.oneOf ? "one" : "any";
|
|
2437
|
+
const explicitDiscriminatorPropertyName = discriminator?.propertyName;
|
|
2145
2438
|
const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
|
|
2146
2439
|
const sharedPropertiesNode = schema.properties ? parse({
|
|
2147
2440
|
schema: memberBaseSchema,
|
|
@@ -2194,7 +2487,7 @@ function convertUnion(context) {
|
|
|
2194
2487
|
members,
|
|
2195
2488
|
discriminatorPropertyName: explicitDiscriminatorPropertyName ?? _kubb_kit.ast.inferDiscriminatorPropertyName(members)
|
|
2196
2489
|
});
|
|
2197
|
-
return _kubb_kit.ast.applyMacros(unionNode, [_kubb_kit.
|
|
2490
|
+
return _kubb_kit.ast.applyMacros(unionNode, [_kubb_kit.macroDedupeUnion], { depth: "shallow" });
|
|
2198
2491
|
}
|
|
2199
2492
|
/**
|
|
2200
2493
|
* Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
|
|
@@ -2671,6 +2964,10 @@ const schemaRules = [
|
|
|
2671
2964
|
primitive: "string"
|
|
2672
2965
|
})
|
|
2673
2966
|
},
|
|
2967
|
+
{
|
|
2968
|
+
match: ({ schema, type }) => !type && !schema.enum?.length && (schema.contentMediaType !== void 0 || schema.contentSchema !== void 0),
|
|
2969
|
+
convert: convertString
|
|
2970
|
+
},
|
|
2674
2971
|
{
|
|
2675
2972
|
match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
|
|
2676
2973
|
convert: convertString
|
|
@@ -2964,7 +3261,7 @@ const locate = (source, location) => {
|
|
|
2964
3261
|
*/
|
|
2965
3262
|
const adapterOasName = "oas";
|
|
2966
3263
|
/**
|
|
2967
|
-
* Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.
|
|
3264
|
+
* Default Kubb adapter for OpenAPI 2.0, 3.0, 3.1, and 3.2 specifications. Reads the
|
|
2968
3265
|
* spec from `input` (a file path, URL, inline content, or parsed object), validates
|
|
2969
3266
|
* it, resolves the base URL, and converts every schema and operation into the
|
|
2970
3267
|
* universal AST that every downstream plugin consumes.
|
|
@@ -3038,6 +3335,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
3038
3335
|
const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
|
|
3039
3336
|
const operationNodes = getOperations(document, refs).map((operation) => parseOperation({
|
|
3040
3337
|
operation,
|
|
3338
|
+
document,
|
|
3041
3339
|
refs,
|
|
3042
3340
|
contentType,
|
|
3043
3341
|
options: parserOptions,
|