@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/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineExtension } from "@kubb/core";
2
- import { Diagnostics, ast, childName, enumPrimitive, enumPropName, extractRefName, macroDiscriminatorEnum, macroEnumName, macroSimplifyUnion, mergeAdjacentObjectsLazy } from "@kubb/kit";
2
+ import { Diagnostics, ast, childName, enumPrimitive, enumPropName, extractRefName, macroDedupeUnion, macroDiscriminatorEnum, macroEnumName, mergeAdjacentObjectsLazy } from "@kubb/kit";
3
3
  import { join } from "node:path";
4
4
  import { access, readFile } from "node:fs/promises";
5
5
  import { upgrade } from "@scalar/openapi-upgrader";
@@ -19,7 +19,7 @@ var compatibility = {
19
19
  "node": ">=22.22"
20
20
  };
21
21
  // package.json
22
- var version = "5.15.4";
22
+ var version = "5.16.1";
23
23
  // src/constants.ts
24
24
  /**
25
25
  * Default parser options applied when no explicit options are provided.
@@ -59,6 +59,17 @@ const SUPPORTED_METHODS = /* @__PURE__ */ new Set([
59
59
  "query"
60
60
  ]);
61
61
  /**
62
+ * Media types OpenAPI 3.2 reads as a stream of items (its "sequential media types"), each described by `itemSchema`.
63
+ */
64
+ const STREAM_MEDIA_TYPES = /* @__PURE__ */ new Set([
65
+ "application/jsonl",
66
+ "application/x-ndjson",
67
+ "application/json-seq",
68
+ "application/geo+json-seq",
69
+ "text/event-stream",
70
+ "multipart/mixed"
71
+ ]);
72
+ /**
62
73
  * Set of JSON Schema keywords that prevent a schema fragment from being inlined during `allOf` flattening.
63
74
  *
64
75
  * A fragment that contains any of these keys carries structural meaning of its own and must stay as a separate
@@ -383,6 +394,14 @@ function isNullable(schema) {
383
394
  return false;
384
395
  }
385
396
  /**
397
+ * Returns `true` for an OpenAPI 3.2 or later document. Swagger 2.0 and OpenAPI 3.0 documents are
398
+ * upgraded to 3.1 before parsing, so `openapi` is always set here.
399
+ */
400
+ function isOpenApi32(document) {
401
+ const [major = 0, minor = 0] = String(document.openapi).split(".").map(Number);
402
+ return major > 3 || major === 3 && minor >= 2;
403
+ }
404
+ /**
386
405
  * Returns the `x-*` keys of `source` as-is, or `undefined` when there are none.
387
406
  */
