@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 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 OpenAPI 3.1 specifications and transforms them into a `@kubb/ast` `RootNode` for downstream code generation plugins.
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.15.4";
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
- return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
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 || [], `${pathPointer}/${operation.method.toLowerCase()}`);
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
- /** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
1389
- function getBodySchema({ content, contentType, refs }) {
1390
- const entry = contentType ? content?.[contentType] : void 0;
1391
- if (!entry) return null;
1392
- const binary = getBinaryFallbackSchema(contentType, entry.schema);
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 entry.schema ? refs.derefKeepingRef(entry.schema) : null;
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 on every path, in document order.
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
- function parseParameter({ param, pointer, parentName, options, parseSchema }) {
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 ?? (contentType ? param.content?.[contentType]?.schema : void 0);
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 = `#/paths/${escapePointerToken(operation.path)}/${operation.method.toLowerCase()}`;
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 node = parseSchema({
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)}/schema`);
1490
- const encoding = getEncoding(body?.content?.[ct]?.encoding);
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: _kubb_kit.ast.optionality(node, bodyRequired),
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)}/schema`);
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 mappedValues = findDiscriminators({
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 values = findDiscriminators({
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.macroSimplifyUnion], { depth: "shallow" });
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.1 specifications. Reads the
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,