@kubb/adapter-oas 5.0.0-beta.61 → 5.0.0-beta.63
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 +262 -137
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +262 -137
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/src/adapter.ts +1 -1
- package/src/constants.ts +13 -14
- package/src/dedupe.ts +237 -0
- package/src/dialect.ts +10 -6
- package/src/discriminator.ts +1 -1
- package/src/factory.ts +1 -1
- package/src/mime.ts +2 -3
- package/src/operation.ts +2 -2
- package/src/parser.ts +48 -42
- package/src/refs.ts +4 -3
- package/src/resolvers.ts +13 -5
- package/src/stream.ts +19 -81
- package/src/types.ts +1 -1
package/dist/index.js
CHANGED
|
@@ -14,10 +14,9 @@ import { collect, narrowSchema } from "@kubb/ast";
|
|
|
14
14
|
*
|
|
15
15
|
* @example
|
|
16
16
|
* ```ts
|
|
17
|
-
* import { DEFAULT_PARSER_OPTIONS } from '@kubb/adapter-oas'
|
|
17
|
+
* import { DEFAULT_PARSER_OPTIONS, parseOas } from '@kubb/adapter-oas'
|
|
18
18
|
*
|
|
19
|
-
* const
|
|
20
|
-
* const root = parser.parse({ ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
19
|
+
* const { root } = parseOas(document, { ...DEFAULT_PARSER_OPTIONS, dateType: 'date' })
|
|
21
20
|
* ```
|
|
22
21
|
*/
|
|
23
22
|
const DEFAULT_PARSER_OPTIONS = {
|
|
@@ -88,12 +87,24 @@ const structuralKeys = new Set([
|
|
|
88
87
|
"not"
|
|
89
88
|
]);
|
|
90
89
|
/**
|
|
90
|
+
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
91
|
+
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
92
|
+
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
93
|
+
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
94
|
+
*/
|
|
95
|
+
const specialCasedFormats = new Set([
|
|
96
|
+
"int64",
|
|
97
|
+
"date-time",
|
|
98
|
+
"date",
|
|
99
|
+
"time"
|
|
100
|
+
]);
|
|
101
|
+
/**
|
|
91
102
|
* Static map from OAS `format` strings to Kubb `SchemaType` values.
|
|
92
103
|
*
|
|
93
104
|
* Only formats whose AST type differs from the OAS `type` field appear here.
|
|
94
|
-
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
95
|
-
* in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
96
|
-
* `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
105
|
+
* Formats that depend on runtime options (`int64`, `date-time`, `date`, `time`) are handled
|
|
106
|
+
* separately in the parser. `ipv4` and `ipv6` map to their own dedicated schema types. `hostname`
|
|
107
|
+
* and `idn-hostname` map to `'url'` as the closest generic string-format type.
|
|
97
108
|
*
|
|
98
109
|
* @example
|
|
99
110
|
* ```ts
|
|
@@ -104,18 +115,6 @@ const structuralKeys = new Set([
|
|
|
104
115
|
* formatMap['float'] // 'number'
|
|
105
116
|
* ```
|
|
106
117
|
*/
|
|
107
|
-
/**
|
|
108
|
-
* Formats `convertFormat` maps to a dedicated type without going through `formatMap`:
|
|
109
|
-
* `int64` and the date/time family. Keep this in sync with the `convertFormat`
|
|
110
|
-
* special-cases in `parser.ts`. `isHandledFormat` reads it so the
|
|
111
|
-
* `KUBB_UNSUPPORTED_FORMAT` diagnostic and the parser agree on what is handled.
|
|
112
|
-
*/
|
|
113
|
-
const specialCasedFormats = new Set([
|
|
114
|
-
"int64",
|
|
115
|
-
"date-time",
|
|
116
|
-
"date",
|
|
117
|
-
"time"
|
|
118
|
-
]);
|
|
119
118
|
const formatMap = {
|
|
120
119
|
uuid: "uuid",
|
|
121
120
|
email: "email",
|
|
@@ -636,7 +635,7 @@ function isDiscriminator(obj) {
|
|
|
636
635
|
//#endregion
|
|
637
636
|
//#region src/factory.ts
|
|
638
637
|
/**
|
|
639
|
-
* Loads and
|
|
638
|
+
* Loads and bundles an OpenAPI document, returning the raw `Document`.
|
|
640
639
|
*
|
|
641
640
|
* Accepts a file path string or an already-parsed document object. File paths and URLs are
|
|
642
641
|
* bundled via `api-ref-bundler`, hoisting external file schemas into named `components.schemas`
|
|
@@ -747,17 +746,180 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
747
746
|
}
|
|
748
747
|
}
|
|
749
748
|
//#endregion
|
|
749
|
+
//#region src/dedupe.ts
|
|
750
|
+
/**
|
|
751
|
+
* Minimum occurrences before a shape is deduplicated.
|
|
752
|
+
*/
|
|
753
|
+
const MIN_OCCURRENCES = 2;
|
|
754
|
+
/**
|
|
755
|
+
* Builds the shared `ref` replacement for a duplicate occurrence, carrying the
|
|
756
|
+
* usage-slot and documentation fields that are not part of the shared type.
|
|
757
|
+
*/
|
|
758
|
+
function createRefNode(node, target) {
|
|
759
|
+
return ast.factory.createSchema({
|
|
760
|
+
type: "ref",
|
|
761
|
+
name: target.name,
|
|
762
|
+
ref: target.ref,
|
|
763
|
+
optional: node.optional,
|
|
764
|
+
nullish: node.nullish,
|
|
765
|
+
readOnly: node.readOnly,
|
|
766
|
+
writeOnly: node.writeOnly,
|
|
767
|
+
deprecated: node.deprecated,
|
|
768
|
+
description: node.description,
|
|
769
|
+
default: node.default,
|
|
770
|
+
example: node.example
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Strips usage-slot flags from an extracted definition and applies its name.
|
|
775
|
+
* A standalone definition is never optional, so `optional`/`nullish` are cleared.
|
|
776
|
+
*/
|
|
777
|
+
function cleanDefinition(node, name) {
|
|
778
|
+
return {
|
|
779
|
+
...node,
|
|
780
|
+
name,
|
|
781
|
+
optional: void 0,
|
|
782
|
+
nullish: void 0
|
|
783
|
+
};
|
|
784
|
+
}
|
|
785
|
+
/**
|
|
786
|
+
* Returns `true` when a node is eligible for deduplication. Only enums and objects qualify, and
|
|
787
|
+
* object shapes that take part in a circular chain are rejected so recursive structures are not
|
|
788
|
+
* extracted (which would break the cycle).
|
|
789
|
+
*/
|
|
790
|
+
function isCandidate(node, circularSchemas) {
|
|
791
|
+
if (node.type === "enum") return true;
|
|
792
|
+
if (node.type !== "object") return false;
|
|
793
|
+
if (node.name && circularSchemas.has(node.name)) return false;
|
|
794
|
+
return !containsCircularRef(node, { circularSchemas });
|
|
795
|
+
}
|
|
796
|
+
/**
|
|
797
|
+
* Produces the name for an inline shape with no existing named component, resolving
|
|
798
|
+
* collisions against `usedNames`. Returns `null` for an unnamed shape, which stays inline.
|
|
799
|
+
*/
|
|
800
|
+
function nameFor(node, usedNames) {
|
|
801
|
+
const base = node.name;
|
|
802
|
+
if (!base) return null;
|
|
803
|
+
let name = base;
|
|
804
|
+
let counter = 2;
|
|
805
|
+
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
806
|
+
usedNames.add(name);
|
|
807
|
+
return name;
|
|
808
|
+
}
|
|
809
|
+
/**
|
|
810
|
+
* Scans a forest of schema and operation nodes and produces a {@link Plan}.
|
|
811
|
+
*
|
|
812
|
+
* A shape that occurs at least {@link MIN_OCCURRENCES} times is deduplicated: if any occurrence is
|
|
813
|
+
* a named top-level schema, the first one becomes the target (so other top-level duplicates and
|
|
814
|
+
* inline copies turn into references to it). Other top-level names with the same content are
|
|
815
|
+
* recorded as aliases. Otherwise a new definition is extracted using {@link nameFor}. The returned
|
|
816
|
+
* plan rewrites nodes against those decisions.
|
|
817
|
+
*
|
|
818
|
+
* @example
|
|
819
|
+
* ```ts
|
|
820
|
+
* const dedupePlan = plan([...schemaNodes, ...operationNodes], { circularSchemas, usedNames })
|
|
821
|
+
* ```
|
|
822
|
+
*/
|
|
823
|
+
function plan(roots, context) {
|
|
824
|
+
const { circularSchemas, usedNames } = context;
|
|
825
|
+
const topLevelNodes = /* @__PURE__ */ new Set();
|
|
826
|
+
const groups = /* @__PURE__ */ new Map();
|
|
827
|
+
for (const root of roots) {
|
|
828
|
+
if (root.kind === "Schema") topLevelNodes.add(root);
|
|
829
|
+
for (const schemaNode of ast.collect(root, { schema: (node) => node })) {
|
|
830
|
+
if (!isCandidate(schemaNode, circularSchemas)) continue;
|
|
831
|
+
const signature = ast.signatureOf(schemaNode);
|
|
832
|
+
const isTopLevel = topLevelNodes.has(schemaNode) && !!schemaNode.name;
|
|
833
|
+
const group = groups.get(signature);
|
|
834
|
+
if (group) {
|
|
835
|
+
group.count++;
|
|
836
|
+
if (isTopLevel) group.topLevelNames.push(schemaNode.name);
|
|
837
|
+
} else groups.set(signature, {
|
|
838
|
+
count: 1,
|
|
839
|
+
representative: schemaNode,
|
|
840
|
+
topLevelNames: isTopLevel ? [schemaNode.name] : []
|
|
841
|
+
});
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
const bySignature = /* @__PURE__ */ new Map();
|
|
845
|
+
const byName = /* @__PURE__ */ new Map();
|
|
846
|
+
const pendingExtractions = [];
|
|
847
|
+
for (const [signature, group] of groups) {
|
|
848
|
+
if (group.count < MIN_OCCURRENCES) continue;
|
|
849
|
+
const [firstName, ...duplicateNames] = group.topLevelNames;
|
|
850
|
+
if (firstName) {
|
|
851
|
+
const target = {
|
|
852
|
+
name: firstName,
|
|
853
|
+
ref: `${SCHEMA_REF_PREFIX}${firstName}`
|
|
854
|
+
};
|
|
855
|
+
bySignature.set(signature, target);
|
|
856
|
+
for (const duplicate of duplicateNames) byName.set(duplicate, target);
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
859
|
+
const name = nameFor(group.representative, usedNames);
|
|
860
|
+
if (!name) continue;
|
|
861
|
+
bySignature.set(signature, {
|
|
862
|
+
name,
|
|
863
|
+
ref: `${SCHEMA_REF_PREFIX}${name}`
|
|
864
|
+
});
|
|
865
|
+
pendingExtractions.push({
|
|
866
|
+
name,
|
|
867
|
+
representative: group.representative
|
|
868
|
+
});
|
|
869
|
+
}
|
|
870
|
+
function rewrite(node, skipRootMatch) {
|
|
871
|
+
if (bySignature.size === 0 && byName.size === 0) return node;
|
|
872
|
+
const root = node;
|
|
873
|
+
return ast.transform(node, { schema(schemaNode) {
|
|
874
|
+
if (schemaNode.type === "ref") {
|
|
875
|
+
const refName = schemaNode.ref ? extractRefName(schemaNode.ref) : schemaNode.name;
|
|
876
|
+
const target = refName ? byName.get(refName) : void 0;
|
|
877
|
+
return target ? {
|
|
878
|
+
...schemaNode,
|
|
879
|
+
name: target.name,
|
|
880
|
+
ref: target.ref
|
|
881
|
+
} : void 0;
|
|
882
|
+
}
|
|
883
|
+
if (skipRootMatch && schemaNode === root) return void 0;
|
|
884
|
+
const target = bySignature.get(ast.signatureOf(schemaNode));
|
|
885
|
+
if (!target) return void 0;
|
|
886
|
+
return createRefNode(schemaNode, target);
|
|
887
|
+
} });
|
|
888
|
+
}
|
|
889
|
+
return {
|
|
890
|
+
extracted: pendingExtractions.map(({ name, representative }) => cleanDefinition(rewrite(representative, true), name)),
|
|
891
|
+
apply(node) {
|
|
892
|
+
return rewrite(node, false);
|
|
893
|
+
},
|
|
894
|
+
applyTopLevel(node) {
|
|
895
|
+
const target = bySignature.get(ast.signatureOf(node));
|
|
896
|
+
if (target && target.name !== node.name) return ast.factory.createSchema({
|
|
897
|
+
type: "ref",
|
|
898
|
+
name: node.name ?? null,
|
|
899
|
+
ref: target.ref,
|
|
900
|
+
description: node.description,
|
|
901
|
+
deprecated: node.deprecated
|
|
902
|
+
});
|
|
903
|
+
return rewrite(node, true);
|
|
904
|
+
},
|
|
905
|
+
isAlias(name) {
|
|
906
|
+
return byName.has(name);
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
}
|
|
910
|
+
//#endregion
|
|
750
911
|
//#region src/refs.ts
|
|
751
912
|
const _refCache = /* @__PURE__ */ new WeakMap();
|
|
752
913
|
/**
|
|
753
914
|
* Resolves a local JSON pointer reference from a document.
|
|
754
915
|
*
|
|
755
|
-
* Accepts `#/...` refs. Returns `null` for empty or non-local
|
|
756
|
-
*
|
|
916
|
+
* Accepts `#/...` refs. Returns `null` for an empty or non-local ref. When the pointer cannot be
|
|
917
|
+
* resolved, reports a `refNotFound` diagnostic into the active build and returns `null`. Outside a
|
|
918
|
+
* build there is no sink to collect it, so it throws instead.
|
|
757
919
|
*
|
|
758
920
|
* @example
|
|
759
921
|
* ```ts
|
|
760
|
-
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
922
|
+
* resolveRef<SchemaObject>(document, '#/components/schemas/Pet')
|
|
761
923
|
* ```
|
|
762
924
|
*/
|
|
763
925
|
function resolveRef(document, $ref) {
|
|
@@ -832,18 +994,21 @@ function dereferenceWithRef(document, schema) {
|
|
|
832
994
|
* const parser = createSchemaParser(context, oasDialect) // explicit
|
|
833
995
|
* ```
|
|
834
996
|
*/
|
|
835
|
-
const oasDialect = ast.
|
|
997
|
+
const oasDialect = ast.defineDialect({
|
|
836
998
|
name: "oas",
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
999
|
+
schema: {
|
|
1000
|
+
isNullable,
|
|
1001
|
+
isReference,
|
|
1002
|
+
isDiscriminator,
|
|
1003
|
+
isBinary: (schema) => schema.type === "string" && schema.contentMediaType === "application/octet-stream",
|
|
1004
|
+
resolveRef
|
|
1005
|
+
},
|
|
1006
|
+
dedupe: { plan }
|
|
842
1007
|
});
|
|
843
1008
|
//#endregion
|
|
844
1009
|
//#region src/discriminator.ts
|
|
845
1010
|
/**
|
|
846
|
-
*
|
|
1011
|
+
* Maps each child schema name to its discriminator patch data by scanning the given
|
|
847
1012
|
* top-level AST schema nodes for union schemas that carry a `discriminatorPropertyName`.
|
|
848
1013
|
*
|
|
849
1014
|
* The streaming path calls this on a small pre-parsed subset of schemas (only the
|
|
@@ -959,9 +1124,8 @@ function findDiscriminator(mapping, ref) {
|
|
|
959
1124
|
/**
|
|
960
1125
|
* MIME type fragments that mark a media type as JSON-like.
|
|
961
1126
|
*
|
|
962
|
-
*
|
|
963
|
-
*
|
|
964
|
-
* `application/vnd.api+json`.
|
|
1127
|
+
* A content type is JSON when it contains any of these substrings. The `+json` entry catches
|
|
1128
|
+
* structured-syntax suffixes such as `application/vnd.api+json`.
|
|
965
1129
|
*/
|
|
966
1130
|
const jsonMimeFragments = [
|
|
967
1131
|
"application/json",
|
|
@@ -1057,8 +1221,8 @@ function getRequestContent({ document, operation, mediaType }) {
|
|
|
1057
1221
|
return available ? [available, content[available]] : false;
|
|
1058
1222
|
}
|
|
1059
1223
|
/**
|
|
1060
|
-
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
1061
|
-
*
|
|
1224
|
+
* Returns the primary request content type. Prefers a JSON-like media type (the last one wins
|
|
1225
|
+
* when several are declared), then the first declared one, defaulting to `'application/json'`.
|
|
1062
1226
|
*/
|
|
1063
1227
|
function getRequestContentType({ document, operation }) {
|
|
1064
1228
|
const content = getRequestBodyContent({
|
|
@@ -1153,9 +1317,9 @@ function getSchemaType(format) {
|
|
|
1153
1317
|
return formatMap[format] ?? null;
|
|
1154
1318
|
}
|
|
1155
1319
|
/**
|
|
1156
|
-
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry
|
|
1157
|
-
* `specialCasedFormats` `convertFormat` handles directly. False means the format falls back to
|
|
1158
|
-
* base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
1320
|
+
* Whether the parser maps `format` to a dedicated type. True for any `formatMap` entry, plus the
|
|
1321
|
+
* `specialCasedFormats` that `convertFormat` handles directly. False means the format falls back to
|
|
1322
|
+
* the base type, which is what `KUBB_UNSUPPORTED_FORMAT` flags. Reading both sources keeps the
|
|
1159
1323
|
* diagnostic in step with the parser as `formatMap` grows.
|
|
1160
1324
|
*/
|
|
1161
1325
|
function isHandledFormat(format) {
|
|
@@ -1173,7 +1337,7 @@ function getPrimitiveType(type) {
|
|
|
1173
1337
|
/**
|
|
1174
1338
|
* Returns all parameters for an operation, merging path-level and operation-level entries.
|
|
1175
1339
|
* Operation-level parameters override path-level ones with the same `in:name` key.
|
|
1176
|
-
* `$ref`
|
|
1340
|
+
* Each `$ref` parameter is dereferenced via `dereferenceWithRef` before merging.
|
|
1177
1341
|
*
|
|
1178
1342
|
* @example
|
|
1179
1343
|
* ```ts
|
|
@@ -1274,7 +1438,10 @@ function getRequestSchema(document, operation, options = {}) {
|
|
|
1274
1438
|
* ```ts
|
|
1275
1439
|
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
1276
1440
|
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
1441
|
+
* ```
|
|
1277
1442
|
*
|
|
1443
|
+
* @example
|
|
1444
|
+
* ```ts
|
|
1278
1445
|
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
1279
1446
|
* // returned unchanged, contains a $ref
|
|
1280
1447
|
* ```
|
|
@@ -1561,6 +1728,21 @@ function normalizeArrayEnum(schema) {
|
|
|
1561
1728
|
};
|
|
1562
1729
|
}
|
|
1563
1730
|
/**
|
|
1731
|
+
* Builds a `null` scalar node carrying the schema's documentation. Shared by the `const: null`
|
|
1732
|
+
* and the drf-spectacular `NullEnum` (`{ enum: [null] }`) branches, which render identically.
|
|
1733
|
+
*/
|
|
1734
|
+
function createNullSchema(schema, name) {
|
|
1735
|
+
return ast.factory.createSchema({
|
|
1736
|
+
type: "null",
|
|
1737
|
+
primitive: "null",
|
|
1738
|
+
name,
|
|
1739
|
+
title: schema.title,
|
|
1740
|
+
description: schema.description,
|
|
1741
|
+
deprecated: schema.deprecated,
|
|
1742
|
+
format: schema.format
|
|
1743
|
+
});
|
|
1744
|
+
}
|
|
1745
|
+
/**
|
|
1564
1746
|
* Names the inline enums on a property's schema, and on each item when the property is a tuple, from
|
|
1565
1747
|
* the parent and property name. Wraps `macroEnumName` at the property construction site.
|
|
1566
1748
|
*/
|
|
@@ -1616,7 +1798,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1616
1798
|
if (refPath && !resolvingRefs.has(refPath)) {
|
|
1617
1799
|
if (!resolvedRefCache.has(refPath)) {
|
|
1618
1800
|
try {
|
|
1619
|
-
const referenced = dialect.resolveRef(document, refPath);
|
|
1801
|
+
const referenced = dialect.schema.resolveRef(document, refPath);
|
|
1620
1802
|
if (referenced) {
|
|
1621
1803
|
resolvingRefs.add(refPath);
|
|
1622
1804
|
resolvedSchema = parseSchema({ schema: referenced }, rawOptions);
|
|
@@ -1665,13 +1847,13 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1665
1847
|
}
|
|
1666
1848
|
const filteredDiscriminantValues = [];
|
|
1667
1849
|
const allOfMembers = schema.allOf.filter((item) => {
|
|
1668
|
-
if (!dialect.isReference(item) || !name) return true;
|
|
1669
|
-
const deref = dialect.resolveRef(document, item.$ref);
|
|
1670
|
-
if (!deref || !dialect.isDiscriminator(deref)) return true;
|
|
1850
|
+
if (!dialect.schema.isReference(item) || !name) return true;
|
|
1851
|
+
const deref = dialect.schema.resolveRef(document, item.$ref);
|
|
1852
|
+
if (!deref || !dialect.schema.isDiscriminator(deref)) return true;
|
|
1671
1853
|
const parentUnion = deref.oneOf ?? deref.anyOf;
|
|
1672
1854
|
if (!parentUnion) return true;
|
|
1673
1855
|
const childRef = `${SCHEMA_REF_PREFIX}${name}`;
|
|
1674
|
-
const inOneOf = parentUnion.some((oneOfItem) => dialect.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1856
|
+
const inOneOf = parentUnion.some((oneOfItem) => dialect.schema.isReference(oneOfItem) && oneOfItem.$ref === childRef);
|
|
1675
1857
|
const inMapping = Object.values(deref.discriminator.mapping ?? {}).some((v) => v === childRef);
|
|
1676
1858
|
if (inOneOf || inMapping) {
|
|
1677
1859
|
const discriminatorValue = findDiscriminator(deref.discriminator.mapping, childRef);
|
|
@@ -1692,9 +1874,9 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1692
1874
|
const missingRequired = schema.required.filter((key) => !outerKeys.has(key));
|
|
1693
1875
|
if (missingRequired.length) {
|
|
1694
1876
|
const resolvedMembers = schema.allOf.flatMap((item) => {
|
|
1695
|
-
if (!dialect.isReference(item)) return [item];
|
|
1696
|
-
const deref = dialect.resolveRef(document, item.$ref);
|
|
1697
|
-
return deref && !dialect.isReference(deref) ? [deref] : [];
|
|
1877
|
+
if (!dialect.schema.isReference(item)) return [item];
|
|
1878
|
+
const deref = dialect.schema.resolveRef(document, item.$ref);
|
|
1879
|
+
return deref && !dialect.schema.isReference(deref) ? [deref] : [];
|
|
1698
1880
|
});
|
|
1699
1881
|
for (const key of missingRequired) for (const resolved of resolvedMembers) if (resolved.properties?.[key]) {
|
|
1700
1882
|
allOfMembers.push(parseSchema({
|
|
@@ -1739,10 +1921,10 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1739
1921
|
const strategy = schema.oneOf ? "one" : "any";
|
|
1740
1922
|
const unionBase = {
|
|
1741
1923
|
...buildSchemaNode(schema, name, nullable, defaultValue),
|
|
1742
|
-
discriminatorPropertyName: dialect.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1924
|
+
discriminatorPropertyName: dialect.schema.isDiscriminator(schema) ? schema.discriminator.propertyName : void 0,
|
|
1743
1925
|
strategy
|
|
1744
1926
|
};
|
|
1745
|
-
const discriminator = dialect.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1927
|
+
const discriminator = dialect.schema.isDiscriminator(schema) ? schema.discriminator : void 0;
|
|
1746
1928
|
const sharedPropertiesNode = schema.properties ? (() => {
|
|
1747
1929
|
const { oneOf: _oneOf, anyOf: _anyOf, ...schemaWithoutUnion } = schema;
|
|
1748
1930
|
return parseSchema({
|
|
@@ -1752,7 +1934,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1752
1934
|
})() : void 0;
|
|
1753
1935
|
if (sharedPropertiesNode || discriminator?.mapping) {
|
|
1754
1936
|
const members = unionMembers.map((s) => {
|
|
1755
|
-
const ref = dialect.isReference(s) ? s.$ref : void 0;
|
|
1937
|
+
const ref = dialect.schema.isReference(s) ? s.$ref : void 0;
|
|
1756
1938
|
const discriminatorValue = findDiscriminator(discriminator?.mapping, ref);
|
|
1757
1939
|
const memberNode = parseSchema({
|
|
1758
1940
|
schema: s,
|
|
@@ -1798,15 +1980,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1798
1980
|
*/
|
|
1799
1981
|
function convertConst({ schema, name, nullable, defaultValue }) {
|
|
1800
1982
|
const constValue = schema.const;
|
|
1801
|
-
if (constValue === null) return
|
|
1802
|
-
type: "null",
|
|
1803
|
-
primitive: "null",
|
|
1804
|
-
name,
|
|
1805
|
-
title: schema.title,
|
|
1806
|
-
description: schema.description,
|
|
1807
|
-
deprecated: schema.deprecated,
|
|
1808
|
-
format: schema.format
|
|
1809
|
-
});
|
|
1983
|
+
if (constValue === null) return createNullSchema(schema, name);
|
|
1810
1984
|
const constPrimitive = getPrimitiveType(typeof constValue === "number" ? "number" : typeof constValue === "boolean" ? "boolean" : "string");
|
|
1811
1985
|
return ast.factory.createSchema({
|
|
1812
1986
|
type: "enum",
|
|
@@ -1895,15 +2069,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1895
2069
|
}, rawOptions);
|
|
1896
2070
|
const nullInEnum = schema.enum.includes(null);
|
|
1897
2071
|
const filteredValues = nullInEnum ? schema.enum.filter((v) => v !== null) : schema.enum;
|
|
1898
|
-
if (nullInEnum && filteredValues.length === 0) return
|
|
1899
|
-
type: "null",
|
|
1900
|
-
primitive: "null",
|
|
1901
|
-
name,
|
|
1902
|
-
title: schema.title,
|
|
1903
|
-
description: schema.description,
|
|
1904
|
-
deprecated: schema.deprecated,
|
|
1905
|
-
format: schema.format
|
|
1906
|
-
});
|
|
2072
|
+
if (nullInEnum && filteredValues.length === 0) return createNullSchema(schema, name);
|
|
1907
2073
|
const enumNullable = nullable || nullInEnum || void 0;
|
|
1908
2074
|
const enumDefault = schema.default === null && enumNullable ? void 0 : schema.default;
|
|
1909
2075
|
const enumPrimitive = getPrimitiveType(type);
|
|
@@ -1953,7 +2119,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1953
2119
|
const properties = schema.properties ? Object.entries(schema.properties).map(([propName, propSchema]) => {
|
|
1954
2120
|
const required = Array.isArray(schema.required) ? schema.required.includes(propName) : !!schema.required;
|
|
1955
2121
|
const resolvedPropSchema = propSchema;
|
|
1956
|
-
const propNullable = dialect.isNullable(resolvedPropSchema);
|
|
2122
|
+
const propNullable = dialect.schema.isNullable(resolvedPropSchema);
|
|
1957
2123
|
const schemaNode = nameEnums(parseSchema({
|
|
1958
2124
|
schema: resolvedPropSchema,
|
|
1959
2125
|
name: childName(name, propName)
|
|
@@ -1990,7 +2156,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
1990
2156
|
maxProperties: schema.maxProperties,
|
|
1991
2157
|
...buildSchemaNode(schema, name, nullable, defaultValue)
|
|
1992
2158
|
});
|
|
1993
|
-
if (dialect.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
2159
|
+
if (dialect.schema.isDiscriminator(schema) && schema.discriminator.mapping) {
|
|
1994
2160
|
const discPropName = schema.discriminator.propertyName;
|
|
1995
2161
|
const values = Object.keys(schema.discriminator.mapping);
|
|
1996
2162
|
const enumName = name ? enumPropName(name, discPropName, options.enumSuffix) : void 0;
|
|
@@ -2134,7 +2300,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2134
2300
|
const schemaRules = [
|
|
2135
2301
|
{
|
|
2136
2302
|
name: "ref",
|
|
2137
|
-
match: ({ schema }) => dialect.isReference(schema),
|
|
2303
|
+
match: ({ schema }) => dialect.schema.isReference(schema),
|
|
2138
2304
|
convert: convertRef
|
|
2139
2305
|
},
|
|
2140
2306
|
{
|
|
@@ -2159,7 +2325,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2159
2325
|
},
|
|
2160
2326
|
{
|
|
2161
2327
|
name: "blob",
|
|
2162
|
-
match: ({ schema }) => dialect.isBinary(schema),
|
|
2328
|
+
match: ({ schema }) => dialect.schema.isBinary(schema),
|
|
2163
2329
|
convert: convertBlob
|
|
2164
2330
|
},
|
|
2165
2331
|
{
|
|
@@ -2240,7 +2406,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2240
2406
|
schema: flattenedSchema,
|
|
2241
2407
|
name
|
|
2242
2408
|
}, rawOptions);
|
|
2243
|
-
const nullable = dialect.isNullable(schema) || void 0;
|
|
2409
|
+
const nullable = dialect.schema.isNullable(schema) || void 0;
|
|
2244
2410
|
const schemaCtx = {
|
|
2245
2411
|
schema,
|
|
2246
2412
|
name,
|
|
@@ -2317,7 +2483,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2317
2483
|
const keys = [];
|
|
2318
2484
|
for (const key in schema.properties) {
|
|
2319
2485
|
const prop = schema.properties[key];
|
|
2320
|
-
if (prop && !dialect.isReference(prop) && prop[flag]) keys.push(key);
|
|
2486
|
+
if (prop && !dialect.schema.isReference(prop) && prop[flag]) keys.push(key);
|
|
2321
2487
|
}
|
|
2322
2488
|
return keys.length ? keys : null;
|
|
2323
2489
|
}
|
|
@@ -2334,14 +2500,14 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2334
2500
|
const content = allContentTypes.flatMap((ct) => {
|
|
2335
2501
|
const schema = getRequestSchema(document, operation, { contentType: ct });
|
|
2336
2502
|
if (!schema) return [];
|
|
2337
|
-
return [{
|
|
2503
|
+
return [ast.factory.createContent({
|
|
2338
2504
|
contentType: ct,
|
|
2339
|
-
schema: ast.
|
|
2505
|
+
schema: ast.optionality(parseSchema({
|
|
2340
2506
|
schema,
|
|
2341
2507
|
name: requestBodyName
|
|
2342
2508
|
}, options), requestBodyMeta.required),
|
|
2343
2509
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
2344
|
-
}];
|
|
2510
|
+
})];
|
|
2345
2511
|
});
|
|
2346
2512
|
const requestBody = content.length > 0 || requestBodyMeta.description ? {
|
|
2347
2513
|
description: requestBodyMeta.description,
|
|
@@ -2366,17 +2532,17 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2366
2532
|
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
2367
2533
|
};
|
|
2368
2534
|
};
|
|
2369
|
-
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ({
|
|
2535
|
+
const content = (ctx.contentType ? [ctx.contentType] : getResponseBodyContentTypes(document, operation, statusCode)).map((contentType) => ast.factory.createContent({
|
|
2370
2536
|
contentType,
|
|
2371
2537
|
...parseEntrySchema(contentType)
|
|
2372
2538
|
}));
|
|
2373
|
-
if (content.length === 0) content.push({
|
|
2539
|
+
if (content.length === 0) content.push(ast.factory.createContent({
|
|
2374
2540
|
contentType: getRequestContentType({
|
|
2375
2541
|
document,
|
|
2376
2542
|
operation
|
|
2377
2543
|
}) || "application/json",
|
|
2378
2544
|
...parseEntrySchema(ctx.contentType)
|
|
2379
|
-
});
|
|
2545
|
+
}));
|
|
2380
2546
|
return ast.factory.createResponse({
|
|
2381
2547
|
statusCode,
|
|
2382
2548
|
description,
|
|
@@ -2384,7 +2550,7 @@ function createSchemaParser(ctx, dialect = oasDialect) {
|
|
|
2384
2550
|
});
|
|
2385
2551
|
});
|
|
2386
2552
|
const pathItem = document.paths?.[operation.path];
|
|
2387
|
-
const pathItemDoc = pathItem && !dialect.isReference(pathItem) ? pathItem : void 0;
|
|
2553
|
+
const pathItemDoc = pathItem && !dialect.schema.isReference(pathItem) ? pathItem : void 0;
|
|
2388
2554
|
const pickDoc = (key) => {
|
|
2389
2555
|
const own = operation.schema[key];
|
|
2390
2556
|
if (typeof own === "string") return own;
|
|
@@ -2469,36 +2635,6 @@ function visit(node, pointer) {
|
|
|
2469
2635
|
//#endregion
|
|
2470
2636
|
//#region src/stream.ts
|
|
2471
2637
|
/**
|
|
2472
|
-
* Builds the deduplication plan from the already-parsed top-level schema nodes plus a
|
|
2473
|
-
* single extra parse pass over operations (so duplicates in request/response bodies are seen).
|
|
2474
|
-
*
|
|
2475
|
-
* Only enums and objects are candidates, and object shapes that reference a circular schema are
|
|
2476
|
-
* rejected to avoid hoisting recursive structures. Inline shapes reuse their context-derived
|
|
2477
|
-
* name (collision-resolved against existing component names). Shapes without a name stay inline.
|
|
2478
|
-
*/
|
|
2479
|
-
function createDedupePlan({ schemaNodes, operationNodes, schemaNames, circularNames }) {
|
|
2480
|
-
const circularSchemas = new Set(circularNames);
|
|
2481
|
-
const usedNames = new Set(schemaNames);
|
|
2482
|
-
return ast.buildDedupePlan([...schemaNodes, ...operationNodes], {
|
|
2483
|
-
isCandidate: (node) => {
|
|
2484
|
-
if (node.type === "enum") return true;
|
|
2485
|
-
if (node.type !== "object") return false;
|
|
2486
|
-
if (node.name && circularSchemas.has(node.name)) return false;
|
|
2487
|
-
return !containsCircularRef(node, { circularSchemas });
|
|
2488
|
-
},
|
|
2489
|
-
nameFor: (node) => {
|
|
2490
|
-
const base = node.name;
|
|
2491
|
-
if (!base) return null;
|
|
2492
|
-
let name = base;
|
|
2493
|
-
let counter = 2;
|
|
2494
|
-
while (usedNames.has(name)) name = `${base}${counter++}`;
|
|
2495
|
-
usedNames.add(name);
|
|
2496
|
-
return name;
|
|
2497
|
-
},
|
|
2498
|
-
refFor: (name) => `${SCHEMA_REF_PREFIX}${name}`
|
|
2499
|
-
});
|
|
2500
|
-
}
|
|
2501
|
-
/**
|
|
2502
2638
|
* Reads the server URL from the document's `servers` array at `serverIndex`,
|
|
2503
2639
|
* interpolating any `serverVariables` into the URL template.
|
|
2504
2640
|
*
|
|
@@ -2567,18 +2703,17 @@ function preScan({ schemas, parseSchema, parseOperation, document, parserOptions
|
|
|
2567
2703
|
const operationNode = parseOperation(parserOptions, operation);
|
|
2568
2704
|
if (operationNode) operationNodes.push(operationNode);
|
|
2569
2705
|
}
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2706
|
+
const circularSchemas = new Set(circularNames);
|
|
2707
|
+
const usedNames = new Set(Object.keys(schemas));
|
|
2708
|
+
dedupePlan = oasDialect.dedupe.plan([...allNodes, ...operationNodes], {
|
|
2709
|
+
circularSchemas,
|
|
2710
|
+
usedNames
|
|
2575
2711
|
});
|
|
2576
|
-
for (const definition of dedupePlan.
|
|
2712
|
+
for (const definition of dedupePlan.extracted) if (definition.type === "enum" && definition.name) enumNames.push(definition.name);
|
|
2577
2713
|
}
|
|
2578
|
-
const aliasNames = dedupePlan?.aliasNames;
|
|
2579
2714
|
return {
|
|
2580
2715
|
refAliasMap,
|
|
2581
|
-
enumNames:
|
|
2716
|
+
enumNames: dedupePlan ? enumNames.filter((name) => !dedupePlan.isAlias(name)) : enumNames,
|
|
2582
2717
|
circularNames,
|
|
2583
2718
|
discriminatorChildMap,
|
|
2584
2719
|
dedupePlan
|
|
@@ -2603,39 +2738,29 @@ function preScan({ schemas, parseSchema, parseOperation, document, parserOptions
|
|
|
2603
2738
|
* ```
|
|
2604
2739
|
*/
|
|
2605
2740
|
function createInputStream({ schemas, parseSchema, parseOperation, document, parserOptions, refAliasMap, discriminatorChildMap, dedupePlan, meta }) {
|
|
2606
|
-
const rewriteTopLevelSchema = (node) => {
|
|
2607
|
-
if (!dedupePlan) return node;
|
|
2608
|
-
const canonical = dedupePlan.canonicalBySignature.get(ast.signatureOf(node));
|
|
2609
|
-
if (canonical && canonical.name !== node.name) return ast.factory.createSchema({
|
|
2610
|
-
type: "ref",
|
|
2611
|
-
name: node.name ?? null,
|
|
2612
|
-
ref: canonical.ref,
|
|
2613
|
-
description: node.description,
|
|
2614
|
-
deprecated: node.deprecated
|
|
2615
|
-
});
|
|
2616
|
-
return ast.applyDedupe(node, dedupePlan, true);
|
|
2617
|
-
};
|
|
2618
2741
|
const schemasIterable = { [Symbol.asyncIterator]() {
|
|
2619
2742
|
return (async function* () {
|
|
2620
|
-
if (dedupePlan) for (const definition of dedupePlan.
|
|
2743
|
+
if (dedupePlan) for (const definition of dedupePlan.extracted) yield definition;
|
|
2621
2744
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
2622
|
-
if (dedupePlan?.
|
|
2745
|
+
if (dedupePlan?.isAlias(name)) continue;
|
|
2623
2746
|
const alias = refAliasMap.get(name);
|
|
2624
2747
|
if (alias?.name && schemas[alias.name]) {
|
|
2625
|
-
|
|
2748
|
+
const aliasNode = {
|
|
2626
2749
|
...parseSchema({
|
|
2627
2750
|
schema: schemas[alias.name],
|
|
2628
2751
|
name: alias.name
|
|
2629
2752
|
}, parserOptions),
|
|
2630
2753
|
name
|
|
2631
|
-
}
|
|
2754
|
+
};
|
|
2755
|
+
yield dedupePlan ? dedupePlan.applyTopLevel(aliasNode) : aliasNode;
|
|
2632
2756
|
continue;
|
|
2633
2757
|
}
|
|
2634
2758
|
const parsed = parseSchema({
|
|
2635
2759
|
schema,
|
|
2636
2760
|
name
|
|
2637
2761
|
}, parserOptions);
|
|
2638
|
-
|
|
2762
|
+
const node = discriminatorChildMap?.get(name) ? patchDiscriminatorNode(parsed, discriminatorChildMap.get(name)) : parsed;
|
|
2763
|
+
yield dedupePlan ? dedupePlan.applyTopLevel(node) : node;
|
|
2639
2764
|
}
|
|
2640
2765
|
})();
|
|
2641
2766
|
} };
|
|
@@ -2643,7 +2768,7 @@ function createInputStream({ schemas, parseSchema, parseOperation, document, par
|
|
|
2643
2768
|
return (async function* () {
|
|
2644
2769
|
for (const operation of getOperations(document)) {
|
|
2645
2770
|
const node = parseOperation(parserOptions, operation);
|
|
2646
|
-
if (node) yield dedupePlan ?
|
|
2771
|
+
if (node) yield dedupePlan ? dedupePlan.apply(node) : node;
|
|
2647
2772
|
}
|
|
2648
2773
|
})();
|
|
2649
2774
|
} };
|
|
@@ -2657,7 +2782,7 @@ function createInputStream({ schemas, parseSchema, parseOperation, document, par
|
|
|
2657
2782
|
//#endregion
|
|
2658
2783
|
//#region src/adapter.ts
|
|
2659
2784
|
/**
|
|
2660
|
-
*
|
|
2785
|
+
* The `name` of `@kubb/adapter-oas`, used to identify this adapter in a Kubb config.
|
|
2661
2786
|
*/
|
|
2662
2787
|
const adapterOasName = "oas";
|
|
2663
2788
|
/**
|