388
407
  function pickExtensions(source) {
@@ -449,7 +468,8 @@ const jsonMimeFragments = [
449
468
  * ```
450
469
  */
451
470
  function isJsonMimeType(mimeType) {
452
- return jsonMimeFragments.some((fragment) => mimeType.includes(fragment));
471
+ const lower = mimeType.toLowerCase();
472
+ return jsonMimeFragments.some((fragment) => lower.includes(fragment));
453
473
  }
454
474
  // src/refs.ts
455
475
  /**
@@ -873,6 +893,17 @@ function reportInvalidSpec(message) {
873
893
  * ```
874
894
  */
875
895
  async function validateDocument(document, { throwOnError = false } = {}) {
896
+ if (isOpenApi32(document)) {
897
+ const diagnostic = {
898
+ code: Diagnostics.code.invalidSpec,
899
+ severity: "warning",
900
+ message: "The document was not validated: `@readme/openapi-parser` does not support OpenAPI 3.2 yet.",
901
+ help: "Set `validate: false` on the adapter to skip this check until the validator supports OpenAPI 3.2."
902
+ };
903
+ if (throwOnError) throw new Diagnostics.Error(diagnostic);
904
+ Diagnostics.report(diagnostic);
905
+ return;
906
+ }
876
907
  const { compileErrors, validate } = await import("@readme/openapi-parser");
877
908
  let result;
878
909
  try {
@@ -902,6 +933,7 @@ function reportValidationErrors({ errors, additionalErrors }) {
902
933
  * validating in place where worker threads are missing, as in edge runtimes.
903
934
  */
904
935
  async function validateDocumentInWorker(document) {
936
+ if (isOpenApi32(document)) return validateDocument(document);
905
937
  let message;
906
938
  try {
907
939
  const { Worker } = await import("node:worker_threads");
@@ -1172,6 +1204,123 @@ function resolveServerUrl(server, overrides) {
1172
1204
  }
1173
1205
  return url;
1174
1206
  }
1207
+ // src/model/mediaType.ts
1208
+ /**
1209
+ * The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
1210
+ */
1211
+ function baseMediaType(contentType) {
1212
+ return contentType.split(";")[0].trim().toLowerCase();
1213
+ }
1214
+ /**
1215
+ * Returns `true` for a media type read as a stream of items: one that OpenAPI 3.2 lists, or any that declares `itemSchema`.
1216
+ */
1217
+ function isStream(contentType, mediaType) {
1218
+ return STREAM_MEDIA_TYPES.has(baseMediaType(contentType)) || mediaType?.itemSchema !== void 0;
1219
+ }
1220
+ /**
1221
+ * Whether the media type is `text/event-stream`, with or without parameters.
1222
+ */
1223
+ function isEventStream(contentType) {
1224
+ return baseMediaType(contentType) === "text/event-stream";
1225
+ }
1226
+ /**
1227
+ * Replaces a `data` string that holds JSON with its `contentSchema`, so the event reads as parsed.
1228
+ */
1229
+ function withParsedData(schema, refs) {
1230
+ const data = schema.properties?.data;
1231
+ const resolved = data && isReference(data) ? refs.resolve(data.$ref, { report: false }) : data;
1232
+ if (!resolved?.contentSchema || !resolved.contentMediaType || !isJsonMimeType(resolved.contentMediaType)) return schema;
1233
+ return {
1234
+ ...schema,
1235
+ properties: {
1236
+ ...schema.properties,
1237
+ data: resolved.contentSchema
1238
+ }
1239
+ };
1240
+ }
1241
+ /**
1242
+ * Keywords next to `$ref` that only annotate it, so a reference that carries nothing else stays a reference.
1243
+ */
1244
+ const REF_ANNOTATIONS = /* @__PURE__ */ new Set([
1245
+ "$ref",
1246
+ "summary",
1247
+ "description",
1248
+ "$comment"
1249
+ ]);
1250
+ /**
1251
+ * Applies `extra` on top of `base` the way JSON Schema does: `properties` and `required` add up, other keywords of `extra` win.
1252
+ */
1253
+ function mergeSchemas(base, extra) {
1254
+ const required = [.../* @__PURE__ */ new Set([...Array.isArray(base.required) ? base.required : [], ...Array.isArray(extra.required) ? extra.required : []])];
1255
+ return {
1256
+ type: "object",
1257
+ ...base,
1258
+ ...extra,
1259
+ properties: {
1260
+ ...base.properties,
1261
+ ...extra.properties
1262
+ },
1263
+ ...required.length ? { required } : {}
1264
+ };
1265
+ }
1266
+ /**
1267
+ * Normalizes the OpenAPI 3.2 `text/event-stream` item schema into one schema per event.
1268
+ * - Keywords next to a `$ref` apply to the referenced schema.
1269
+ * - Shared `properties` merge into each inline `oneOf`/`anyOf` member.
1270
+ * - A JSON `data` string becomes its `contentSchema`.
1271
+ */
1272
+ function toEventSchema(schema, refs) {
1273
+ if (isReference(schema)) {
1274
+ const siblings = Object.fromEntries(Object.entries(schema).filter(([key]) => !REF_ANNOTATIONS.has(key)));
1275
+ const resolved = Object.keys(siblings).length ? refs.resolve(schema.$ref, { report: false }) : null;
1276
+ return resolved ? toEventSchema(mergeSchemas(resolved, siblings), refs) : schema;
1277
+ }
1278
+ const key = schema.oneOf?.length ? "oneOf" : schema.anyOf?.length ? "anyOf" : void 0;
1279
+ if (!key) return withParsedData(schema, refs);
1280
+ const members = schema[key];
1281
+ const { properties, required, ...rest } = schema;
1282
+ const merge = !!properties && !members.some(isReference);
1283
+ const shared = {
1284
+ properties,
1285
+ required
1286
+ };
1287
+ return {
1288
+ ...merge ? rest : schema,
1289
+ [key]: members.map((member) => isReference(member) ? member : withParsedData(merge ? mergeSchemas(shared, member) : member, refs))
1290
+ };
1291
+ }
1292
+ /**
1293
+ * Whether the schema, following `$ref`s, or a composition member of it declares an `event` or `data` property.
1294
+ */
1295
+ function declaresEventFields(schema, refs, seen) {
1296
+ if (isReference(schema)) {
1297
+ if (seen.has(schema.$ref)) return false;
1298
+ seen.add(schema.$ref);
1299
+ const resolved = refs.resolve(schema.$ref, { report: false });
1300
+ return !!resolved && declaresEventFields(resolved, refs, seen);
1301
+ }
1302
+ if (schema.properties && (Object.hasOwn(schema.properties, "event") || Object.hasOwn(schema.properties, "data"))) return true;
1303
+ return [
1304
+ ...schema.oneOf ?? [],
1305
+ ...schema.anyOf ?? [],
1306
+ ...schema.allOf ?? []
1307
+ ].some((member) => declaresEventFields(member, refs, seen));
1308
+ }
1309
+ /**
1310
+ * Returns `'event'` when the item declares `event` or `data`, directly or in a composition member, else `'data'`.
1311
+ */
1312
+ function getItemShape(schema, refs) {
1313
+ return declaresEventFields(schema, refs, /* @__PURE__ */ new Set()) ? "event" : "data";
1314
+ }
1315
+ /**
1316
+ * Returns the raw item schema: `itemSchema`, or `schema` on `text/event-stream` as 3.0 and 3.1 documents write one event.
1317
+ */
1318
+ function getItemSchema({ contentType, mediaType, refs, document }) {
1319
+ if (!mediaType || !isStream(contentType, mediaType)) return null;
1320
+ const raw = mediaType.itemSchema ?? (isEventStream(contentType) && !isOpenApi32(document) ? mediaType.schema : void 0);
1321
+ if (!raw) return null;
1322
+ return isEventStream(contentType) ? toEventSchema(raw, refs) : raw;
1323
+ }
1175
1324
  // src/emit/schemaShape.ts
1176
1325
  /**
1177
1326
  * Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
@@ -1229,7 +1378,7 @@ function flattenSchema(schema) {
1229
1378
  /**
1230
1379
  * Links one freshly converted schema and its nested schemas to their RFC 6901 pointers
1231
1380
  * (`#/components/schemas/Pet/properties/owner/properties/name`), and reports the advisory
1232
- * diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) against them.
1381
+ * diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`, `KUBB_DUPLICATE_DISCRIMINATOR`) against them.
1233
1382
  * Refs are not followed, so the resolved schema is handled under its own walk.
1234
1383
  * Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
1235
1384
  */
@@ -1274,16 +1423,67 @@ function visit(node, pointer) {
1274
1423
  for (const member of node.members ?? []) visit(member, `${pointer}/not`);
1275
1424
  return;
1276
1425
  }
1426
+ if (node.type === "union" && node.discriminatorPropertyName) reportDuplicateDiscriminators(node, node.discriminatorPropertyName, pointer);
1277
1427
  if (node.type === "union" || node.type === "intersection") {
1278
1428
  const keyword = node.type === "intersection" ? "allOf" : node.strategy === "any" ? "anyOf" : "oneOf";
1279
1429
  for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/${keyword}/${index}`);
1280
1430
  }
1281
1431
  }
1432
+ /**
1433
+ * Reports a declared discriminator whose value pins more than one member. The spec cannot tell
1434
+ * those members apart, and Zod's `z.discriminatedUnion` throws on the first parse.
1435
+ * One diagnostic per union, since the build dedupes by code and pointer.
1436
+ */
1437
+ function reportDuplicateDiscriminators(node, propertyName, pointer) {
1438
+ const keyword = node.strategy === "any" ? "anyOf" : "oneOf";
1439
+ const membersByValue = /* @__PURE__ */ new Map();
1440
+ for (const [index, member] of (node.members ?? []).entries()) {
1441
+ const label = memberName(member) ?? `${keyword}/${index}`;
1442
+ for (const value of memberValues(member, propertyName)) membersByValue.set(value, [...membersByValue.get(value) ?? [], label]);
1443
+ }
1444
+ const duplicates = [...membersByValue].filter(([, members]) => members.length > 1);
1445
+ if (!duplicates.length) return;
1446
+ Diagnostics.report({
1447
+ code: Diagnostics.code.duplicateDiscriminator,
1448
+ severity: "warning",
1449
+ message: duplicates.map(([value, members]) => `Discriminator "${propertyName}" value ${JSON.stringify(value)} matches more than one member (${members.join(", ")}).`).join(" "),
1450
+ help: `Give each member its own "${propertyName}" value, or drop the discriminator. Zod's z.discriminatedUnion throws on the first parse of this union.`,
1451
+ location: {
1452
+ kind: "schema",
1453
+ pointer
1454
+ }
1455
+ });
1456
+ }
1457
+ function memberValues(node, propertyName, seen = /* @__PURE__ */ new Set()) {
1458
+ if (seen.has(node)) return [];
1459
+ seen.add(node);
1460
+ const target = node.type === "ref" && node.schema ? node.schema : node;
1461
+ if (target.type === "union") return [...new Set(target.members?.flatMap((member) => memberValues(member, propertyName, seen)))];
1462
+ return intersect(ast.resolveSchemaProperties({
1463
+ node,
1464
+ propertyName
1465
+ }).map((property) => [...ast.getSchemaLiteralValues(property.schema)]));
1466
+ }
1467
+ function intersect(slices) {
1468
+ const pinned = slices.filter((values) => values.length > 0);
1469
+ return pinned.reduce((acc, values) => acc.filter((value) => values.includes(value)), pinned[0] ?? []);
1470
+ }
1471
+ function memberName(member) {
1472
+ if (member.type === "ref") return member.name ?? void 0;
1473
+ if (member.type === "intersection") return member.members?.map(memberName).find(Boolean);
1474
+ }
1282
1475
  // src/operation.ts
1283
1476
  function slugify(value) {
1284
1477
  return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
1285
1478
  }
1286
1479
  /**
1480
+ * Returns the operation's JSON pointer, under `additionalOperations` for an OpenAPI 3.2 method outside the fixed fields.
1481
+ */
1482
+ function getOperationPointer({ path, method }) {
1483
+ const pathPointer = `#/paths/${escapePointerToken(path)}`;
1484
+ return SUPPORTED_METHODS.has(method.toLowerCase()) ? `${pathPointer}/${method.toLowerCase()}` : `${pathPointer}/additionalOperations/${escapePointerToken(method)}`;
1485
+ }
1486
+ /**
1287
1487
  * Returns the operation's `operationId`, falling back to `<method>_<slugified-path>` when absent.
1288
1488
  */
1289
1489
  function getOperationId({ path, method, schema }) {
@@ -1336,7 +1536,7 @@ function getParameters({ operation, refs }) {
1336
1536
  }] : [];
1337
1537
  });
1338
1538
  const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
1339
- const operationParams = resolveParams(operation.schema?.parameters || [], `${pathPointer}/${operation.method.toLowerCase()}`);
1539
+ const operationParams = resolveParams(operation.schema?.parameters || [], getOperationPointer(operation));
1340
1540
  const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
1341
1541
  const paramMap = /* @__PURE__ */ new Map();
1342
1542
  for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
@@ -1369,17 +1569,59 @@ function getEncoding(encoding) {
1369
1569
  });
1370
1570
  return entries.length ? Object.fromEntries(entries) : void 0;
1371
1571
  }
