@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.js
CHANGED
|
@@ -27,6 +27,17 @@ const DEFAULT_PARSER_OPTIONS = {
|
|
|
27
27
|
enumSuffix: "enum"
|
|
28
28
|
};
|
|
29
29
|
/**
|
|
30
|
+
* JSON-Pointer prefix for schemas declared under `components.schemas` in an OpenAPI document.
|
|
31
|
+
*
|
|
32
|
+
* Used when building or parsing `$ref` strings.
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```ts
|
|
36
|
+
* `${SCHEMA_REF_PREFIX}Pet` // '#/components/schemas/Pet'
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
const SCHEMA_REF_PREFIX = "#/components/schemas/";
|
|
40
|
+
/**
|
|
30
41
|
* OpenAPI version string written into the stub document created during multi-spec merges.
|
|
31
42
|
*/
|
|
32
43
|
const MERGE_OPENAPI_VERSION = "3.0.0";
|
|
@@ -748,12 +759,11 @@ function getResponseBody(responseBody, contentType) {
|
|
|
748
759
|
}
|
|
749
760
|
let availableContentType;
|
|
750
761
|
const contentTypes = Object.keys(body.content);
|
|
751
|
-
contentTypes.
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
});
|
|
762
|
+
for (const mt of contentTypes) if (matchesMimeType.json(mt)) {
|
|
763
|
+
availableContentType = mt;
|
|
764
|
+
break;
|
|
765
|
+
}
|
|
766
|
+
if (!availableContentType) availableContentType = contentTypes[0];
|
|
757
767
|
if (availableContentType) return [
|
|
758
768
|
availableContentType,
|
|
759
769
|
body.content[availableContentType],
|
|
@@ -773,11 +783,13 @@ function getResponseBody(responseBody, contentType) {
|
|
|
773
783
|
* ```
|
|
774
784
|
*/
|
|
775
785
|
function getResponseSchema(document, operation, statusCode, options = {}) {
|
|
776
|
-
if (operation.schema.responses)
|
|
777
|
-
const
|
|
778
|
-
const
|
|
779
|
-
|
|
780
|
-
|
|
786
|
+
if (operation.schema.responses) {
|
|
787
|
+
const responses = operation.schema.responses;
|
|
788
|
+
for (const key in responses) {
|
|
789
|
+
const schema = responses[key];
|
|
790
|
+
if (schema && isReference(schema)) responses[key] = resolveRef(document, schema.$ref);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
781
793
|
const responseBody = getResponseBody(operation.getResponseByStatusCode(statusCode), options.contentType);
|
|
782
794
|
if (responseBody === false) return {};
|
|
783
795
|
const schema = Array.isArray(responseBody) ? responseBody[1].schema : responseBody.schema;
|
|
@@ -816,14 +828,24 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
816
828
|
* // returned unchanged — contains a $ref
|
|
817
829
|
* ```
|
|
818
830
|
*/
|
|
831
|
+
/**
|
|
832
|
+
* Returns `true` when `fragment` carries any JSON Schema keyword that makes it
|
|
833
|
+
* structurally significant on its own (see `structuralKeys`).
|
|
834
|
+
*
|
|
835
|
+
* A fragment with a structural keyword can't be safely merged into a parent schema.
|
|
836
|
+
*/
|
|
837
|
+
function hasStructuralKeywords(fragment) {
|
|
838
|
+
for (const key in fragment) if (structuralKeys.has(key)) return true;
|
|
839
|
+
return false;
|
|
840
|
+
}
|
|
819
841
|
function flattenSchema(schema) {
|
|
820
842
|
if (!schema?.allOf || schema.allOf.length === 0) return schema ?? null;
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
if (
|
|
843
|
+
const allOfFragments = schema.allOf;
|
|
844
|
+
if (allOfFragments.some((item) => isRef(item))) return schema;
|
|
845
|
+
if (allOfFragments.some(hasStructuralKeywords)) return schema;
|
|
824
846
|
const merged = { ...schema };
|
|
825
847
|
delete merged.allOf;
|
|
826
|
-
for (const fragment of
|
|
848
|
+
for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) if (merged[key] === void 0) merged[key] = value;
|
|
827
849
|
return merged;
|
|
828
850
|
}
|
|
829
851
|
/**
|
|
@@ -853,10 +875,15 @@ function collectRefs(schema, refs = /* @__PURE__ */ new Set()) {
|
|
|
853
875
|
for (const item of schema) collectRefs(item, refs);
|
|
854
876
|
return refs;
|
|
855
877
|
}
|
|
856
|
-
if (schema && typeof schema === "object") for (const
|
|
857
|
-
const
|
|
858
|
-
if (
|
|
859
|
-
|
|
878
|
+
if (schema && typeof schema === "object") for (const key in schema) {
|
|
879
|
+
const value = schema[key];
|
|
880
|
+
if (key === "$ref" && typeof value === "string") {
|
|
881
|
+
if (value.startsWith("#/components/schemas/")) {
|
|
882
|
+
const name = value.slice(21);
|
|
883
|
+
if (name) refs.add(name);
|
|
884
|
+
}
|
|
885
|
+
} else collectRefs(value, refs);
|
|
886
|
+
}
|
|
860
887
|
return refs;
|
|
861
888
|
}
|
|
862
889
|
/**
|
|
@@ -940,13 +967,23 @@ function getSchemas(document, { contentType }) {
|
|
|
940
967
|
}
|
|
941
968
|
const schemas = {};
|
|
942
969
|
const nameMapping = /* @__PURE__ */ new Map();
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
970
|
+
for (const [, items] of normalizedNames) {
|
|
971
|
+
const isSingle = items.length === 1;
|
|
972
|
+
let hasMultipleSources = false;
|
|
973
|
+
if (!isSingle) {
|
|
974
|
+
const firstSource = items[0].source;
|
|
975
|
+
for (let i = 1; i < items.length; i++) if (items[i].source !== firstSource) {
|
|
976
|
+
hasMultipleSources = true;
|
|
977
|
+
break;
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
items.forEach((item, index) => {
|
|
981
|
+
const suffix = isSingle ? "" : hasMultipleSources ? getSemanticSuffix(item.source) : index === 0 ? "" : String(index + 1);
|
|
982
|
+
const uniqueName = item.originalName + suffix;
|
|
983
|
+
schemas[uniqueName] = item.schema;
|
|
984
|
+
nameMapping.set(`#/components/${item.source}/${item.originalName}`, uniqueName);
|
|
985
|
+
});
|
|
986
|
+
}
|
|
950
987
|
return {
|
|
951
988
|
schemas: sortSchemas(schemas),
|
|
952
989
|
nameMapping
|
|
@@ -1093,7 +1130,7 @@ function createSchemaParser(ctx) {
|
|
|
1093
1130
|
if (!deref || !isDiscriminator(deref)) return true;
|
|
1094
1131
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1095
1132
|
if (!parentUnion) return true;
|
|
1096
|
-
const childRef =
|
|
1133
|
+
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1097
1134
|
const inOneOf = parentUnion.some((oneOfItem) => isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1098
1135
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1099
1136
|
if (inOneOf || inMapping) {
|
|
@@ -1588,41 +1625,70 @@ function createSchemaParser(ctx) {
|
|
|
1588
1625
|
});
|
|
1589
1626
|
}
|
|
1590
1627
|
/**
|
|
1628
|
+
* Reads the inline `requestBody` metadata (description / required / contentType) that OAS exposes
|
|
1629
|
+
* outside the schema itself. Returns an empty object when the request body is missing or a `$ref`.
|
|
1630
|
+
*/
|
|
1631
|
+
function getRequestBodyMeta(operation) {
|
|
1632
|
+
const body = operation.schema.requestBody;
|
|
1633
|
+
if (!body || isReference(body)) return { required: false };
|
|
1634
|
+
const inline = body;
|
|
1635
|
+
return {
|
|
1636
|
+
description: inline.description,
|
|
1637
|
+
required: inline.required === true,
|
|
1638
|
+
contentType: inline.content ? Object.keys(inline.content)[0] : void 0
|
|
1639
|
+
};
|
|
1640
|
+
}
|
|
1641
|
+
/**
|
|
1642
|
+
* Reads the inline response object (not a `$ref`) and returns its description plus its `content` map.
|
|
1643
|
+
*/
|
|
1644
|
+
function getResponseMeta(responseObj) {
|
|
1645
|
+
if (typeof responseObj !== "object" || responseObj === null || Array.isArray(responseObj)) return {};
|
|
1646
|
+
const inline = responseObj;
|
|
1647
|
+
return {
|
|
1648
|
+
description: inline.description,
|
|
1649
|
+
content: inline.content
|
|
1650
|
+
};
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Collects property names whose schema has a truthy boolean flag (`readOnly` or `writeOnly`).
|
|
1654
|
+
* `$ref` entries are skipped since their flags live on the dereferenced target.
|
|
1655
|
+
*/
|
|
1656
|
+
function collectPropertyKeysByFlag(schema, flag) {
|
|
1657
|
+
if (!schema?.properties) return void 0;
|
|
1658
|
+
const keys = [];
|
|
1659
|
+
for (const key in schema.properties) {
|
|
1660
|
+
const prop = schema.properties[key];
|
|
1661
|
+
if (prop && !isReference(prop) && prop[flag]) keys.push(key);
|
|
1662
|
+
}
|
|
1663
|
+
return keys.length ? keys : void 0;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1591
1666
|
* Converts an OAS `Operation` into an `OperationNode`.
|
|
1592
1667
|
*/
|
|
1593
1668
|
function parseOperation(options, operation) {
|
|
1594
1669
|
const parameters = getParameters(document, operation).map((param) => parseParameter(options, param));
|
|
1595
1670
|
const requestBodySchema = getRequestSchema(document, operation, { contentType: ctx.contentType });
|
|
1596
1671
|
const requestBodySchemaNode = requestBodySchema ? parseSchema({ schema: requestBodySchema }, options) : void 0;
|
|
1597
|
-
const
|
|
1598
|
-
const requestBodyKeysToOmit = requestBodySchema?.properties ? Object.entries(requestBodySchema.properties).filter(([, prop]) => !isReference(prop) && prop.readOnly).map(([key]) => key) : void 0;
|
|
1599
|
-
const requestBodyRequired = operation.schema.requestBody && !isReference(operation.schema.requestBody) ? operation.schema.requestBody.required === true : false;
|
|
1600
|
-
const requestBodyContentType = (() => {
|
|
1601
|
-
if (!operation.schema.requestBody || isReference(operation.schema.requestBody)) return;
|
|
1602
|
-
const content = operation.schema.requestBody.content;
|
|
1603
|
-
return content ? Object.keys(content)[0] : void 0;
|
|
1604
|
-
})();
|
|
1672
|
+
const requestBodyMeta = getRequestBodyMeta(operation);
|
|
1605
1673
|
const requestBody = requestBodySchemaNode ? {
|
|
1606
|
-
description:
|
|
1607
|
-
schema: ast.syncOptionality(requestBodySchemaNode,
|
|
1608
|
-
keysToOmit:
|
|
1609
|
-
required:
|
|
1610
|
-
contentType:
|
|
1674
|
+
description: requestBodyMeta.description,
|
|
1675
|
+
schema: ast.syncOptionality(requestBodySchemaNode, requestBodyMeta.required),
|
|
1676
|
+
keysToOmit: collectPropertyKeysByFlag(requestBodySchema, "readOnly"),
|
|
1677
|
+
required: requestBodyMeta.required || void 0,
|
|
1678
|
+
contentType: requestBodyMeta.contentType
|
|
1611
1679
|
} : void 0;
|
|
1612
1680
|
const responses = operation.getResponseStatusCodes().map((statusCode) => {
|
|
1613
1681
|
const responseObj = operation.getResponseByStatusCode(statusCode);
|
|
1614
1682
|
const responseSchema = getResponseSchema(document, operation, statusCode, { contentType: ctx.contentType });
|
|
1615
1683
|
const schema = responseSchema && Object.keys(responseSchema).length > 0 ? parseSchema({ schema: responseSchema }, options) : ast.createSchema({ type: typeOptionMap.get(options.emptySchemaType) });
|
|
1616
|
-
const description
|
|
1617
|
-
const
|
|
1618
|
-
const mediaType = rawContent ? getMediaType(Object.keys(rawContent)[0] ?? "") : getMediaType(operation.contentType ?? "");
|
|
1619
|
-
const keysToOmit = responseSchema?.properties ? Object.entries(responseSchema.properties).filter(([, prop]) => !isReference(prop) && prop.writeOnly).map(([key]) => key) : void 0;
|
|
1684
|
+
const { description, content } = getResponseMeta(responseObj);
|
|
1685
|
+
const mediaType = content ? getMediaType(Object.keys(content)[0] ?? "") : getMediaType(operation.contentType ?? "");
|
|
1620
1686
|
return ast.createResponse({
|
|
1621
1687
|
statusCode,
|
|
1622
1688
|
description,
|
|
1623
1689
|
schema,
|
|
1624
1690
|
mediaType,
|
|
1625
|
-
keysToOmit:
|
|
1691
|
+
keysToOmit: collectPropertyKeysByFlag(responseSchema, "writeOnly")
|
|
1626
1692
|
});
|
|
1627
1693
|
});
|
|
1628
1694
|
const urlPath = new URLPath(operation.path);
|