@kubb/adapter-oas 5.16.1 → 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.cjs CHANGED
@@ -35,7 +35,7 @@ var compatibility = {
35
35
  "node": ">=22.22"
36
36
  };
37
37
  // package.json
38
- var version = "5.16.1";
38
+ var version = "5.17.1";
39
39
  // src/constants.ts
40
40
  /**
41
41
  * Default parser options applied when no explicit options are provided.
@@ -445,17 +445,32 @@ function isBinary(schema) {
445
445
  return schema.contentMediaType === "application/octet-stream" && schema.contentEncoding === void 0 && (schema.type === void 0 || schema.type === "string");
446
446
  }
447
447
  /**
448
+ * Keywords that only annotate a schema. A schema with nothing else does not constrain the value.
449
+ */
450
+ const annotationKeywords = /* @__PURE__ */ new Set([
451
+ "title",
452
+ "description",
453
+ "default",
454
+ "example",
455
+ "examples",
456
+ "deprecated",
457
+ "readOnly",
458
+ "writeOnly",
459
+ "externalDocs"
460
+ ]);
461
+ /**
448
462
  * Returns the binary schema for a non-JSON entry the OAS 3.1 upgrade emptied out, or `undefined`
449
463
  * when the entry carries a schema of its own. The upgrade rewrites `format: 'binary'` into
450
464
  * `contentMediaType` and drops it whenever it only repeats the media type key, for any binary
451
465
  * media type (`application/octet-stream`, `application/pdf`, `image/png`, ...), not just
452
466
  * `application/octet-stream`. Without the fallback such a body resolves to the empty schema type
453
- * instead of a blob.
467
+ * instead of a blob. Annotations such as `description` survive the upgrade and are kept.
454
468
  */
455
469
  function getBinaryFallbackSchema(mediaType, schema) {
456
470
  if (!mediaType || isJsonMimeType(mediaType)) return void 0;
457
- if (schema && Object.keys(schema).length > 0) return void 0;
471
+ if (schema && Object.keys(schema).some((key) => !annotationKeywords.has(key) && !key.startsWith("x-"))) return void 0;
458
472
  return {
473
+ ...schema,
459
474
  type: "string",
460
475
  contentMediaType: "application/octet-stream"
461
476
  };
@@ -1220,6 +1235,30 @@ function resolveServerUrl(server, overrides) {
1220
1235
  }
1221
1236
  return url;
1222
1237
  }
1238
+ /**
1239
+ * Maps OpenAPI servers to `InputServer` entries: drops entries without a `url` and fills `{variable}` defaults.
1240
+ */
1241
+ function toInputServers(servers) {
1242
+ return servers.filter((entry) => entry.url).map(({ url, description, variables }) => {
1243
+ const declared = variables && Object.keys(variables).length > 0 ? { variables } : {};
1244
+ try {
1245
+ return {
1246
+ url: resolveServerUrl({
1247
+ url,
1248
+ variables
1249
+ }),
1250
+ description,
1251
+ ...declared
1252
+ };
1253
+ } catch {
1254
+ return {
1255
+ url,
1256
+ description,
1257
+ ...declared
1258
+ };
1259
+ }
1260
+ });
1261
+ }
1223
1262
  // src/model/mediaType.ts