1372
- /** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
1373
- function getBodySchema({ content, contentType, refs }) {
1374
- const entry = contentType ? content?.[contentType] : void 0;
1375
- if (!entry) return null;
1376
- const binary = getBinaryFallbackSchema(contentType, entry.schema);
1572
+ /**
1573
+ * Returns the media type entry for `contentType`, resolving a `$ref` into `components.mediaTypes`. `null` when absent.
1574
+ */
1575
+ function getMediaType({ content, contentType, refs }) {
1576
+ return contentType ? refs.deref(content?.[contentType]) : null;
1577
+ }
1578
+ /**
1579
+ * Returns the schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON
1580
+ * entry. For a stream this is the item schema, so `schema: string` next to `itemSchema` types one item.
1581
+ */
1582
+ function getBodySchema({ content, contentType, refs, document }) {
1583
+ const entry = getMediaType({
1584
+ content,
1585
+ contentType,
1586
+ refs
1587
+ });
1588
+ if (!contentType || !entry) return null;
1589
+ const schema = getItemSchema({
1590
+ contentType,
1591
+ mediaType: entry,
1592
+ refs,
1593
+ document
1594
+ }) ?? entry.schema;
1595
+ const binary = entry.itemSchema ? void 0 : getBinaryFallbackSchema(contentType, schema);
1377
1596
  if (binary) return binary;
1378
- return entry.schema ? refs.derefKeepingRef(entry.schema) : null;
1597
+ return schema ? refs.derefKeepingRef(schema) : null;
1598
+ }
1599
+ /** The `stream` field of a content node, set when the media type is a stream. `node` is the parsed item schema. */
1600
+ function getStreamField({ contentType, mediaType, refs, document, node }) {
1601
+ if (!isStream(contentType, mediaType)) return {};
1602
+ const raw = getItemSchema({
1603
+ contentType,
1604
+ mediaType,
1605
+ refs,
1606
+ document
1607
+ });
1608
+ return { stream: {
1609
+ ...raw ? { schema: node } : {},
1610
+ ...raw && isEventStream(contentType) ? { shape: getItemShape(raw, refs) } : {}
1611
+ } };
1612
+ }
1613
+ /** The media type keyword the body schema was read from, for the diagnostic pointer. */
1614
+ function schemaKeyword(mediaType) {
1615
+ return mediaType?.itemSchema ? "itemSchema" : "schema";
1616
+ }
1617
+ /** Pointer of a media type entry, following a `$ref` into `components.mediaTypes` so a diagnostic lands on the source. */
1618
+ function mediaTypePointer(content, contentType, fallback) {
1619
+ const entry = content?.[contentType];
1620
+ return isReference(entry) ? entry.$ref : fallback;
1379
1621
  }
