@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.js CHANGED
@@ -19,7 +19,7 @@ var compatibility = {
19
19
  "node": ">=22.22"
20
20
  };
21
21
  // package.json
22
- var version = "5.16.1";
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
  };
@@ -1204,6 +1219,30 @@ function resolveServerUrl(server, overrides) {
1204
1219
  }
1205
1220
  return url;
1206
1221
  }
1222
+ /**
1223
+ * Maps OpenAPI servers to `InputServer` entries: drops entries without a `url` and fills `{variable}` defaults.
1224
+ */
1225
+ function toInputServers(servers) {
1226
+ return servers.filter((entry) => entry.url).map(({ url, description, variables }) => {
1227
+ const declared = variables && Object.keys(variables).length > 0 ? { variables } : {};
1228
+ try {
1229
+ return {
1230
+ url: resolveServerUrl({
1231
+ url,
1232
+ variables
1233
+ }),
1234
+ description,
1235
+ ...declared
1236
+ };
1237
+ } catch {
1238
+ return {
1239
+ url,
1240
+ description,
1241
+ ...declared
1242
+ };
1243
+ }
1244
+ });
1245
+ }
1207
1246
  // src/model/mediaType.ts
1208
1247
  /**
1209
1248
  * The media type without its parameters, so `text/event-stream; charset=utf-8` matches the bare name.
@@ -1385,6 +1424,39 @@ function flattenSchema(schema) {
1385
1424
  function scanSchema(node, pointer = node.pointer) {
1386
1425
  if (pointer) visit(node, pointer);
1387
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
+ }
1388
1460
  function visit(node, pointer) {
1389
1461
  node.pointer ??= pointer;
1390
1462
  if (node.deprecated) Diagnostics.report({
@@ -1396,16 +1468,7 @@ function visit(node, pointer) {
1396
1468
  pointer
1397
1469
  }
1398
1470
  });
1399
- if (typeof node.format === "string" && !isHandledFormat(node.format)) Diagnostics.report({
1400
- code: Diagnostics.code.unsupportedFormat,
1401
- severity: "warning",
1402
- message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
1403
- help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
1404
- location: {
1405
- kind: "schema",
1406
- pointer
1407
- }
1408
- });
1471
+ if (typeof node.format === "string" && !isHandledFormat(node.format)) tallyFormat(node.format, pointer);
1409
1472
  if (node.type === "object") {
1410
1473
  for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
1411
1474
  if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
@@ -1514,18 +1577,6 @@ function getResponseByStatusCode({ operation, refs, statusCode }) {
1514
1577
  function getRequestBody({ operation, refs }) {
1515
1578
  return refs.deref(operation.schema.requestBody);
1516
1579
  }
1517
- /**
1518
- * Returns the primary request content type. Prefers a JSON-like media type (the last one wins
1519
- * when several are declared), then the first declared one, defaulting to `'application/json'`.
1520
- */
1521
- function getRequestContentType({ operation, refs }) {
1522
- const content = getRequestBody({
1523
- operation,
1524
- refs
1525
- })?.content;
1526
- const mediaTypes = content ? Object.keys(content) : [];
1527
- return mediaTypes.findLast(isJsonMimeType) ?? mediaTypes[0] ?? "application/json";
1528
- }
1529
1580
  /** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
1530
1581
  function getParameters({ operation, refs }) {
1531
1582
  const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
@@ -1788,15 +1839,13 @@ function parseOperation({ operation, ...ctx }) {
1788
1839
  }) : {}
1789
1840
  };
1790
1841
  };
1791
- 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({
1792
1844
  contentType: ct,
1793
1845
  ...parseEntrySchema(ct)
1794
1846
  }));
1795
1847
  if (responseContent.length === 0) responseContent.push(ast.factory.createContent({
1796
- contentType: getRequestContentType({
1797
- operation,
1798
- refs
1799
- }),
1848
+ contentType: "",
1800
1849
  ...parseEntrySchema(contentType)
1801
1850
  }));
1802
1851
  return ast.factory.createResponse({
@@ -1812,6 +1861,7 @@ function parseOperation({ operation, ...ctx }) {
1812
1861
  const fallback = operation.pathItem[key];
1813
1862
  return typeof fallback === "string" ? fallback : void 0;
1814
1863
  };
1864
+ const servers = operation.schema.servers ?? operation.pathItem.servers;
1815
1865
  return ast.factory.createOperation({
1816
1866
  pointer,
1817
1867
  operationId,
@@ -1825,7 +1875,8 @@ function parseOperation({ operation, ...ctx }) {
1825
1875
  parameters,
1826
1876
  requestBody,
1827
1877
  responses,
1828
- extensions: pickExtensions(operation.schema)
1878
+ extensions: pickExtensions(operation.schema),
1879
+ ...servers && { servers: toInputServers(servers) }
1829
1880
  });
1830
1881
  }
1831
1882
  // src/emit/createNode.ts
@@ -1974,6 +2025,15 @@ function removeInheritedDiscriminator({ members, refs }) {
1974
2025
  });
1975
2026
  }
1976
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
+ /**
1977
2037
  * Narrows each `oneOf`/`anyOf` member with its discriminant value, intersecting the member's own
1978
2038
  * node with either the shared-properties slice carrying that value, or a synthetic discriminant
1979
2039
  * literal. The referenced child schema's own definition is left untouched — narrowing happens only
@@ -2036,7 +2096,7 @@ function narrowUnionMembers({ unionMembers, discriminator, sharedPropertiesNode,
2036
2096
  schema: s,
2037
2097
  name
2038
2098
  });
2039
- if (!discriminatorValues.length || !discriminator) return memberNode;
2099
+ if (!discriminatorValues.length || !discriminator || !isObjectLike(s, refs)) return memberNode;
2040
2100
  const narrowedDiscriminatorNode = sharedPropertiesNode ? pickDiscriminatorPropertyNode(ast.applyMacros(sharedPropertiesNode, [macroDiscriminatorEnum({
2041
2101
  propertyName: discriminator.propertyName,
2042
2102
  values: discriminatorValues
@@ -3384,25 +3444,7 @@ const adapterOas = defineExtension({
3384
3444
  document,
3385
3445
  server
3386
3446
  }),
3387
- servers: (document.servers ?? []).filter((entry) => entry.url).map(({ url, description, variables }) => {
3388
- const declared = variables && Object.keys(variables).length > 0 ? { variables } : {};
3389
- try {
3390
- return {
3391
- url: resolveServerUrl({
3392
- url,
3393
- variables
3394
- }),
3395
- description,
3396
- ...declared
3397
- };
3398
- } catch {
3399
- return {
3400
- url,
3401
- description,
3402
- ...declared
3403
- };
3404
- }
3405
- }),
3447
+ servers: toInputServers(document.servers ?? []),
3406
3448
  securitySchemes: Object.entries(document.components?.securitySchemes ?? {}).flatMap(([name, value]) => {
3407
3449
  const scheme = refs.deref(value);
3408
3450
  return scheme?.type ? [{
@@ -3455,18 +3497,19 @@ const adapterOas = defineExtension({
3455
3497
  parsedDocument = document;
3456
3498
  const refs = createRefs(document);
3457
3499
  const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
3458
- return parseInput({
3500
+ const parseSchema = createSchemaParser({
3501
+ document,
3502
+ refs,
3503
+ renames,
3504
+ options: parserOptions
3505
+ });
3506
+ return groupUnsupportedFormats(() => parseInput({
3459
3507
  document,
3460
3508
  refs,
3461
3509
  schemas,
3462
3510
  pointers,
3463
- parseSchema: createSchemaParser({
3464
- document,
3465
- refs,
3466
- renames,
3467
- options: parserOptions
3468
- })
3469
- });
3511
+ parseSchema
3512
+ }));
3470
3513
  })();
3471
3514
  inputCache.set(source, {
3472
3515
  promise,