@kubb/adapter-oas 5.17.0 → 5.17.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
@@ -19,7 +19,7 @@ var compatibility = {
19
19
  "node": ">=22.22"
20
20
  };
21
21
  // package.json
22
- var version = "5.17.0";
22
+ var version = "5.17.1";
23
23
  // src/constants.ts
24
24
  /**
25
25
  * Default parser options applied when no explicit options are provided.
@@ -429,17 +429,32 @@ function isBinary(schema) {
429
429
  return schema.contentMediaType === "application/octet-stream" && schema.contentEncoding === void 0 && (schema.type === void 0 || schema.type === "string");
430
430
  }
431
431
  /**
432
+ * Keywords that only annotate a schema. A schema with nothing else does not constrain the value.
433
+ */
434
+ const annotationKeywords = /* @__PURE__ */ new Set([
435
+ "title",
436
+ "description",
437
+ "default",
438
+ "example",
439
+ "examples",
440
+ "deprecated",
441
+ "readOnly",
442
+ "writeOnly",
443
+ "externalDocs"
444
+ ]);
445
+ /**
432
446
  * Returns the binary schema for a non-JSON entry the OAS 3.1 upgrade emptied out, or `undefined`
433
447
  * when the entry carries a schema of its own. The upgrade rewrites `format: 'binary'` into
434
448
  * `contentMediaType` and drops it whenever it only repeats the media type key, for any binary
435
449
  * media type (`application/octet-stream`, `application/pdf`, `image/png`, ...), not just
436
450
  * `application/octet-stream`. Without the fallback such a body resolves to the empty schema type
437
- * instead of a blob.
451
+ * instead of a blob. Annotations such as `description` survive the upgrade and are kept.
438
452
  */