1380
1622
  /**
1381
- * Builds an `Operation` for every supported HTTP method on every path, in document order.
1382
- * `x-` path keys and unresolvable path-item `$ref`s are skipped.
1623
+ * Builds an `Operation` for every supported HTTP method and every OpenAPI 3.2 `additionalOperations` entry on
1624
+ * every path, in document order. `x-` path keys and unresolvable path-item `$ref`s are skipped.
1383
1625
  */
1384
1626
  function getOperations(document, refs) {
1385
1627
  const operations = [];
@@ -1401,6 +1643,12 @@ function getOperations(document, refs) {
1401
1643
  pathItem
1402
1644
  });
1403
1645
  }
1646
+ for (const [method, schema] of Object.entries(pathItem.additionalOperations ?? {})) if (schema && typeof schema === "object" && !SUPPORTED_METHODS.has(method.toLowerCase())) operations.push({
1647
+ path,
1648
+ method,
1649
+ schema,
1650
+ pathItem
1651
+ });
1404
1652
  }
1405
1653
  return operations;
1406
1654
  }
@@ -1414,10 +1662,15 @@ function collectPropertyKeysByFlag(schema, flag) {
1414
1662
  }
1415
1663
  return keys.length ? keys : null;
1416
1664
  }
1417
- function parseParameter({ param, pointer, parentName, options, parseSchema }) {
1665
+ /** Converts one parameter into a `ParameterNode`, naming an inline schema after the operation and the parameter. */
1666
+ function parseParameter({ param, pointer, parentName, options, parseSchema, refs }) {
1418
1667
  const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
1419
1668
  const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
1420
- const rawSchema = param.schema ?? (contentType ? param.content?.[contentType]?.schema : void 0);
1669
+ const rawSchema = param.schema ?? getMediaType({
1670
+ content: param.content,
1671
+ contentType,
1672
+ refs
1673
+ })?.schema;
1421
1674
  const schema = rawSchema ? parseSchema({
1422
1675
  schema: rawSchema,
1423
1676
  name: schemaName
@@ -1440,16 +1693,17 @@ function parseParameter({ param, pointer, parentName, options, parseSchema }) {
1440
1693
  * Converts an OAS `Operation` into an `OperationNode`.
1441
1694
  */
1442
1695
  function parseOperation({ operation, ...ctx }) {
1443
- const { refs, contentType, options, parseSchema } = ctx;
1696
+ const { document, refs, contentType, options, parseSchema } = ctx;
1444
1697
  const operationId = getOperationId(operation);
1445
1698
  const operationName = operationId ? pascalCase(operationId) : void 0;
1446
- const pointer = `#/paths/${escapePointerToken(operation.path)}/${operation.method.toLowerCase()}`;
1699
+ const pointer = getOperationPointer(operation);
1447
1700
  const parameters = getParameters({
1448
1701
  operation,
1449
1702
  refs
1450
1703
  }).map((entry) => parseParameter({
1451
1704
  options,
1452
1705
  parseSchema,
1706
+ refs,
1453
1707
  ...entry,
1454
1708
  parentName: operationName
1455
1709
  }));
@@ -1463,20 +1717,33 @@ function parseOperation({ operation, ...ctx }) {
1463
1717
  const schema = getBodySchema({
1464
1718
  content: body?.content,
1465
1719
  contentType: ct,
1466
- refs
1720
+ refs,
1721
+ document
1467
1722
  });
1468
1723
  if (!schema) return [];
1469
- const node = parseSchema({
1724
+ const mediaType = getMediaType({
1725
+ content: body?.content,
1726
+ contentType: ct,
1727
+ refs
1728
+ });
1729
+ const node = ast.optionality(parseSchema({
1470
1730
  schema,
1471
1731
  name: requestBodyName
1472
- });
1473
- scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}/schema`);
1474
- const encoding = getEncoding(body?.content?.[ct]?.encoding);
1732
+ }), bodyRequired);
1733
+ scanSchema(node, `${mediaTypePointer(body?.content, ct, `${pointer}/requestBody/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
1734
+ const encoding = getEncoding(mediaType?.encoding);
1475
1735
  return [ast.factory.createContent({
1476
1736
  contentType: ct,
1477
- schema: ast.optionality(node, bodyRequired),
1737
+ schema: node,
1478
1738
  keysToOmit: collectPropertyKeysByFlag(schema, "readOnly"),
1479
- ...encoding && { encoding }
1739
+ ...encoding && { encoding },
1740
+ ...getStreamField({
1741
+ contentType: ct,
1742
+ mediaType,
1743
+ refs,
1744
+ document,
1745
+ node
1746
+ })
1480
1747
  })];
1481
1748
  });
1482
1749
  const requestBody = content.length > 0 || body?.description ? {
@@ -1494,6 +1761,12 @@ function parseOperation({ operation, ...ctx }) {
1494
1761
  const responseName = operationName ? `${operationName}Status${statusCode}` : void 0;
1495
1762
  const parseEntrySchema = (ct) => {
1496
1763
  const raw = getBodySchema({
1764
+ content: response?.content,
1765
+ contentType: ct,
1766
+ refs,
1767
+ document
1768
+ });
1769
+ const mediaType = getMediaType({
1497
1770
  content: response?.content,
1498
1771
  contentType: ct,
1499
1772
  refs
@@ -1502,10 +1775,17 @@ function parseOperation({ operation, ...ctx }) {
1502
1775
  schema: raw,
1503
1776
  name: responseName
1504
1777
  }) : ast.factory.createSchema({ type: options.emptySchemaType });
1505
- if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}/schema`);
1778
+ if (raw && ct) scanSchema(node, `${mediaTypePointer(response?.content, ct, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}`)}/${schemaKeyword(mediaType)}`);
1506
1779
  return {
1507
1780
  schema: node,
1508
- keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
1781
+ keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly"),
1782
+ ...ct ? getStreamField({
1783
+ contentType: ct,
1784
+ mediaType,
1785
+ refs,
1786
+ document,
1787
+ node
1788
+ }) : {}
1509
1789
  };
1510
1790
  };
1511
1791
  const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => ast.factory.createContent({
@@ -1536,7 +1816,7 @@ function parseOperation({ operation, ...ctx }) {
1536
1816
  pointer,
1537
1817
  operationId,
1538
1818
  protocol: "http",
1539
- method: operation.method.toUpperCase(),
1819
+ method: SUPPORTED_METHODS.has(operation.method) ? operation.method.toUpperCase() : operation.method,
1540
1820
  path: operation.path,
1541
1821
  tags: Array.isArray(operation.schema.tags) ? operation.schema.tags.map(String) : [],
1542
1822
  summary: pickDoc("summary") || void 0,
@@ -1631,6 +1911,14 @@ function createDiscriminantNode({ propertyName, values }) {
1631
1911
  });
1632
1912
  }
1633
1913
  /**
1914
+ * Returns the `$ref` of an OpenAPI 3.2 `defaultMapping`, written as a schema name or a `$ref`; `undefined` when none.
1915
+ */
1916
+ function getDefaultMappingRef(discriminator) {
1917
+ const value = discriminator?.defaultMapping;
1918
+ if (!value) return void 0;
1919
+ return value.includes("/") ? value : `${SCHEMA_REF_PREFIX}${value}`;
1920
+ }
1921
+ /**
1634
1922
  * Returns every discriminator key whose mapping value matches `ref`, in mapping order.
1635
1923
  *
1636
1924
  * A mapping may point several keys at the same schema, in which case the member has to be
@@ -1726,13 +2014,15 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
1726
2014
  }
1727
2015
  return constrains(variant) ? null : value;
1728
2016
  }
2017
+ const defaultRef = getDefaultMappingRef(discriminator);
1729
2018
  return unionMembers.map((s) => {
1730
2019
  const ref = isReference(s) ? s.$ref : void 0;
1731
- const mappedValues = findDiscriminators({
2020
+ const isDefault = ref !== void 0 && ref === defaultRef;
2021
+ const mappedValues = isDefault ? [] : findDiscriminators({
1732
2022
  mapping: discriminator?.mapping,
1733
2023
  ref
1734
2024
  });
1735
- const implicitValue = mappedValues.length ? null : implicitDiscriminantValue(s);
2025
+ const implicitValue = mappedValues.length || isDefault ? null : implicitDiscriminantValue(s);
1736
2026
  const propertyType = propertyTypeOf({
1737
2027
  schema: s,
1738
2028
  propertyName: discriminator?.propertyName ?? "",
@@ -1777,7 +2067,8 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
1777
2067
  const parentUnion = deref?.oneOf ?? deref?.anyOf;
1778
2068
  if (!deref || !discriminator || isDiscriminator(deref) && !parentUnion) return true;
1779
2069
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1780
- const values = findDiscriminators({
2070
+ const isDefault = childRef === getDefaultMappingRef(discriminator);
2071
+ const values = isDefault ? [] : findDiscriminators({
1781
2072
  mapping: discriminator.mapping,
1782
2073
  ref: childRef
1783
2074
  });
@@ -1800,7 +2091,7 @@ function extractDiscriminatedAllOfMembers({ allOfMembers, name, refs }) {
1800
2091
  })
1801
2092
  });
1802
2093
  }
1803
- return !(parentUnion && (values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
2094
+ return !(parentUnion && (isDefault || values.length > 0 || parentUnion.some((member) => isReference(member) && member.$ref === childRef)));
1804
2095
  }),
1805
2096
  discriminantValues
1806
2097
  };
@@ -2122,10 +2413,12 @@ function convertUnion(context) {
2122
2413
  nullable,
2123
2414
  defaultValue
2124
2415
  };
2125
- const unionMembers = resolveUnionMembers([...schema.oneOf ?? [], ...schema.anyOf ?? []], schema.properties ?? {});
2126
- const strategy = schema.oneOf ? "one" : "any";
2127
- const explicitDiscriminatorPropertyName = isDiscriminator(schema) ? schema.discriminator.propertyName : void 0;
2128
2416
  const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
2417
+ const declaredMembers = [...schema.oneOf ?? [], ...schema.anyOf ?? []];
2418
+ const defaultRef = getDefaultMappingRef(discriminator);
2419
+ const unionMembers = resolveUnionMembers(defaultRef && !declaredMembers.some((member) => isReference(member) && member.$ref === defaultRef) ? [...declaredMembers, { $ref: defaultRef }] : declaredMembers, schema.properties ?? {});
2420
+ const strategy = schema.oneOf ? "one" : "any";
2421
+ const explicitDiscriminatorPropertyName = discriminator?.propertyName;
2129
2422
  const { oneOf: _o, anyOf: _a, discriminator: _d, ...memberBaseSchema } = schema;
2130
2423
  const sharedPropertiesNode = schema.properties ? parse({
2131
2424
  schema: memberBaseSchema,
@@ -2178,7 +2471,7 @@ function convertUnion(context) {
2178
2471
  members,
2179
2472
  discriminatorPropertyName: explicitDiscriminatorPropertyName ?? ast.inferDiscriminatorPropertyName(members)
2180
2473
  });
2181
- return ast.applyMacros(unionNode, [macroSimplifyUnion], { depth: "shallow" });
2474
+ return ast.applyMacros(unionNode, [macroDedupeUnion], { depth: "shallow" });
2182
2475
  }
2183
2476
  /**
2184
2477
  * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
@@ -2655,6 +2948,10 @@ const schemaRules = [
2655
2948
  primitive: "string"
2656
2949
  })
2657
2950
  },
2951
+ {
2952
+ match: ({ schema, type }) => !type && !schema.enum?.length && (schema.contentMediaType !== void 0 || schema.contentSchema !== void 0),
2953
+ convert: convertString
2954
+ },
2658
2955
  {
2659
2956
  match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
2660
2957
  convert: convertString
@@ -2949,7 +3246,7 @@ const locate = (source, location) => {
2949
3246
  */
2950
3247
  const adapterOasName = "oas";
2951
3248
  /**
2952
- * Default Kubb adapter for OpenAPI 2.0, 3.0, and 3.1 specifications. Reads the
3249
+ * Default Kubb adapter for OpenAPI 2.0, 3.0, 3.1, and 3.2 specifications. Reads the
2953
3250
  * spec from `input` (a file path, URL, inline content, or parsed object), validates
2954
3251
  * it, resolves the base URL, and converts every schema and operation into the
2955
3252
  * universal AST that every downstream plugin consumes.
@@ -3023,6 +3320,7 @@ const adapterOas = defineExtension({
3023
3320
  const discriminatorChildMap = discriminatorParentNodes.length > 0 ? buildDiscriminatorChildMap(discriminatorParentNodes) : null;
3024
3321
  const operationNodes = getOperations(document, refs).map((operation) => parseOperation({
3025
3322
  operation,
3323
+ document,
3026
3324
  refs,
3027
3325
  contentType,
3028
3326
  options: parserOptions,