@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 +111 -45
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +111 -45
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/constants.ts +12 -0
- package/src/parser.ts +54 -46
- package/src/resolvers.ts +48 -25
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.
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
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)
|
|
803
|
-
const
|
|
804
|
-
const
|
|
805
|
-
|
|
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
|
-
|
|
848
|
-
|
|
849
|
-
if (
|
|
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
|
|
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
|
|
883
|
-
const
|
|
884
|
-
if (
|
|
885
|
-
|
|
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
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
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 =
|
|
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
|
|
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:
|
|
1633
|
-
schema: _kubb_core.ast.syncOptionality(requestBodySchemaNode,
|
|
1634
|
-
keysToOmit:
|
|
1635
|
-
required:
|
|
1636
|
-
contentType:
|
|
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
|
|
1643
|
-
const
|
|
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:
|
|
1717
|
+
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
1652
1718
|
});
|
|
1653
1719
|
});
|
|
1654
1720
|
const urlPath = new URLPath(operation.path);
|