@kubb/adapter-oas 5.0.0-alpha.43 → 5.0.0-alpha.44

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
@@ -53,6 +53,17 @@ const DEFAULT_PARSER_OPTIONS = {
53
53
  enumSuffix: "enum"
54
54
  };
55
55
  /**
56
+ * JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
57
+ *
58
+ * Used when building or parsing `$ref` strings.
59
+ *
60
+ * @example
61
+ * ```ts
62
+ * `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
63
+ * ```
64
+ */
65
+ const SCHEMA_REF_PREFIX = "#/components/schemas/";
66
+ /**
56
67
  * OpenAPI version string written into the stub document created during multi-spec merges.
57
68
  */
58
69
  const MERGE_OPENAPI_VERSION = "3.0.0";
@@ -774,12 +785,11 @@ function getResponseBody(responseBody, contentType) {
774
785
  }
775
786
  let availableContentType;
776
787
  const contentTypes = Object.keys(body.content);
777
- contentTypes.forEach((mt) => {
778
- if (!availableContentType && oas_utils.matchesMimeType.json(mt)) availableContentType = mt;
779
- });
780
- if (!availableContentType) contentTypes.forEach((mt) => {
781
- if (!availableContentType) availableContentType = mt;
782
- });
788
+ for (const mt of contentTypes) if (oas_utils.matchesMimeType.json(mt)) {
789
+ availableContentType = mt;
790
+ break;
791
+ }
792
+ if (!availableContentType) availableContentType = contentTypes[0];
783
793
  if (availableContentType) return [
784
794
  availableContentType,
785
795
  body.content[availableContentType],
@@ -799,11 +809,13 @@ function getResponseBody(responseBody, contentType) {
799
809
  * ```
800
810
  */
801
811
  function getResponseSchema(document, operation, statusCode, options = {}) {
802
- if (operation.schema.responses) Object.keys(operation.schema.responses).forEach((key) => {
803
- const schema = operation.schema.responses[key];
804
- const $ref = isReference(schema) ? schema.$ref : void 0;
805
- if (schema && $ref) operation.schema.responses[key] = resolveRef(document, $ref);
806
- });
812
+ if (operation.schema.responses) {
813
+ const responses = operation.schema.responses;
814
+ for (const key in responses) {
815
+ const schema = responses[key];
816
+ if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
817
+ }
818
+ }
807
819
  const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
808
820
  if (responseBody === false) return {};
809
821
  const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
@@ -842,14 +854,24 @@ function getRequestSchema(document, operation, options = {}) {
842
854
  * // returned unchanged — contains a $ref
843
855
  * ```
844
856
  */
857
+ /**
858
+ * Returns `true` when `fragment` carries any JSON Schema keyword that makes it
859
+ * structurally significant on its own (see `structuralKeys`).
860
+ *
861
+ * A fragment with a structural keyword can't be safely merged into a parent schema.
862
+ */
863
+ function hasStructuralKeywords(fragment) {
864
+ for (const key in fragment) if (structuralKeys.has(key)) return true;
865
+ return false;
866
+ }
845
867
  function flattenSchema(schema) {
846
868
  if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
847
- if (schema.allOf.some((item) => (0, oas_types.isRef)(item))) return schema;
848
- const isPlainFragment = (item) => !Object.keys(item).some((key) => structuralKeys.has(key));
849
- if (!schema.allOf.every((item) => isPlainFragment(item))) return schema;
869
+ const allOfFragments = schema.allOf;
870
+ if (allOfFragments.some((item) => (0, oas_types.isRef)(item))) return schema;
871
+ if (allOfFragments.some(hasStructuralKeywords)) return schema;
850
872
  const merged = { ...schema };
851
873
  delete merged.allOf;
852
- for (const fragment of schema.allOf) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
874
+ for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
853
875
  return merged;
854
876
  }
855
877
  /**
@@ -879,10 +901,15 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
879
901
  for (const item of schema) collectRefs(item, refs);
880
902
  return refs;
881
903
  }
882
- if (schema && typeof schema === "object") for (const [key, value] of Object.entries(schema)) if (key === "$ref" && typeof value === "string") {
883
- const match = value.match(/^#\/components\/schemas\/(.+)$/);
884
- if (match) refs.add(match[1]);
885
- } else collectRefs(value, refs);
904
+ if (schema && typeof schema === "object") for (const key in schema) {
905
+ const value = schema[key];
906
+ if (key === "$ref" && typeof value === "string") {
907
+ if (value.startsWith("#/components/schemas/")) {
908
+ const name = value.slice(21);
909
+ if (name) refs.add(name);
910
+ }
911
+ } else collectRefs(value, refs);
912
+ }
886
913
  return refs;
887
914
  }
888
915
  /**
@@ -966,13 +993,23 @@ function getSchemas(document, { contentType }) {
966
993
  }
967
994
  const schemas = {};
968
995
  const nameMapping = /* @__PURE__ */ new Map();
969
- const multipleSources = (items) => new Set(items.map((i) => i.source)).size > 1;
970
- for (const [, items] of normalizedNames) items.forEach((item, index) => {
971
- const suffix = items.length === 1 ? "" : multipleSources(items) ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
972
- const uniqueName = item.originalName + suffix;
973
- schemas[uniqueName] = item.schema;
974
- nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
975
- });
996
+ for (const [, items] of normalizedNames) {
997
+ const isSingle = items.length === 1;
998
+ let hasMultipleSources = false;
999
+ if (!isSingle) {
1000
+ const firstSource = items[0].source;
1001
+ for (let i = 1; i < items.length; i++) if (items[i].source !== firstSource) {
1002
+ hasMultipleSources = true;
1003
+ break;
1004
+ }
1005
+ }
1006
+ items.forEach((item, index) => {
1007
+ const suffix = isSingle ? "" : hasMultipleSources ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
1008
+ const uniqueName = item.originalName + suffix;
1009
+ schemas[uniqueName] = item.schema;
1010
+ nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
1011
+ });
1012
+ }
976
1013
  return {
977
1014
  schemas: sortSchemas(schemas),
978
1015
  nameMapping
@@ -1119,7 +1156,7 @@ function createSchemaParser(ctx) {
1119
1156
  if (!deref || !isDiscriminator(deref)) return true;
1120
1157
  const parentUnion = deref.oneOf ?? deref.anyOf;
1121
1158
  if (!parentUnion) return true;
1122
- const childRef = `#/components/schemas/${name}`;
1159
+ const childRef = `${SCHEMA_REF_PREFIX}${name}`;
1123
1160
  const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
1124
1161
  const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
1125
1162
  if (inOneOf || inMapping) {
@@ -1614,41 +1651,70 @@ function createSchemaParser(ctx) {
1614
1651
  });
1615
1652
  }
1616
1653
  /**
1654
+ * Reads the inline `requestBody` metadata (description / required / contentType) that OAS exposes
1655
+ * outside the schema itself. Returns an empty object when the request body is missing or a `$ref`.
1656
+ */
1657
+ function getRequestBodyMeta(operation) {
1658
+ const body = operation.schema.requestBody;
1659
+ if (!body || isReference(body)) return { required: false };
1660
+ const inline = body;
1661
+ return {
1662
+ description: inline.description,
1663
+ required: inline.required === true,
1664
+ contentType: inline.content ? Object.keys(inline.content)[0] : void 0
1665
+ };
1666
+ }
1667
+ /**
1668
+ * Reads the inline response object (not a `$ref`) and returns its description plus its `content` map.
1669
+ */
1670
+ function getResponseMeta(responseObj) {
1671
+ if (typeof responseObj !== "object" || responseObj === null || Array.isArray(responseObj)) return {};
1672
+ const inline = responseObj;
1673
+ return {
1674
+ description: inline.description,
1675
+ content: inline.content
1676
+ };
1677
+ }
1678
+ /**
1679
+ * Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
1680
+ * `$ref` entries are skipped since their flags live on the dereferenced target.
1681
+ */
1682
+ function collectPropertyKeysByFlag(schema, flag) {
1683
+ if (!schema?.properties) return void 0;
1684
+ const keys = [];
1685
+ for (const key in schema.properties) {
1686
+ const prop = schema.properties[key];
1687
+ if (prop && !isReference(prop) && prop[flag]) keys.push(key);
1688
+ }
1689
+ return keys.length ? keys : void 0;
1690
+ }
1691
+ /**
1617
1692
  * Converts an OAS `Operation` into an `OperationNode`.
1618
1693
  */
1619
1694
  function parseOperation(options, operation) {
1620
1695
  const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
1621
1696
  const requestBodySchema = getRequestSchema(document, operation, { contentType: ctx.contentType });
1622
1697
  const requestBodySchemaNode = requestBodySchema ? parseSchema({ schema: requestBodySchema }, options) : void 0;
1623
- const requestBodyDescription = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.description : void 0;
1624
- const requestBodyKeysToOmit = requestBodySchema?.properties ? Object.entries(requestBodySchema.properties).filter(([, prop]) => !isReference(prop) && prop.readOnly).map(([key]) => key) : void 0;
1625
- const requestBodyRequired = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.required === true : false;
1626
- const requestBodyContentType = (() => {
1627
- if (!operation.schema.requestBody || isReference(operation.schema.requestBody)) return;
1628
- const content = operation.schema.requestBody.content;
1629
- return content ? Object.keys(content)[0] : void 0;
1630
- })();
1698
+ const requestBodyMeta = getRequestBodyMeta(operation);
1631
1699
  const requestBody = requestBodySchemaNode ? {
1632
- description: requestBodyDescription,
1633
- schema: _kubb_core.ast.syncOptionality(requestBodySchemaNode, requestBodyRequired),
1634
- keysToOmit: requestBodyKeysToOmit?.length ? requestBodyKeysToOmit : void 0,
1635
- required: requestBodyRequired || void 0,
1636
- contentType: requestBodyContentType
1700
+ description: requestBodyMeta.description,
1701
+ schema: _kubb_core.ast.syncOptionality(requestBodySchemaNode, requestBodyMeta.required),
1702
+ keysToOmit: collectPropertyKeysByFlag(requestBodySchema, "readOnly"),
1703
+ required: requestBodyMeta.required || void 0,
1704
+ contentType: requestBodyMeta.contentType
1637
1705
  } : void 0;
1638
1706
  const responses = operation.getResponseStatusCodes().map((statusCode) => {
1639
1707
  const responseObj = operation.getResponseByStatusCode(statusCode);
1640
1708
  const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
1641
1709
  const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : _kubb_core.ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) });
1642
- const description = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.description : void 0;
1643
- const rawContent = typeof responseObj === "object" && responseObj !== null && !Array.isArray(responseObj) ? responseObj.content : void 0;
1644
- const mediaType = rawContent ? getMediaType(Object.keys(rawContent)[0] ?? "") : getMediaType(operation.contentType ?? "");
1645
- const keysToOmit = responseSchema?.properties ? Object.entries(responseSchema.properties).filter(([, prop]) => !isReference(prop) && prop.writeOnly).map(([key]) => key) : void 0;
1710
+ const { description, content } = getResponseMeta(responseObj);
1711
+ const mediaType = content ? getMediaType(Object.keys(content)[0] ?? "") : getMediaType(operation.contentType ?? "");
1646
1712
  return _kubb_core.ast.createResponse({
1647
1713
  statusCode,
1648
1714
  description,
1649
1715
  schema,
1650
1716
  mediaType,
1651
- keysToOmit: keysToOmit?.length ? keysToOmit : void 0
1717
+ keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
1652
1718
  });
1653
1719
  });
1654
1720
  const urlPath = new URLPath(operation.path);