1224
1263
  /**
1225
1264
  * The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
@@ -1401,6 +1440,39 @@ function flattenSchema(schema) {
1401
1440
  function scanSchema(node, pointer = node.pointer) {
1402
1441
  if (pointer) visit(node, pointer);
1403
1442
  }
1443
+ let formatTally;
1444
+ /**
1445
+ * Runs `fn` and reports `KUBB_UNSUPPORTED_FORMAT` once per format with the number of schemas that use it,
1446
+ * instead of once per schema. `format` is an open value in OpenAPI, so a large spec would otherwise print
1447
+ * thousands of warnings that say the same thing. `fn` must be synchronous.
1448
+ */
1449
+ function groupUnsupportedFormats(fn) {
1450
+ formatTally = /* @__PURE__ */ new Map();
1451
+ try {
1452
+ return fn();
1453
+ } finally {
1454
+ const tally = formatTally;
1455
+ formatTally = void 0;
1456
+ for (const [format, pointers] of tally) reportUnsupportedFormat(format, [...pointers]);
1457
+ }
1458
+ }
1459
+ function tallyFormat(format, pointer) {
1460
+ if (!formatTally) return reportUnsupportedFormat(format, [pointer]);
1461
+ formatTally.set(format, (formatTally.get(format) ?? /* @__PURE__ */ new Set()).add(pointer));
1462
+ }
1463
+ function reportUnsupportedFormat(format, pointers) {
1464
+ const count = pointers.length;
1465
+ _kubb_kit.Diagnostics.report({
1466
+ code: _kubb_kit.Diagnostics.code.unsupportedFormat,
1467
+ severity: "warning",
1468
+ 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.`,
1469
+ help: `Use a format Kubb supports, or handle "${format}" with a custom parser or plugin.`,
1470
+ location: {
1471
+ kind: "schema",
1472
+ pointer: pointers[0]
1473
+ }
1474
+ });
1475
+ }
1404
1476
  function visit(node, pointer) {
1405
1477
  node.pointer ??= pointer;
1406
1478
  if (node.deprecated) _kubb_kit.Diagnostics.report({
@@ -1412,16 +1484,7 @@ function visit(node, pointer) {
1412
1484
  pointer
1413
1485
  }
1414
1486
  });
1415
- if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_kit.Diagnostics.report({
1416
- code: _kubb_kit.Diagnostics.code.unsupportedFormat,
1417
- severity: "warning",
1418
- message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
1419
- help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
1420
- location: {
1421
- kind: "schema",
1422
- pointer
1423
- }
1424
- });
1487
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) tallyFormat(node.format, pointer);
1425
1488
  if (node.type === "object") {
1426
1489
  for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
1427
1490
  if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
@@ -1530,18 +1593,6 @@ function getResponseByStatusCode({ operation, refs, statusCode }) {
1530
1593
  function getRequestBody({ operation, refs }) {
1531
1594
  return refs.deref(operation.schema.requestBody);
1532
1595
  }
1533
- /**
1534
- * Returns the primary request content type. Prefers a JSON-like media type (the last one wins
1535
- * when several are declared), then the first declared one, defaulting to `'application/json'`.
1536
- */
1537
- function getRequestContentType({ operation, refs }) {
1538
- const content = getRequestBody({
1539
- operation,
1540
- refs
1541
- })?.content;
1542
- const mediaTypes = content ? Object.keys(content) : [];
1543
- return mediaTypes.findLast(isJsonMimeType) ?? mediaTypes[0] ?? "application/json";
1544
- }
1545
1596
  /** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
1546
1597
  function getParameters({ operation, refs }) {
1547
1598
  const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
@@ -1804,15 +1855,13 @@ function parseOperation({ operation, ...ctx }) {
1804
1855
  }) : {}
1805
1856
  };
1806
1857
  };
1807
- const responseContent = (contentType ? [contentType] : Object.keys(response?.content ?? {})).map((ct) => _kubb_kit.ast.factory.createContent({
1858
+ const declaredContentTypes = Object.keys(response?.content ?? {});
1859
+ const responseContent = (contentType && declaredContentTypes.length ? [contentType] : declaredContentTypes).map((ct) => _kubb_kit.ast.factory.createContent({
1808
1860
  contentType: ct,
1809
1861
  ...parseEntrySchema(ct)
1810
1862
  }));
1811
1863
  if (responseContent.length === 0) responseContent.push(_kubb_kit.ast.factory.createContent({
1812
- contentType: getRequestContentType({
1813
- operation,
1814
- refs
1815
- }),
1864
+ contentType: "",
1816
1865
  ...parseEntrySchema(contentType)
1817
1866
  }));
1818
1867
  return _kubb_kit.ast.factory.createResponse({
@@ -1828,6 +1877,7 @@ function parseOperation({ operation, ...ctx }) {
1828
1877
  const fallback = operation.pathItem[key];
1829
1878
  return typeof fallback === "string" ? fallback : void 0;
1830
1879
  };
1880
+ const servers = operation.schema.servers ?? operation.pathItem.servers;
1831
1881
  return _kubb_kit.ast.factory.createOperation({
1832
1882
  pointer,
1833
1883
  operationId,
@@ -1841,7 +1891,8 @@ function parseOperation({ operation, ...ctx }) {
1841
1891
  parameters,
1842
1892
  requestBody,
1843
1893
  responses,
1844
- extensions: pickExtensions(operation.schema)
1894
+ extensions: pickExtensions(operation.schema),
1895
+ ...servers && { servers: toInputServers(servers) }
1845
1896
  });
1846
1897
  }
1847
1898
  // src/emit/createNode.ts
@@ -1990,6 +2041,15 @@ function removeInheritedDiscriminator({ members, refs }) {
1990
2041
  });
1991
2042
  }
1992
2043
  /**
2044
+ * Whether a member can carry the discriminator property. A discriminator is only a hint, so an array
2045
+ * or primitive member keeps its own type instead of an intersection no value can satisfy.
2046
+ */
2047
+ function isObjectLike(member, refs) {
2048
+ const type = (isReference(member) ? refs.resolve(member.$ref, { report: false }) : member)?.type;
2049
+ if (type === void 0) return true;
2050
+ return Array.isArray(type) ? type.includes("object") : type === "object";
2051
+ }
2052
+ /**
1993
2053
  * Narrows each `oneOf`/`anyOf` member with its discriminant value, intersecting the member's own
1994
2054
  * node with either the shared-properties slice carrying that value, or a synthetic discriminant
1995
2055
  * literal. The referenced child schema's own definition is left untouched — narrowing happens only
@@ -2052,7 +2112,7 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
2052
2112
  schema: s,
2053
2113
  name
2054
2114
  });
2055
- if (!discriminatorValues.length || !discriminator) return memberNode;
2115
+ if (!discriminatorValues.length || !discriminator || !isObjectLike(s, refs)) return memberNode;
2056
2116
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(_kubb_kit.ast.applyMacros(sharedPropertiesNode, [(0, _kubb_kit.macroDiscriminatorEnum)({
2057
2117
  propertyName: discriminator.propertyName,
2058
2118
  values: discriminatorValues
@@ -3399,25 +3459,7 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3399
3459
  document,
3400
3460
  server
3401
3461
  }),
3402
- servers: (document.servers ?? []).filter((entry) => entry.url).map(({ url, description, variables }) => {
3403
- const declared = variables && Object.keys(variables).length > 0 ? { variables } : {};
3404
- try {
3405
- return {
3406
- url: resolveServerUrl({
3407
- url,
3408
- variables
3409
- }),
3410
- description,
3411
- ...declared
3412
- };
3413
- } catch {
3414
- return {
3415
- url,
3416
- description,
3417
- ...declared
3418
- };
3419
- }
3420
- }),
3462
+ servers: toInputServers(document.servers ?? []),
3421
3463
  securitySchemes: Object.entries(document.components?.securitySchemes ?? {}).flatMap(([name, value]) => {
3422
3464
  const scheme = refs.deref(value);
3423
3465
  return scheme?.type ? [{
@@ -3470,18 +3512,19 @@ const adapterOas = (0, _kubb_core.defineExtension)({
3470
3512
  parsedDocument = document;
3471
3513
  const refs = createRefs(document);
3472
3514
  const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
3473
- return parseInput({
3515
+ const parseSchema = createSchemaParser({
3516
+ document,
3517
+ refs,
3518
+ renames,
3519
+ options: parserOptions
3520
+ });
3521
+ return groupUnsupportedFormats(() => parseInput({
3474
3522
  document,
3475
3523
  refs,
3476
3524
  schemas,
3477
3525
  pointers,
3478
- parseSchema: createSchemaParser({
3479
- document,
3480
- refs,
3481
- renames,
3482
- options: parserOptions
3483
- })
3484
- });
3526
+ parseSchema
3527
+ }));
3485
3528
  })();
3486
3529
  inputCache.set(source, {
3487
3530
  promise,