@kubb/adapter-oas 5.0.0-beta.29 → 5.0.0-beta.30

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
@@ -713,6 +713,35 @@ function dereferenceWithRef(document, schema) {
713
713
  return schema;
714
714
  }
715
715
  //#endregion
716
+ //#region src/dialect.ts
717
+ /**
718
+ * The OpenAPI / Swagger dialect — the default used by `@kubb/adapter-oas`.
719
+ *
720
+ * Implements the spec-agnostic {@link ast.SchemaDialect} contract: it isolates the
721
+ * decisions that differ between specs (nullability, `$ref`, discriminator, binary,
722
+ * ref resolution) so the converter pipeline and dispatch rules stay shared. A
723
+ * future adapter (e.g. AsyncAPI) ships its own dialect — `type: ['null', …]`
724
+ * nullability, no discriminator object, binary via `contentEncoding` — and reuses
725
+ * the rest unchanged.
726
+ *
727
+ * Formats (`uuid`, `email`, dates, …) are intentionally NOT here: they are shared
728
+ * JSON Schema vocabulary, so the converters keep that common case.
729
+ *
730
+ * @example
731
+ * ```ts
732
+ * const parser = createSchemaParser(context) // uses oasDialect
733
+ * const parser = createSchemaParser(context, oasDialect) // explicit
734
+ * ```
735
+ */
736
+ const oasDialect = ast.defineSchemaDialect({
737
+ name: "oas",
738
+ isNullable,
739
+ isReference,
740
+ isDiscriminator,
741
+ isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
742
+ resolveRef
743
+ });
744
+ //#endregion
716
745
  //#region src/resolvers.ts
717
746
  /**
718
747
  * Replaces `{variable}` placeholders in an OpenAPI server URL with provided values.
@@ -1142,7 +1171,7 @@ function normalizeArrayEnum(schema) {
1142
1171
  *
1143
1172
  * @internal
1144
1173
  */
