@kubb/adapter-oas 5.12.8 → 5.13.1
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 +333 -157
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +340 -157
- package/dist/index.js.map +1 -1
- package/dist/validateWorker.js +17 -0
- package/dist/validateWorker.js.map +1 -0
- package/kubb-extension.json +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -32,11 +32,11 @@ yaml = __toESM(yaml, 1);
|
|
|
32
32
|
//#region kubb-extension.json
|
|
33
33
|
var compatibility = {
|
|
34
34
|
"kubb": ">=5.8.3",
|
|
35
|
-
"node": ">=22.
|
|
35
|
+
"node": ">=22.22"
|
|
36
36
|
};
|
|
37
37
|
//#endregion
|
|
38
38
|
//#region package.json
|
|
39
|
-
var version = "5.
|
|
39
|
+
var version = "5.13.1";
|
|
40
40
|
//#endregion
|
|
41
41
|
//#region src/constants.ts
|
|
42
42
|
/**
|
|
@@ -630,8 +630,34 @@ async function validateDocument(document, { throwOnError = false } = {}) {
|
|
|
630
630
|
message: compileErrors(result),
|
|
631
631
|
help: "Fix each problem listed in the spec, then validate again."
|
|
632
632
|
});
|
|
633
|
-
|
|
634
|
-
|
|
633
|
+
reportValidationErrors(result);
|
|
634
|
+
}
|
|
635
|
+
function reportValidationErrors({ errors, additionalErrors }) {
|
|
636
|
+
for (const { message } of errors) reportInvalidSpec(message.replace(/\n.*/s, ""));
|
|
637
|
+
if (additionalErrors > 0) reportInvalidSpec(`${additionalErrors} more validation errors are not listed.`);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Validates like {@link validateDocument} without `throwOnError`, but in a worker thread, so the
|
|
641
|
+
* build keeps generating while `@readme/openapi-parser` dereferences and checks the spec. Await the
|
|
642
|
+
* returned promise inside the build's diagnostics scope to report its warnings there. Falls back to
|
|
643
|
+
* validating in place where worker threads are missing, as in edge runtimes.
|
|
644
|
+
*/
|
|
645
|
+
async function validateDocumentInWorker(document) {
|
|
646
|
+
let message;
|
|
647
|
+
try {
|
|
648
|
+
const { Worker } = await import("node:worker_threads");
|
|
649
|
+
const workerFile = require("url").pathToFileURL(__filename).href.endsWith(".ts") ? "./validateWorker.ts" : "./validateWorker.js";
|
|
650
|
+
const worker = new Worker(new URL(workerFile, require("url").pathToFileURL(__filename).href), { workerData: document });
|
|
651
|
+
message = await new Promise((resolve, reject) => {
|
|
652
|
+
worker.once("message", resolve);
|
|
653
|
+
worker.once("error", reject);
|
|
654
|
+
worker.once("exit", (code) => reject(/* @__PURE__ */ new Error(`Validation worker exited with code ${code}`)));
|
|
655
|
+
}).finally(() => worker.terminate());
|
|
656
|
+
} catch {
|
|
657
|
+
return validateDocument(document);
|
|
658
|
+
}
|
|
659
|
+
if ("thrown" in message) return reportInvalidSpec(message.thrown);
|
|
660
|
+
if (!message.valid) reportValidationErrors(message);
|
|
635
661
|
}
|
|
636
662
|
//#endregion
|
|
637
663
|
//#region src/oas.ts
|
|
@@ -720,7 +746,7 @@ function isJsonMimeType(mimeType) {
|
|
|
720
746
|
//#endregion
|
|
721
747
|
//#region src/model/components.ts
|
|
722
748
|
/**
|
|
723
|
-
* Extracts the inline schema from a media-type `content` map.
|
|
749
|
+
* Extracts the inline schema from a media-type `content` map, with the media type it sits under.
|
|
724
750
|
*
|
|
725
751
|
* Prefers `preferredContentType` when given, otherwise uses the first key in the map.
|
|
726
752
|
* Returns `null` when `content` is absent, the schema is missing, or the schema is a `$ref`.
|
|
@@ -728,15 +754,19 @@ function isJsonMimeType(mimeType) {
|
|
|
728
754
|
* @example
|
|
729
755
|
* ```ts
|
|
730
756
|
* extractSchemaFromContent(operation.content, 'application/json')
|
|
731
|
-
* // SchemaObject | null
|
|
757
|
+
* // { schema: SchemaObject, contentType: 'application/json' } | null
|
|
732
758
|
* ```
|
|
733
759
|
*/
|
|
734
760
|
function extractSchemaFromContent(content, preferredContentType) {
|
|
735
761
|
if (!content) return null;
|
|
736
762
|
const firstContentType = Object.keys(content)[0] ?? "application/json";
|
|
737
|
-
const
|
|
738
|
-
|
|
739
|
-
|
|
763
|
+
const targetContentType = preferredContentType ?? firstContentType;
|
|
764
|
+
const schema = content[targetContentType]?.schema;
|
|
765
|
+
if (!schema || isReference(schema)) return null;
|
|
766
|
+
return {
|
|
767
|
+
schema,
|
|
768
|
+
contentType: targetContentType
|
|
769
|
+
};
|
|
740
770
|
}
|
|
741
771
|
/**
|
|
742
772
|
* Walks a schema tree and collects the names of all `#/components/schemas/<name>` `$ref`s.
|
|
@@ -788,7 +818,7 @@ function* collectNestedDefinitions(schema, pointer, name) {
|
|
|
788
818
|
const nestedName = `${name}_${key}`;
|
|
789
819
|
yield {
|
|
790
820
|
schema: definition,
|
|
791
|
-
|
|
821
|
+
ref: definitionPointer,
|
|
792
822
|
originalName: nestedName
|
|
793
823
|
};
|
|
794
824
|
yield* collectNestedDefinitions(definition, definitionPointer, nestedName);
|
|
@@ -866,25 +896,28 @@ function getSchemas(document, { contentType }, refs) {
|
|
|
866
896
|
return resolved && !isReference(resolved) ? resolved : schema;
|
|
867
897
|
}
|
|
868
898
|
const candidates = [...Object.entries(components?.schemas ?? {}).flatMap(([name, schema]) => {
|
|
869
|
-
const
|
|
899
|
+
const ref = `#/components/schemas/${escapePointerToken(name)}`;
|
|
870
900
|
return [{
|
|
871
901
|
schema: resolveSchemaRef(schema),
|
|
872
902
|
source: "schemas",
|
|
873
903
|
originalName: name,
|
|
874
|
-
|
|
875
|
-
}, ...[...collectNestedDefinitions(schema,
|
|
904
|
+
ref
|
|
905
|
+
}, ...[...collectNestedDefinitions(schema, ref, name)].map((nested) => ({
|
|
876
906
|
...nested,
|
|
877
907
|
source: "schemas",
|
|
878
908
|
nested: true
|
|
879
909
|
}))];
|
|
880
910
|
}), ...["responses", "requestBodies"].flatMap((source) => Object.entries(components?.[source] ?? {}).flatMap(([name, item]) => {
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
911
|
+
const extracted = extractSchemaFromContent(item.content, contentType);
|
|
912
|
+
if (!extracted) return [];
|
|
913
|
+
const ref = `#/components/${source}/${escapePointerToken(name)}`;
|
|
914
|
+
return [{
|
|
915
|
+
schema: resolveSchemaRef(extracted.schema),
|
|
884
916
|
source,
|
|
885
917
|
originalName: name,
|
|
886
|
-
|
|
887
|
-
|
|
918
|
+
ref,
|
|
919
|
+
pointer: `${ref}/content/${escapePointerToken(extracted.contentType)}/schema`
|
|
920
|
+
}];
|
|
888
921
|
}))];
|
|
889
922
|
const normalizedNames = /* @__PURE__ */ new Map();
|
|
890
923
|
for (const item of candidates) {
|
|
@@ -895,6 +928,7 @@ function getSchemas(document, { contentType }, refs) {
|
|
|
895
928
|
}
|
|
896
929
|
const schemas = {};
|
|
897
930
|
const renames = /* @__PURE__ */ new Map();
|
|
931
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
898
932
|
for (const [, items] of normalizedNames) {
|
|
899
933
|
const isSingle = items.length === 1;
|
|
900
934
|
const hasMultipleSources = !isSingle && new Set(items.map((item) => item.source)).size > 1;
|
|
@@ -902,12 +936,14 @@ function getSchemas(document, { contentType }, refs) {
|
|
|
902
936
|
const suffix = isSingle ? "" : hasMultipleSources ? semanticSuffixes[item.source] : index ? String(index + 1) : "";
|
|
903
937
|
const uniqueName = item.originalName + suffix;
|
|
904
938
|
schemas[uniqueName] = item.schema;
|
|
905
|
-
|
|
939
|
+
pointers.set(uniqueName, item.pointer ?? item.ref);
|
|
940
|
+
if (suffix || item.nested) renames.set(item.ref, uniqueName);
|
|
906
941
|
});
|
|
907
942
|
}
|
|
908
943
|
return {
|
|
909
944
|
schemas: sortSchemas(schemas, renames),
|
|
910
|
-
renames
|
|
945
|
+
renames,
|
|
946
|
+
pointers
|
|
911
947
|
};
|
|
912
948
|
}
|
|
913
949
|
//#endregion
|
|
@@ -964,6 +1000,115 @@ function resolveServerUrl(server, overrides) {
|
|
|
964
1000
|
return url;
|
|
965
1001
|
}
|
|
966
1002
|
//#endregion
|
|
1003
|
+
//#region src/emit/schemaShape.ts
|
|
1004
|
+
/**
|
|
1005
|
+
* Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
|
|
1006
|
+
*/
|
|
1007
|
+
function isHandledFormat(format) {
|
|
1008
|
+
return formatMap[format] !== void 0 || specialCasedFormats.has(format);
|
|
1009
|
+
}
|
|
1010
|
+
/**
|
|
1011
|
+
* Resolves the `dateType` option down to the value for one format. The scalar form of
|
|
1012
|
+
* `dateType` applies to every format; the object form picks `dateTime`/`date`/`time`
|
|
1013
|
+
* individually and defaults an omitted key to `'string'`.
|
|
1014
|
+
*/
|
|
1015
|
+
function resolveDateTypeValue(dateType, format) {
|
|
1016
|
+
if (typeof dateType !== "object" || dateType === null) return dateType;
|
|
1017
|
+
return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
|
|
1021
|
+
* labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
|
|
1022
|
+
* 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
|
|
1023
|
+
*/
|
|
1024
|
+
function extractExamples(schema) {
|
|
1025
|
+
if (Array.isArray(schema.examples)) return schema.examples;
|
|
1026
|
+
return schema.example !== void 0 ? [schema.example] : void 0;
|
|
1027
|
+
}
|
|
1028
|
+
function hasStructuralKeywords(fragment) {
|
|
1029
|
+
return Object.keys(fragment).some((key) => structuralKeys.has(key));
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Flattens a keyword-only `allOf` into its parent schema.
|
|
1033
|
+
*
|
|
1034
|
+
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
1035
|
+
* (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
|
|
1036
|
+
* original schema unchanged when flattening is unsafe.
|
|
1037
|
+
*
|
|
1038
|
+
* @example
|
|
1039
|
+
* ```ts
|
|
1040
|
+
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
1041
|
+
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
1042
|
+
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
1043
|
+
* // returned unchanged, contains a $ref
|
|
1044
|
+
* ```
|
|
1045
|
+
*/
|
|
1046
|
+
function flattenSchema(schema) {
|
|
1047
|
+
if (!schema.allOf?.length) return schema;
|
|
1048
|
+
const allOfFragments = schema.allOf;
|
|
1049
|
+
if (allOfFragments.some((item) => isReference(item))) return schema;
|
|
1050
|
+
if (allOfFragments.some(hasStructuralKeywords)) return schema;
|
|
1051
|
+
const { allOf: _allOf, ...rest } = schema;
|
|
1052
|
+
const merged = rest;
|
|
1053
|
+
for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
|
|
1054
|
+
return merged;
|
|
1055
|
+
}
|
|
1056
|
+
//#endregion
|
|
1057
|
+
//#region src/schemaDiagnostics.ts
|
|
1058
|
+
/**
|
|
1059
|
+
* Links one freshly converted schema and its nested schemas to their RFC 6901 pointers
|
|
1060
|
+
* (`#/components/schemas/Pet/properties/owner/properties/name`), and reports the advisory
|
|
1061
|
+
* diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) against them.
|
|
1062
|
+
* Refs are not followed, so the resolved schema is handled under its own walk.
|
|
1063
|
+
* Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
|
|
1064
|
+
*/
|
|
1065
|
+
function scanSchema(node, pointer = node.pointer) {
|
|
1066
|
+
if (pointer) visit(node, pointer);
|
|
1067
|
+
}
|
|
1068
|
+
function visit(node, pointer) {
|
|
1069
|
+
node.pointer ??= pointer;
|
|
1070
|
+
if (node.deprecated) _kubb_kit.Diagnostics.report({
|
|
1071
|
+
code: _kubb_kit.Diagnostics.code.deprecated,
|
|
1072
|
+
severity: "info",
|
|
1073
|
+
message: "This schema is marked as deprecated.",
|
|
1074
|
+
location: {
|
|
1075
|
+
kind: "schema",
|
|
1076
|
+
pointer
|
|
1077
|
+
}
|
|
1078
|
+
});
|
|
1079
|
+
if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_kit.Diagnostics.report({
|
|
1080
|
+
code: _kubb_kit.Diagnostics.code.unsupportedFormat,
|
|
1081
|
+
severity: "warning",
|
|
1082
|
+
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
1083
|
+
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
1084
|
+
location: {
|
|
1085
|
+
kind: "schema",
|
|
1086
|
+
pointer
|
|
1087
|
+
}
|
|
1088
|
+
});
|
|
1089
|
+
if (node.type === "object") {
|
|
1090
|
+
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
1091
|
+
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
1092
|
+
return;
|
|
1093
|
+
}
|
|
1094
|
+
if (node.type === "array") {
|
|
1095
|
+
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
1096
|
+
return;
|
|
1097
|
+
}
|
|
1098
|
+
if (node.type === "tuple") {
|
|
1099
|
+
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
1100
|
+
return;
|
|
1101
|
+
}
|
|
1102
|
+
if (node.type === "not") {
|
|
1103
|
+
for (const member of node.members ?? []) visit(member, `${pointer}/not`);
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
if (node.type === "union" || node.type === "intersection") {
|
|
1107
|
+
const keyword = node.type === "intersection" ? "allOf" : node.strategy === "any" ? "anyOf" : "oneOf";
|
|
1108
|
+
for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/${keyword}/${index}`);
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
//#endregion
|
|
967
1112
|
//#region src/operation.ts
|
|
968
1113
|
function slugify(value) {
|
|
969
1114
|
return value.replace(/[^a-zA-Z0-9]/g, "-").replace(/-{2,}/g, "-").replace(/^-|-$/g, "");
|
|
@@ -1013,11 +1158,18 @@ function getRequestContentType({ operation, refs }) {
|
|
|
1013
1158
|
}
|
|
1014
1159
|
/** Merges path-level and operation-level parameters; operation-level wins per `in:name`. */
|
|
1015
1160
|
function getParameters({ operation, refs }) {
|
|
1016
|
-
const resolveParams = (params) => params.
|
|
1017
|
-
|
|
1018
|
-
|
|
1161
|
+
const resolveParams = (params, pointer) => params.flatMap((entry, index) => {
|
|
1162
|
+
const param = refs.derefKeepingRef(entry);
|
|
1163
|
+
return param && typeof param === "object" && "in" in param && "name" in param ? [{
|
|
1164
|
+
param,
|
|
1165
|
+
pointer: `${pointer}/parameters/${index}`
|
|
1166
|
+
}] : [];
|
|
1167
|
+
});
|
|
1168
|
+
const pathPointer = `#/paths/${escapePointerToken(operation.path)}`;
|
|
1169
|
+
const operationParams = resolveParams(operation.schema?.parameters || [], `${pathPointer}/${operation.method.toLowerCase()}`);
|
|
1170
|
+
const pathLevelParams = resolveParams(operation.pathItem.parameters ?? [], pathPointer);
|
|
1019
1171
|
const paramMap = /* @__PURE__ */ new Map();
|
|
1020
|
-
for (const
|
|
1172
|
+
for (const entry of [...pathLevelParams, ...operationParams]) if (entry.param.name && entry.param.in) paramMap.set(`${entry.param.in}:${entry.param.name}`, entry);
|
|
1021
1173
|
return Array.from(paramMap.values());
|
|
1022
1174
|
}
|
|
1023
1175
|
/** Schema for one media type of a `content` map, with the binary fallback for an emptied non-JSON entry. */
|
|
@@ -1065,7 +1217,7 @@ function collectPropertyKeysByFlag(schema, flag) {
|
|
|
1065
1217
|
}
|
|
1066
1218
|
return keys.length ? keys : null;
|
|
1067
1219
|
}
|
|
1068
|
-
function parseParameter({ param, parentName, options, parseSchema }) {
|
|
1220
|
+
function parseParameter({ param, pointer, parentName, options, parseSchema }) {
|
|
1069
1221
|
const schemaName = parentName && param.name ? pascalCase(`${parentName} ${param.name}`) : void 0;
|
|
1070
1222
|
const contentType = param.schema ? void 0 : Object.keys(param.content ?? {})[0];
|
|
1071
1223
|
const rawSchema = param.schema ?? (contentType ? param.content?.[contentType]?.schema : void 0);
|
|
@@ -1073,10 +1225,12 @@ function parseParameter({ param, parentName, options, parseSchema }) {
|
|
|
1073
1225
|
schema: rawSchema,
|
|
1074
1226
|
name: schemaName
|
|
1075
1227
|
}) : _kubb_kit.ast.factory.createSchema({ type: options.unknownType });
|
|
1228
|
+
if (rawSchema) scanSchema(schema, contentType ? `${pointer}/content/${escapePointerToken(contentType)}/schema` : `${pointer}/schema`);
|
|
1076
1229
|
const style = param.style;
|
|
1077
1230
|
const explode = param.explode;
|
|
1078
1231
|
const allowReserved = param.allowReserved;
|
|
1079
1232
|
return _kubb_kit.ast.factory.createParameter({
|
|
1233
|
+
pointer,
|
|
1080
1234
|
name: param.name,
|
|
1081
1235
|
in: param.in,
|
|
1082
1236
|
schema: {
|
|
@@ -1097,13 +1251,14 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1097
1251
|
const { refs, contentType, options, parseSchema } = ctx;
|
|
1098
1252
|
const operationId = getOperationId(operation);
|
|
1099
1253
|
const operationName = operationId ? pascalCase(operationId) : void 0;
|
|
1254
|
+
const pointer = `#/paths/${escapePointerToken(operation.path)}/${operation.method.toLowerCase()}`;
|
|
1100
1255
|
const parameters = getParameters({
|
|
1101
1256
|
operation,
|
|
1102
1257
|
refs
|
|
1103
|
-
}).map((
|
|
1258
|
+
}).map((entry) => parseParameter({
|
|
1104
1259
|
options,
|
|
1105
1260
|
parseSchema,
|
|
1106
|
-
|
|
1261
|
+
...entry,
|
|
1107
1262
|
parentName: operationName
|
|
1108
1263
|
}));
|
|
1109
1264
|
const body = getRequestBody({
|
|
@@ -1119,16 +1274,19 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1119
1274
|
refs
|
|
1120
1275
|
});
|
|
1121
1276
|
if (!schema) return [];
|
|
1277
|
+
const node = parseSchema({
|
|
1278
|
+
schema,
|
|
1279
|
+
name: requestBodyName
|
|
1280
|
+
});
|
|
1281
|
+
scanSchema(node, `${pointer}/requestBody/content/${escapePointerToken(ct)}/schema`);
|
|
1122
1282
|
return [_kubb_kit.ast.factory.createContent({
|
|
1123
1283
|
contentType: ct,
|
|
1124
|
-
schema: _kubb_kit.ast.optionality(
|
|
1125
|
-
schema,
|
|
1126
|
-
name: requestBodyName
|
|
1127
|
-
}), bodyRequired),
|
|
1284
|
+
schema: _kubb_kit.ast.optionality(node, bodyRequired),
|
|
1128
1285
|
keysToOmit: collectPropertyKeysByFlag(schema, "readOnly")
|
|
1129
1286
|
})];
|
|
1130
1287
|
});
|
|
1131
1288
|
const requestBody = content.length > 0 || body?.description ? {
|
|
1289
|
+
pointer: `${pointer}/requestBody`,
|
|
1132
1290
|
description: body?.description,
|
|
1133
1291
|
required: bodyRequired || void 0,
|
|
1134
1292
|
content: content.length > 0 ? content : void 0
|
|
@@ -1146,11 +1304,13 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1146
1304
|
contentType: ct,
|
|
1147
1305
|
refs
|
|
1148
1306
|
});
|
|
1307
|
+
const node = raw && Object.keys(raw).length > 0 ? parseSchema({
|
|
1308
|
+
schema: raw,
|
|
1309
|
+
name: responseName
|
|
1310
|
+
}) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType });
|
|
1311
|
+
if (raw && ct) scanSchema(node, `${pointer}/responses/${statusCode}/content/${escapePointerToken(ct)}/schema`);
|
|
1149
1312
|
return {
|
|
1150
|
-
schema:
|
|
1151
|
-
schema: raw,
|
|
1152
|
-
name: responseName
|
|
1153
|
-
}) : _kubb_kit.ast.factory.createSchema({ type: options.emptySchemaType }),
|
|
1313
|
+
schema: node,
|
|
1154
1314
|
keysToOmit: collectPropertyKeysByFlag(raw, "writeOnly")
|
|
1155
1315
|
};
|
|
1156
1316
|
};
|
|
@@ -1166,6 +1326,7 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1166
1326
|
...parseEntrySchema(contentType)
|
|
1167
1327
|
}));
|
|
1168
1328
|
return _kubb_kit.ast.factory.createResponse({
|
|
1329
|
+
pointer: `${pointer}/responses/${statusCode}`,
|
|
1169
1330
|
statusCode,
|
|
1170
1331
|
description: response?.description,
|
|
1171
1332
|
content: responseContent
|
|
@@ -1178,6 +1339,7 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1178
1339
|
return typeof fallback === "string" ? fallback : void 0;
|
|
1179
1340
|
};
|
|
1180
1341
|
return _kubb_kit.ast.factory.createOperation({
|
|
1342
|
+
pointer,
|
|
1181
1343
|
operationId,
|
|
1182
1344
|
protocol: "http",
|
|
1183
1345
|
method: operation.method.toUpperCase(),
|
|
@@ -1193,60 +1355,6 @@ function parseOperation({ operation, ...ctx }) {
|
|
|
1193
1355
|
});
|
|
1194
1356
|
}
|
|
1195
1357
|
//#endregion
|
|
1196
|
-
//#region src/emit/schemaShape.ts
|
|
1197
|
-
/**
|
|
1198
|
-
* Whether `format` maps to a dedicated type; `false` is what `KUBB_UNSUPPORTED_FORMAT` flags.
|
|
1199
|
-
*/
|
|
1200
|
-
function isHandledFormat(format) {
|
|
1201
|
-
return formatMap[format] !== void 0 || specialCasedFormats.has(format);
|
|
1202
|
-
}
|
|
1203
|
-
/**
|
|
1204
|
-
* Resolves the `dateType` option down to the value for one format. The scalar form of
|
|
1205
|
-
* `dateType` applies to every format; the object form picks `dateTime`/`date`/`time`
|
|
1206
|
-
* individually and defaults an omitted key to `'string'`.
|
|
1207
|
-
*/
|
|
1208
|
-
function resolveDateTypeValue(dateType, format) {
|
|
1209
|
-
if (typeof dateType !== "object" || dateType === null) return dateType;
|
|
1210
|
-
return dateType[format === "date-time" ? "dateTime" : format] ?? "string";
|
|
1211
|
-
}
|
|
1212
|
-
/**
|
|
1213
|
-
* Reads schema examples as an array. OAS 3.1 uses an `examples` array, but specs (including ones
|
|
1214
|
-
* labeled 3.1) still use the singular OAS 3.0 `example`, which the upgrader only converts on the
|
|
1215
|
-
* 3.0 -> 3.1 hop. Normalize both into one array so the AST node exposes only `examples`.
|
|
1216
|
-
*/
|
|
1217
|
-
function extractExamples(schema) {
|
|
1218
|
-
if (Array.isArray(schema.examples)) return schema.examples;
|
|
1219
|
-
return schema.example !== void 0 ? [schema.example] : void 0;
|
|
1220
|
-
}
|
|
1221
|
-
function hasStructuralKeywords(fragment) {
|
|
1222
|
-
return Object.keys(fragment).some((key) => structuralKeys.has(key));
|
|
1223
|
-
}
|
|
1224
|
-
/**
|
|
1225
|
-
* Flattens a keyword-only `allOf` into its parent schema.
|
|
1226
|
-
*
|
|
1227
|
-
* Only flattens when every member is a plain fragment, with no `$ref` and no structural keywords
|
|
1228
|
-
* (see `structuralKeys`). Outer schema values take precedence over fragment values. Returns the
|
|
1229
|
-
* original schema unchanged when flattening is unsafe.
|
|
1230
|
-
*
|
|
1231
|
-
* @example
|
|
1232
|
-
* ```ts
|
|
1233
|
-
* flattenSchema({ allOf: [{ description: 'A pet' }], type: 'object', properties: {} })
|
|
1234
|
-
* // { type: 'object', properties: {}, description: 'A pet' }
|
|
1235
|
-
* flattenSchema({ allOf: [{ $ref: '#/components/schemas/Pet' }] })
|
|
1236
|
-
* // returned unchanged, contains a $ref
|
|
1237
|
-
* ```
|
|
1238
|
-
*/
|
|
1239
|
-
function flattenSchema(schema) {
|
|
1240
|
-
if (!schema.allOf?.length) return schema;
|
|
1241
|
-
const allOfFragments = schema.allOf;
|
|
1242
|
-
if (allOfFragments.some((item) => isReference(item))) return schema;
|
|
1243
|
-
if (allOfFragments.some(hasStructuralKeywords)) return schema;
|
|
1244
|
-
const { allOf: _allOf, ...rest } = schema;
|
|
1245
|
-
const merged = rest;
|
|
1246
|
-
for (const fragment of allOfFragments) for (const [key, value] of Object.entries(fragment)) merged[key] ??= value;
|
|
1247
|
-
return merged;
|
|
1248
|
-
}
|
|
1249
|
-
//#endregion
|
|
1250
1358
|
//#region src/emit/createNode.ts
|
|
1251
1359
|
/**
|
|
1252
1360
|
* Builds a schema node from a converter's base context plus its type-specific fields. Every
|
|
@@ -1932,6 +2040,7 @@ function convertNumeric(context, type) {
|
|
|
1932
2040
|
* `options.unknownType`, otherwise parses it as a regular schema.
|
|
1933
2041
|
*/
|
|
1934
2042
|
function resolveMapSchema(mapSchema, { options, parse }) {
|
|
2043
|
+
if (mapSchema === false) return _kubb_kit.ast.factory.createSchema({ type: "never" });
|
|
1935
2044
|
if (mapSchema === true || typeof mapSchema === "object" && Object.keys(mapSchema).length === 0) return _kubb_kit.ast.factory.createSchema({ type: options.unknownType });
|
|
1936
2045
|
return parse({ schema: mapSchema });
|
|
1937
2046
|
}
|
|
@@ -1991,6 +2100,10 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse })
|
|
|
1991
2100
|
options,
|
|
1992
2101
|
parse
|
|
1993
2102
|
})])) : void 0;
|
|
2103
|
+
const propertyNames = "propertyNames" in schema && schema.propertyNames !== void 0 ? resolveMapSchema(schema.propertyNames, {
|
|
2104
|
+
options,
|
|
2105
|
+
parse
|
|
2106
|
+
}) : void 0;
|
|
1994
2107
|
const objectNode = createNode({
|
|
1995
2108
|
schema,
|
|
1996
2109
|
name,
|
|
@@ -2002,6 +2115,7 @@ function convertObject({ schema, name, nullable, defaultValue, options, parse })
|
|
|
2002
2115
|
properties,
|
|
2003
2116
|
additionalProperties: additionalPropertiesNode,
|
|
2004
2117
|
patternProperties,
|
|
2118
|
+
propertyNames,
|
|
2005
2119
|
minProperties: schema.minProperties,
|
|
2006
2120
|
maxProperties: schema.maxProperties
|
|
2007
2121
|
});
|
|
@@ -2127,7 +2241,7 @@ const schemaRules = [
|
|
|
2127
2241
|
convert: convertEnum
|
|
2128
2242
|
},
|
|
2129
2243
|
{
|
|
2130
|
-
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema,
|
|
2244
|
+
match: ({ schema, type }) => type === "object" || !!schema.properties || !!schema.additionalProperties || "patternProperties" in schema || "propertyNames" in schema,
|
|
2131
2245
|
convert: convertObject
|
|
2132
2246
|
},
|
|
2133
2247
|
{
|
|
@@ -2356,55 +2470,6 @@ function createRefs(document) {
|
|
|
2356
2470
|
};
|
|
2357
2471
|
}
|
|
2358
2472
|
//#endregion
|
|
2359
|
-
//#region src/schemaDiagnostics.ts
|
|
2360
|
-
/**
|
|
2361
|
-
* Reports the advisory diagnostics (`KUBB_UNSUPPORTED_FORMAT`, `KUBB_DEPRECATED`) for one freshly
|
|
2362
|
-
* converted top-level schema.
|
|
2363
|
-
*
|
|
2364
|
-
* Walks the node the parser produced, threading the RFC 6901 pointer as it descends so a nested
|
|
2365
|
-
* field reports against its full path (`#/components/schemas/Pet/properties/owner/properties/name`).
|
|
2366
|
-
* Refs are not followed, so the resolved schema is reported under its own walk.
|
|
2367
|
-
* Reports land in the active build run, are a no-op outside one, and repeats are deduped by the build.
|
|
2368
|
-
*/
|
|
2369
|
-
function scanSchema({ node, name }) {
|
|
2370
|
-
visit(node, `#/components/schemas/${escapePointerToken(name)}`);
|
|
2371
|
-
}
|
|
2372
|
-
function visit(node, pointer) {
|
|
2373
|
-
if (node.deprecated) _kubb_kit.Diagnostics.report({
|
|
2374
|
-
code: _kubb_kit.Diagnostics.code.deprecated,
|
|
2375
|
-
severity: "info",
|
|
2376
|
-
message: "This schema is marked as deprecated.",
|
|
2377
|
-
location: {
|
|
2378
|
-
kind: "schema",
|
|
2379
|
-
pointer
|
|
2380
|
-
}
|
|
2381
|
-
});
|
|
2382
|
-
if (typeof node.format === "string" && !isHandledFormat(node.format)) _kubb_kit.Diagnostics.report({
|
|
2383
|
-
code: _kubb_kit.Diagnostics.code.unsupportedFormat,
|
|
2384
|
-
severity: "warning",
|
|
2385
|
-
message: `Kubb does not map the format "${node.format}" to a specific type, so it falls back to the base type.`,
|
|
2386
|
-
help: `Use a format Kubb supports, or handle "${node.format}" with a custom parser or plugin.`,
|
|
2387
|
-
location: {
|
|
2388
|
-
kind: "schema",
|
|
2389
|
-
pointer
|
|
2390
|
-
}
|
|
2391
|
-
});
|
|
2392
|
-
if (node.type === "object") {
|
|
2393
|
-
for (const property of node.properties) visit(property.schema, `${pointer}/properties/${escapePointerToken(property.name)}`);
|
|
2394
|
-
if (node.additionalProperties && typeof node.additionalProperties === "object") visit(node.additionalProperties, `${pointer}/additionalProperties`);
|
|
2395
|
-
return;
|
|
2396
|
-
}
|
|
2397
|
-
if (node.type === "array") {
|
|
2398
|
-
for (const item of node.items ?? []) visit(item, `${pointer}/items`);
|
|
2399
|
-
return;
|
|
2400
|
-
}
|
|
2401
|
-
if (node.type === "tuple") {
|
|
2402
|
-
for (const [index, item] of (node.items ?? []).entries()) visit(item, `${pointer}/items/${index}`);
|
|
2403
|
-
return;
|
|
2404
|
-
}
|
|
2405
|
-
if (node.type === "union" || node.type === "intersection" || node.type === "not") for (const [index, member] of (node.members ?? []).entries()) visit(member, `${pointer}/members/${index}`);
|
|
2406
|
-
}
|
|
2407
|
-
//#endregion
|
|
2408
2473
|
//#region src/sourceLocator.ts
|
|
2409
2474
|
let cached;
|
|
2410
2475
|
const toRange = ([start, end]) => ({
|
|
@@ -2445,17 +2510,122 @@ function findRefUsage(document, ref) {
|
|
|
2445
2510
|
return found[0];
|
|
2446
2511
|
}
|
|
2447
2512
|
/**
|
|
2448
|
-
*
|
|
2513
|
+
* One pass over JSON text, so a located diagnostic in a large JSON spec skips `yaml.parseDocument`.
|
|
2514
|
+
* Holds the range of every key and array item by pointer, and the first usage of every `$ref`, as
|
|
2515
|
+
* `findPointer` and `findRefUsage` return them. Throws where the text breaks JSON syntax, such as a
|
|
2516
|
+
* comment, an unquoted key, or an unquoted `$ref` value. Other bare values, such as `object`, pass.
|
|
2449
2517
|
*/
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2518
|
+
function indexJson(source) {
|
|
2519
|
+
const pointers = /* @__PURE__ */ new Map();
|
|
2520
|
+
const refs = /* @__PURE__ */ new Map();
|
|
2521
|
+
let at = source.indexOf("{");
|
|
2522
|
+
const fail = () => {
|
|
2523
|
+
throw new SyntaxError(`Unexpected character at ${at}`);
|
|
2524
|
+
};
|
|
2525
|
+
const skipSpace = () => {
|
|
2526
|
+
for (let code = source.charCodeAt(at); code === 32 || code === 9 || code === 10 || code === 13; code = source.charCodeAt(++at));
|
|
2527
|
+
};
|
|
2528
|
+
const readString = () => {
|
|
2529
|
+
if (source[at] !== "\"") fail();
|
|
2530
|
+
const start = at;
|
|
2531
|
+
for (at++; source.charCodeAt(at) !== 34; at++) {
|
|
2532
|
+
if (at >= source.length) fail();
|
|
2533
|
+
if (source.charCodeAt(at) === 92) at++;
|
|
2534
|
+
}
|
|
2535
|
+
const raw = source.slice(start + 1, at++);
|
|
2536
|
+
return {
|
|
2537
|
+
text: raw.includes("\\") ? JSON.parse(source.slice(start, at)) : raw,
|
|
2538
|
+
start,
|
|
2539
|
+
end: at
|
|
2540
|
+
};
|
|
2541
|
+
};
|
|
2542
|
+
const expect = (char) => {
|
|
2543
|
+
skipSpace();
|
|
2544
|
+
if (source[at++] !== char) fail();
|
|
2545
|
+
skipSpace();
|
|
2546
|
+
};
|
|
2547
|
+
const walk = (pointer) => {
|
|
2548
|
+
const open = source[at];
|
|
2549
|
+
if (open === "\"") {
|
|
2550
|
+
readString();
|
|
2551
|
+
return;
|
|
2552
|
+
}
|
|
2553
|
+
if (open !== "{" && open !== "[") {
|
|
2554
|
+
const start = at;
|
|
2555
|
+
while (at < source.length && !/[\s,\]}]/.test(source[at])) at++;
|
|
2556
|
+
if (at === start) fail();
|
|
2557
|
+
return;
|
|
2558
|
+
}
|
|
2559
|
+
const close = open === "{" ? "}" : "]";
|
|
2560
|
+
at++;
|
|
2561
|
+
for (let index = 0;; index++) {
|
|
2562
|
+
skipSpace();
|
|
2563
|
+
if (source[at] === close) break;
|
|
2564
|
+
if (index > 0) expect(",");
|
|
2565
|
+
if (open === "[") {
|
|
2566
|
+
const start = at;
|
|
2567
|
+
const next = `${pointer}/${index}`;
|
|
2568
|
+
walk(next);
|
|
2569
|
+
if (!pointers.has(next)) pointers.set(next, {
|
|
2570
|
+
start,
|
|
2571
|
+
end: at
|
|
2572
|
+
});
|
|
2573
|
+
continue;
|
|
2574
|
+
}
|
|
2575
|
+
const key = readString();
|
|
2576
|
+
const next = `${pointer}/${escapePointerToken(key.text)}`;
|
|
2577
|
+
if (!pointers.has(next)) pointers.set(next, {
|
|
2578
|
+
start: key.start,
|
|
2579
|
+
end: key.end
|
|
2580
|
+
});
|
|
2581
|
+
expect(":");
|
|
2582
|
+
if (key.text !== "$ref" || source[at] === "{" || source[at] === "[") {
|
|
2583
|
+
walk(next);
|
|
2584
|
+
continue;
|
|
2585
|
+
}
|
|
2586
|
+
const value = readString();
|
|
2587
|
+
if (!refs.has(value.text)) refs.set(value.text, {
|
|
2588
|
+
start: value.start,
|
|
2589
|
+
end: value.end
|
|
2590
|
+
});
|
|
2591
|
+
}
|
|
2592
|
+
at++;
|
|
2593
|
+
};
|
|
2594
|
+
walk("#");
|
|
2595
|
+
return {
|
|
2596
|
+
pointers,
|
|
2597
|
+
refs
|
|
2598
|
+
};
|
|
2599
|
+
}
|
|
2600
|
+
/** Indexes plain JSON text; anything else goes to the YAML parser. */
|
|
2601
|
+
function parse(source) {
|
|
2602
|
+
if (/^[ \t\n\r]*\{/.test(source)) try {
|
|
2603
|
+
return {
|
|
2604
|
+
source,
|
|
2605
|
+
json: indexJson(source)
|
|
2606
|
+
};
|
|
2607
|
+
} catch {}
|
|
2608
|
+
return {
|
|
2454
2609
|
source,
|
|
2455
2610
|
document: yaml.parseDocument(source)
|
|
2456
2611
|
};
|
|
2612
|
+
}
|
|
2613
|
+
/**
|
|
2614
|
+
* Maps OpenAPI JSON pointers and unresolved references to ranges in YAML or JSON source.
|
|
2615
|
+
*/
|
|
2616
|
+
const locate = (source, location) => {
|
|
2617
|
+
if (!("pointer" in location)) return void 0;
|
|
2618
|
+
cached = cached?.source === source ? {
|
|
2619
|
+
...cached,
|
|
2620
|
+
source
|
|
2621
|
+
} : parse(source);
|
|
2622
|
+
const ref = "ref" in location ? location.ref : void 0;
|
|
2623
|
+
if ("json" in cached) {
|
|
2624
|
+
const { pointers, refs } = cached.json;
|
|
2625
|
+
return pointers.get(location.pointer) ?? (ref ? refs.get(ref) : void 0);
|
|
2626
|
+
}
|
|
2457
2627
|
const { document } = cached;
|
|
2458
|
-
return findPointer(document, location.pointer) ?? (
|
|
2628
|
+
return findPointer(document, location.pointer) ?? (ref ? findRefUsage(document, ref) : void 0);
|
|
2459
2629
|
};
|
|
2460
2630
|
//#endregion
|
|
2461
2631
|
//#region src/adapter.ts
|
|
@@ -2512,25 +2682,26 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2512
2682
|
}),
|
|
2513
2683
|
build({ validate, contentType, server, discriminator, enums, ...parserOptions }) {
|
|
2514
2684
|
let parsedDocument = null;
|
|
2685
|
+
const pendingValidations = [];
|
|
2515
2686
|
const inputCache = /* @__PURE__ */ new WeakMap();
|
|
2516
2687
|
/**
|
|
2517
2688
|
* Parses every schema and operation once, resolving ref aliases and discriminator children from that same pass.
|
|
2518
2689
|
*/
|
|
2519
|
-
function parseInput({ document, refs, schemas, parseSchema }) {
|
|
2690
|
+
function parseInput({ document, refs, schemas, pointers, parseSchema }) {
|
|
2520
2691
|
const parsedByName = /* @__PURE__ */ new Map();
|
|
2521
2692
|
const refAliasMap = /* @__PURE__ */ new Map();
|
|
2522
2693
|
const enumNames = [];
|
|
2523
2694
|
const discriminatorParentNodes = [];
|
|
2524
2695
|
for (const [name, schema] of Object.entries(schemas)) {
|
|
2525
|
-
const node =
|
|
2526
|
-
|
|
2527
|
-
|
|
2528
|
-
|
|
2696
|
+
const node = {
|
|
2697
|
+
...parseSchema({
|
|
2698
|
+
schema,
|
|
2699
|
+
name
|
|
2700
|
+
}),
|
|
2701
|
+
pointer: pointers.get(name)
|
|
2702
|
+
};
|
|
2529
2703
|
parsedByName.set(name, node);
|
|
2530
|
-
scanSchema(
|
|
2531
|
-
node,
|
|
2532
|
-
name
|
|
2533
|
-
});
|
|
2704
|
+
scanSchema(node);
|
|
2534
2705
|
if (node.type === "ref" && node.name && node.name !== name) refAliasMap.set(name, node);
|
|
2535
2706
|
if (_kubb_kit.ast.narrowSchema(node, "enum") && node.name) enumNames.push(node.name);
|
|
2536
2707
|
if (discriminator === "propagate" && (schema.oneOf ?? schema.anyOf) && schema.discriminator?.propertyName) discriminatorParentNodes.push(node);
|
|
@@ -2555,7 +2726,8 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2555
2726
|
const base = child ? patchDiscriminatorNode(parsed, child) : parsed;
|
|
2556
2727
|
const node = aliased ? {
|
|
2557
2728
|
...aliased,
|
|
2558
|
-
name
|
|
2729
|
+
name,
|
|
2730
|
+
pointer: parsed.pointer
|
|
2559
2731
|
} : base;
|
|
2560
2732
|
schemaNodes.push(promotedEnums ? refPromotedEnums(node, promotedEnums) : node);
|
|
2561
2733
|
}
|
|
@@ -2621,6 +2793,9 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2621
2793
|
assertDocument(document);
|
|
2622
2794
|
await validateDocument(document, options);
|
|
2623
2795
|
},
|
|
2796
|
+
async settle() {
|
|
2797
|
+
await Promise.all(pendingValidations.splice(0));
|
|
2798
|
+
},
|
|
2624
2799
|
async parse(source, { signal } = {}) {
|
|
2625
2800
|
signal?.throwIfAborted();
|
|
2626
2801
|
const cached = inputCache.get(source);
|
|
@@ -2629,14 +2804,15 @@ const adapterOas = (0, _kubb_core.defineExtension)({
|
|
|
2629
2804
|
const document = await parseFromConfig(source);
|
|
2630
2805
|
signal?.throwIfAborted();
|
|
2631
2806
|
assertDocument(document);
|
|
2632
|
-
if (validate)
|
|
2807
|
+
if (validate) pendingValidations.push(validateDocumentInWorker(document));
|
|
2633
2808
|
parsedDocument = document;
|
|
2634
2809
|
const refs = createRefs(document);
|
|
2635
|
-
const { schemas, renames } = getSchemas(document, { contentType }, refs);
|
|
2810
|
+
const { schemas, renames, pointers } = getSchemas(document, { contentType }, refs);
|
|
2636
2811
|
return parseInput({
|
|
2637
2812
|
document,
|
|
2638
2813
|
refs,
|
|
2639
2814
|
schemas,
|
|
2815
|
+
pointers,
|
|
2640
2816
|
parseSchema: createSchemaParser({
|
|
2641
2817
|
document,
|
|
2642
2818
|
refs,
|