439
453
  function getBinaryFallbackSchema(mediaType, schema) {
440
454
  if (!mediaType || isJsonMimeType(mediaType)) return void 0;
441
- if (schema && Object.keys(schema).length > 0) return void 0;
455
+ if (schema && Object.keys(schema).some((key) => !annotationKeywords.has(key) && !key.startsWith("x-"))) return void 0;
442
456
  return {
457
+ ...schema,
443
458
  type: "string",
444
459
  contentMediaType: "application/octet-stream"
445
460
  };
@@ -1409,6 +1424,39 @@ function flattenSchema(schema) {
1409
1424
  function scanSchema(node, pointer = node.pointer) {
1410
1425
  if (pointer) visit(node, pointer);
1411
1426
  }
1427
+ let formatTally;
1428
+ /**
1429
+ * Runs `fn` and reports `KUBB_UNSUPPORTED_FORMAT` once per format with the number of schemas that use it,
1430
+ * instead of once per schema. `format` is an open value in OpenAPI, so a large spec would otherwise print
1431
+ * thousands of warnings that say the same thing. `fn` must be synchronous.
1432
+ */
1433
+ function groupUnsupportedFormats(fn) {
1434
+ formatTally = /* @__PURE__ */ new Map();
1435
+ try {
1436
+ return fn();
1437
+ } finally {
1438
+ const tally = formatTally;
1439
+ formatTally = void 0;
1440
+ for (const [format, pointers] of tally) reportUnsupportedFormat(format, [...pointers]);
1441
+ }
1442
+ }
1443
+ function tallyFormat(format, pointer) {
1444
+ if (!formatTally) return reportUnsupportedFormat(format, [pointer]);
1445
+ formatTally.set(format, (formatTally.get(format) ?? /* @__PURE__ */ new Set()).add(pointer));
1446
+ }
1447
+ function reportUnsupportedFormat(format, pointers) {
1448
+ const count = pointers.length;
1449
+ Diagnostics.report({
1450
+ code: Diagnostics.code.unsupportedFormat,
1451
+ severity: "warning",
1452
+ message: count === 1 ? `Kubb does not map the format "${format}" to a specific type, so it falls back to the base type.` : `Kubb does not map the format "${format}" to a specific type, so ${count} schemas fall back to the base type.`,
1453
+ help: `Use a format Kubb supports, or handle "${format}" with a custom parser or plugin.`,
1454
+ location: {
1455
+ kind: "schema",
1456
+ pointer: pointers[0]
1457
+ }
1458
+ });
1459
+ }
1412
1460
  function visit(node, pointer) {
1413
1461
  node.pointer ??= pointer;
1414
1462
  if (node.deprecated) Diagnostics.report({
@@ -1420,16 +1468,7 @@ function visit(node, pointer) {
1420
1468
  pointer
1421
1469
  }
1422
1470
  });
1423
- if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
1424
- code: Diagnostics.code.unsupportedFormat,
1425
- severity: "warning",
1426
- message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
1427
- help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
1428
- location: {
1429
- kind: "schema",
1430
- pointer
1431
- }
1432
- });
1471
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) tallyFormat(node.format, pointer);
1433
1472
  if (node.type === "object") {
1434
1473
  for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
1435
1474
  if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
@@ -1538,18 +1577,6 @@ function getResponseByStatusCode({ operation, refs, statusCode }) {
1538
1577
  function getRequestBody({ operation, refs }) {
1539
1578
  return refs.deref(operation.schema.requestBody);
1540
1579
  }
1541
- /**
1542
- * Returns the primary request content type. Prefers a JSON-like media type (the last one wins
1543
- * when several are declared), then the first declared one, defaulting to `'application/json'`.
1544
- */
1545
- function getRequestContentType({ operation, refs }) {
1546
- const content = getRequestBody({
1547
- operation,
1548
- refs
1549
- })?.content;
1550
- const mediaTypes = content ? Object.keys(content) : [];
1551
- return mediaTypes.findLast(isJsonMimeType) ?? mediaTypes[0] ?? "application/json";
1552
- }
1553
1580
  /** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
1554
1581
  function getParameters({ operation, refs }) {
1555
1582
  const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
@@ -1812,15 +1839,13 @@ function parseOperation({ operation, ...ctx }) {
1812
1839
  }) : {}
1813
1840
  };
1814
1841
  };
1815
- const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => ast.factory.createContent({
1842
+ const declaredContentTypes = Object.keys(response?.content ?? {});
1843
+ const responseContent = (contentType && declaredContentTypes.length ? [contentType] : declaredContentTypes).map((ct) => ast.factory.createContent({
1816
1844
  contentType: ct,
1817
1845
  ...parseEntrySchema(ct)
1818
1846
  }));
1819
1847
  if (responseContent.length === 0) responseContent.push(ast.factory.createContent({
1820
- contentType: getRequestContentType({
1821
- operation,
1822
- refs
1823
- }),
1848
+ contentType: "",
1824
1849
  ...parseEntrySchema(contentType)
1825
1850
  }));
1826
1851
  return ast.factory.createResponse({
@@ -2000,6 +2025,15 @@ function removeInheritedDiscriminator({ members, refs }) {
2000
2025
  });
2001
2026
  }
2002
2027
  /**
2028
+ * Whether a member can carry the discriminator property. A discriminator is only a hint, so an array
2029
+ * or primitive member keeps its own type instead of an intersection no value can satisfy.
2030
+ */
2031
+ function isObjectLike(member, refs) {
2032
+ const type = (isReference(member) ? refs.resolve(member.$ref, { report: false }) : member)?.type;
2033
+ if (type === void 0) return true;
2034
+ return Array.isArray(type) ? type.includes("object") : type === "object";
2035
+ }
2036
+ /**
2003
2037
  * Narrows each `oneOf`/`anyOf` member with its discriminant value, intersecting the member's own
2004
2038
  * node with either the shared-properties slice carrying that value, or a synthetic discriminant
2005
2039
  * literal. The referenced child schema's own definition is left untouched — narrowing happens only
@@ -2062,7 +2096,7 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
2062
2096
  schema: s,
2063
2097
  name
2064
2098
  });
2065
- if (!discriminatorValues.length || !discriminator) return memberNode;
2099
+ if (!discriminatorValues.length || !discriminator || !isObjectLike(s, refs)) return memberNode;
2066
2100
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
2067
2101
  propertyName: discriminator.propertyName,
2068
2102
  values: discriminatorValues
@@ -3463,18 +3497,19 @@ const adapterOas = defineExtension({
3463
3497
  parsedDocument = document;
3464
3498
  const refs = createRefs(document);
3465
3499
  const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
3466
- return parseInput({
3500
+ const parseSchema = createSchemaParser({
3501
+ document,
3502
+ refs,
3503
+ renames,
3504
+ options: parserOptions
3505
+ });
3506
+ return groupUnsupportedFormats(() => parseInput({
3467
3507
  document,
3468
3508
  refs,
3469
3509
  schemas,
3470
3510
  pointers,
3471
- parseSchema: createSchemaParser({
3472
- document,
3473
- refs,
3474
- renames,
3475
- options: parserOptions
3476
- })
3477
- });
3511
+ parseSchema
3512
+ }));
3478
3513
  })();
3479
3514
  inputCache.set(source, {
3480
3515
  promise,