1145
- function createSchemaParser(ctx) {
1174
+ function createSchemaParser(ctx, dialect = oasDialect) {
1146
1175
  const document = ctx.document;
1147
1176
  /**
1148
1177
  * Tracks `$ref` paths that are currently being resolved to prevent infinite
@@ -1176,7 +1205,7 @@ function createSchemaParser(ctx) {
1176
1205
  if (refPath && !resolvingRefs.has(refPath)) {
1177
1206
  if (!resolvedRefCache.has(refPath)) {
1178
1207
  try {
1179
- const referenced = resolveRef(document, refPath);
1208
+ const referenced = dialect.resolveRef(document, refPath);
1180
1209
  if (referenced) {
1181
1210
  resolvingRefs.add(refPath);
1182
1211
  resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
@@ -1225,13 +1254,13 @@ function createSchemaParser(ctx) {
1225
1254
  }
1226
1255
  const filteredDiscriminantValues = [];
1227
1256
  const allOfMembers = schema.allOf.filter((item) => {
1228
- if (!isReference(item) || !name) return true;
1229
- const deref = resolveRef(document, item.$ref);
1230
- if (!deref || !isDiscriminator(deref)) return true;
1257
+ if (!dialect.isReference(item) || !name) return true;
1258
+ const deref = dialect.resolveRef(document, item.$ref);
1259
+ if (!deref || !dialect.isDiscriminator(deref)) return true;
1231
1260
  const parentUnion = deref.oneOf ?? deref.anyOf;
1232
1261
  if (!parentUnion) return true;
1233
1262
  const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1234
- const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1263
+ const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
1235
1264
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1236
1265
  if (inOneOf || inMapping) {
1237
1266
  const discriminatorValue = ast.findDiscriminator(deref.discriminator.mapping, childRef);
@@ -1252,9 +1281,9 @@ function createSchemaParser(ctx) {
1252
1281
  const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
1253
1282
  if (missingRequired.length) {
1254
1283
  const resolvedMembers = schema.allOf.flatMap((item) => {
1255
- if (!isReference(item)) return [item];
1256
- const deref = resolveRef(document, item.$ref);
1257
- return deref && !isReference(deref) ? [deref] : [];
1284
+ if (!dialect.isReference(item)) return [item];
1285
+ const deref = dialect.resolveRef(document, item.$ref);
1286
+ return deref && !dialect.isReference(deref) ? [deref] : [];
1258
1287
  });
1259
1288
  for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
1260
1289
  allOfMembers.push(parseSchema({
@@ -1299,10 +1328,10 @@ function createSchemaParser(ctx) {
1299
1328
  const strategy = schema.oneOf ? "one" : "any";
1300
1329
  const unionBase = {
1301
1330
  ...buildSchemaNode(schema, name, nullable, defaultValue),
1302
- discriminatorPropertyName: isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1331
+ discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
1303
1332
  strategy
1304
1333
  };
1305
- const discriminator = isDiscriminator(schema) ? schema.discriminator : void 0;
1334
+ const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
1306
1335
  const sharedPropertiesNode = schema.properties ? (() => {
1307
1336
  const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
1308
1337
  return parseSchema({
@@ -1312,7 +1341,7 @@ function createSchemaParser(ctx) {
1312
1341
  })() : void 0;
1313
1342
  if (sharedPropertiesNode || discriminator?.mapping) {
1314
1343
  const members = unionMembers.map((s) => {
1315
- const ref = isReference(s) ? s.$ref : void 0;
1344
+ const ref = dialect.isReference(s) ? s.$ref : void 0;
1316
1345
  const discriminatorValue = ast.findDiscriminator(discriminator?.mapping, ref);
1317
1346
  const memberNode = parseSchema({
1318
1347
  schema: s,
@@ -1504,7 +1533,7 @@ function createSchemaParser(ctx) {
1504
1533
  const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
1505
1534
  const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
1506
1535
  const resolvedPropSchema = propSchema;
1507
- const propNullable = isNullable(resolvedPropSchema);
1536
+ const propNullable = dialect.isNullable(resolvedPropSchema);
1508
1537
  const propNode = parseSchema({
1509
1538
  schema: resolvedPropSchema,
1510
1539
  name: ast.childName(name, propName)
@@ -1549,7 +1578,7 @@ function createSchemaParser(ctx) {
1549
1578
  maxProperties: schema.maxProperties,
1550
1579
  ...buildSchemaNode(schema, name, nullable, defaultValue)
1551
1580
  });
1552
- if (isDiscriminator(schema) && schema.discriminator.mapping) {
1581
+ if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
1553
1582
  const discPropName = schema.discriminator.propertyName;
1554
1583
  const values = Object.keys(schema.discriminator.mapping);
1555
1584
  const enumName = name ? ast.enumPropName(name, discPropName, options.enumSuffix) : void 0;
@@ -1652,11 +1681,143 @@ function createSchemaParser(ctx) {
1652
1681
  });
1653
1682
  }
1654
1683
  /**
1684
+ * Converts a binary string schema (`type: 'string'`, `contentMediaType: 'application/octet-stream'`)
1685
+ * into a `blob` node.
1686
+ */
1687
+ function convertBlob({ schema, name, nullable, defaultValue }) {
1688
+ return ast.createSchema({
1689
+ type: "blob",
1690
+ primitive: "string",
1691
+ ...buildSchemaNode(schema, name, nullable, defaultValue)
1692
+ });
1693
+ }
1694
+ /**
1695
+ * Converts an OAS 3.1 multi-type array (e.g. `type: ['string', 'number']`) into a `UnionSchemaNode`.
1696
+ *
1697
+ * Returns `null` when only one non-`null` type remains (e.g. `['string', 'null']`), so `parseSchema`
1698
+ * falls through and handles it as that single type with nullability already folded in.
1699
+ */
1700
+ function convertMultiType({ schema, name, nullable, defaultValue, rawOptions }) {
1701
+ const types = schema.type;
1702
+ const nonNullTypes = types.filter((t) => t !== "null");
1703
+ if (nonNullTypes.length <= 1) return null;
1704
+ const arrayNullable = types.includes("null") || nullable || void 0;
1705
+ return ast.createSchema({
1706
+ type: "union",
1707
+ members: nonNullTypes.map((t) => parseSchema({
1708
+ schema: {
1709
+ ...schema,
1710
+ type: t
1711
+ },
1712
+ name
1713
+ }, rawOptions)),
1714
+ ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1715
+ });
1716
+ }
1717
+ /**
1718
+ * Ordered schema-dispatch table. Order is significant: composition keywords (`$ref`, `allOf`,
1719
+ * `oneOf`/`anyOf`) take precedence over `const`/`format`, which take precedence over the plain
1720
+ * `type`. The first matching rule that produces a node wins; see {@link SchemaRule} for the
1721
+ * match/convert/fall-through contract.
1722
+ */
1723
+ const schemaRules = [
1724
+ {
1725
+ name: "ref",
1726
+ match: ({ schema }) => dialect.isReference(schema),
1727
+ convert: convertRef
1728
+ },
1729
+ {
1730
+ name: "allOf",
1731
+ match: ({ schema }) => !!schema.allOf?.length,
1732
+ convert: convertAllOf
1733
+ },
1734
+ {
1735
+ name: "union",
1736
+ match: ({ schema }) => !!(schema.oneOf?.length || schema.anyOf?.length),
1737
+ convert: convertUnion
1738
+ },
1739
+ {
1740
+ name: "const",
1741
+ match: ({ schema }) => "const" in schema && schema.const !== void 0,
1742
+ convert: convertConst
1743
+ },
1744
+ {
1745
+ name: "format",
1746
+ match: ({ schema }) => !!schema.format,
1747
+ convert: convertFormat
1748
+ },
1749
+ {
1750
+ name: "blob",
1751
+ match: ({ schema }) => dialect.isBinary(schema),
1752
+ convert: convertBlob
1753
+ },
1754
+ {
1755
+ name: "multi-type",
1756
+ match: ({ schema }) => Array.isArray(schema.type) && schema.type.length > 1,
1757
+ convert: convertMultiType
1758
+ },
1759
+ {
1760
+ name: "constrained-string",
1761
+ match: ({ schema, type }) => !type && (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0),
1762
+ convert: convertString
1763
+ },
1764
+ {
1765
+ name: "constrained-number",
1766
+ match: ({ schema, type }) => !type && (schema.minimum !== void 0 || schema.maximum !== void 0),
1767
+ convert: (ctx) => convertNumeric(ctx, "number")
1768
+ },
1769
+ {
1770
+ name: "enum",
1771
+ match: ({ schema }) => !!schema.enum?.length,
1772
+ convert: convertEnum
1773
+ },
1774
+ {
1775
+ name: "object",
1776
+ match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
1777
+ convert: convertObject
1778
+ },
1779
+ {
1780
+ name: "tuple",
1781
+ match: ({ schema }) => "prefixItems" in schema,
1782
+ convert: convertTuple
1783
+ },
1784
+ {
1785
+ name: "array",
1786
+ match: ({ schema, type }) => type === "array" || "items" in schema,
1787
+ convert: convertArray
1788
+ },
1789
+ {
1790
+ name: "string",
1791
+ match: ({ type }) => type === "string",
1792
+ convert: convertString
1793
+ },
1794
+ {
1795
+ name: "number",
1796
+ match: ({ type }) => type === "number",
1797
+ convert: (ctx) => convertNumeric(ctx, "number")
1798
+ },
1799
+ {
1800
+ name: "integer",
1801
+ match: ({ type }) => type === "integer",
1802
+ convert: (ctx) => convertNumeric(ctx, "integer")
1803
+ },
1804
+ {
1805
+ name: "boolean",
1806
+ match: ({ type }) => type === "boolean",
1807
+ convert: convertBoolean
1808
+ },
1809
+ {
1810
+ name: "null",
1811
+ match: ({ type }) => type === "null",
1812
+ convert: convertNull
1813
+ }
1814
+ ];
1815
+ /**
1655
1816
  * Central dispatcher that converts an OAS `SchemaObject` into a `SchemaNode`.
1656
1817
  *
1657
- * Dispatch order (first match wins): `$ref` `allOf` `oneOf`/`anyOf` `const` → `format`
1658
- * octet-stream blob multi-type array constraint-inferred type `enum` object/array/tuple/scalar
1659
- * → empty-schema fallback (`emptySchemaType` option).
1818
+ * Builds the per-schema context, then runs it through the ordered {@link schemaRules} table
1819
+ * via {@link ast.dispatch}. When no rule produces a node, falls back to the configured
1820
+ * `emptySchemaType`.
1660
1821
  */
1661
1822
  function parseSchema({ schema, name }, rawOptions) {
1662
1823
  const options = {
@@ -1668,59 +1829,18 @@ function createSchemaParser(ctx) {
1668
1829
  schema: flattenedSchema,
1669
1830
  name
1670
1831
  }, rawOptions);
1671
- const nullable = isNullable(schema) || void 0;
1672
- const defaultValue = schema.default === null && nullable ? void 0 : schema.default;
1673
- const type = Array.isArray(schema.type) ? schema.type[0] : schema.type;
1674
- const ctx = {
1832
+ const nullable = dialect.isNullable(schema) || void 0;
1833
+ const schemaCtx = {
1675
1834
  schema,
1676
1835
  name,
1677
1836
  nullable,
1678
- defaultValue,
1679
- type,
1837
+ defaultValue: schema.default === null && nullable ? void 0 : schema.default,
1838
+ type: Array.isArray(schema.type) ? schema.type[0] : schema.type,
1680
1839
  rawOptions,
1681
1840
  options
1682
1841
  };
1683
- if (isReference(schema)) return convertRef(ctx);
1684
- if (schema.allOf?.length) return convertAllOf(ctx);
1685
- if ([...schema.oneOf ?? [], ...schema.anyOf ?? []].length) return convertUnion(ctx);
1686
- if ("const" in schema && schema.const !== void 0) return convertConst(ctx);
1687
- if (schema.format) {
1688
- const formatResult = convertFormat(ctx);
1689
- if (formatResult) return formatResult;
1690
- }
1691
- if (schema.type === "string" && schema.contentMediaType === "application/octet-stream") return ast.createSchema({
1692
- type: "blob",
1693
- primitive: "string",
1694
- ...buildSchemaNode(schema, name, nullable, defaultValue)
1695
- });
1696
- if (Array.isArray(schema.type) && schema.type.length > 1) {
1697
- const nonNullTypes = schema.type.filter((t) => t !== "null");
1698
- const arrayNullable = schema.type.includes("null") || nullable || void 0;
1699
- if (nonNullTypes.length > 1) return ast.createSchema({
1700
- type: "union",
1701
- members: nonNullTypes.map((t) => parseSchema({
1702
- schema: {
1703
- ...schema,
1704
- type: t
1705
- },
1706
- name
1707
- }, rawOptions)),
1708
- ...buildSchemaNode(schema, name, arrayNullable, defaultValue)
1709
- });
1710
- }
1711
- if (!type) {
1712
- if (schema.minLength !== void 0 || schema.maxLength !== void 0 || schema.pattern !== void 0) return convertString(ctx);
1713
- if (schema.minimum !== void 0 || schema.maximum !== void 0) return convertNumeric(ctx, "number");
1714
- }
1715
- if (schema.enum?.length) return convertEnum(ctx);
1716
- if (type === "object" || schema.properties || schema.additionalProperties || "patternProperties" in schema) return convertObject(ctx);
1717
- if ("prefixItems" in schema) return convertTuple(ctx);
1718
- if (type === "array" || "items" in schema) return convertArray(ctx);
1719
- if (type === "string") return convertString(ctx);
1720
- if (type === "number") return convertNumeric(ctx, "number");
1721
- if (type === "integer") return convertNumeric(ctx, "integer");
1722
- if (type === "boolean") return convertBoolean(ctx);
1723
- if (type === "null") return convertNull(ctx);
1842
+ const node = ast.dispatch(schemaRules, schemaCtx);
1843
+ if (node) return node;
1724
1844
  const emptyType = typeOptionMap.get(options.emptySchemaType);
1725
1845
  return ast.createSchema({
1726
1846
  type: emptyType,
@@ -1783,7 +1903,7 @@ function createSchemaParser(ctx) {
1783
1903
  const keys = [];
1784
1904
  for (const key in schema.properties) {
1785
1905
  const prop = schema.properties[key];
1786
- if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1906
+ if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
1787
1907
  }
1788
1908
  return keys.length ? keys : null;
1789
1909
  }
@@ -1845,6 +1965,7 @@ function createSchemaParser(ctx) {
1845
1965
  const urlPath = new URLPath(operation.path);
1846
1966
  return ast.createOperation({
1847
1967
  operationId,
1968
+ protocol: "http",
1848
1969
  method: operation.method.toUpperCase(),
1849
1970
  path: urlPath.path,
1850
1971
  tags: operation.getTags().map((tag) => tag.name),