@postman-cse/onboarding-bootstrap 1.0.0 → 1.2.0
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/README.md +24 -4
- package/dist/action.cjs +1068 -107
- package/dist/cli.cjs +1068 -107
- package/dist/index.cjs +1068 -107
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -49627,8 +49627,6 @@ var COMPATIBLE_SCHEMA_URIS = /* @__PURE__ */ new Set([
|
|
|
49627
49627
|
`${DRAFT_2020_12_SCHEMA_URI}#`
|
|
49628
49628
|
]);
|
|
49629
49629
|
var ASSERTION_KEYS = /* @__PURE__ */ new Set([
|
|
49630
|
-
"$defs",
|
|
49631
|
-
"$id",
|
|
49632
49630
|
"$ref",
|
|
49633
49631
|
"$schema",
|
|
49634
49632
|
"additionalItems",
|
|
@@ -49673,6 +49671,10 @@ var ASSERTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
49673
49671
|
]);
|
|
49674
49672
|
var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
49675
49673
|
"title",
|
|
49674
|
+
"$comment",
|
|
49675
|
+
"$defs",
|
|
49676
|
+
"definitions",
|
|
49677
|
+
"$id",
|
|
49676
49678
|
"description",
|
|
49677
49679
|
"default",
|
|
49678
49680
|
"example",
|
|
@@ -49683,8 +49685,32 @@ var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
49683
49685
|
"xml",
|
|
49684
49686
|
"externalDocs",
|
|
49685
49687
|
"discriminator",
|
|
49686
|
-
"
|
|
49688
|
+
"contentEncoding",
|
|
49689
|
+
"contentMediaType",
|
|
49690
|
+
"contentSchema"
|
|
49691
|
+
]);
|
|
49692
|
+
var ASSERTED_FORMATS = /* @__PURE__ */ new Set([
|
|
49693
|
+
"date-time",
|
|
49694
|
+
"date",
|
|
49695
|
+
"time",
|
|
49696
|
+
"duration",
|
|
49697
|
+
"email",
|
|
49698
|
+
"hostname",
|
|
49699
|
+
"ipv4",
|
|
49700
|
+
"ipv6",
|
|
49701
|
+
"uri",
|
|
49702
|
+
"uri-reference",
|
|
49703
|
+
"uri-template",
|
|
49704
|
+
"uuid",
|
|
49705
|
+
"json-pointer",
|
|
49706
|
+
"relative-json-pointer",
|
|
49707
|
+
"regex"
|
|
49687
49708
|
]);
|
|
49709
|
+
var INT32_MIN = -2147483648;
|
|
49710
|
+
var INT32_MAX = 2147483647;
|
|
49711
|
+
var MAX_REFERENCED_SCHEMAS = 400;
|
|
49712
|
+
var DRAFT_2020_12_ONLY_KEYS = /* @__PURE__ */ new Set(["prefixItems", "dependentRequired", "dependentSchemas", "minContains", "maxContains", "unevaluatedItems", "unevaluatedProperties"]);
|
|
49713
|
+
var DRAFT_07_ONLY_KEYS = /* @__PURE__ */ new Set(["dependencies", "additionalItems"]);
|
|
49688
49714
|
function asRecord3(value) {
|
|
49689
49715
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
49690
49716
|
return value;
|
|
@@ -49702,8 +49728,8 @@ function resolvePointer(root, ref) {
|
|
|
49702
49728
|
function unsupported(message) {
|
|
49703
49729
|
return { unsupported: message };
|
|
49704
49730
|
}
|
|
49705
|
-
function
|
|
49706
|
-
const values = asArray(required).map((entry) => String(entry)).filter((entry) => !
|
|
49731
|
+
function mergeRequiredWithoutStripped(required, strippedProperties) {
|
|
49732
|
+
const values = asArray(required).map((entry) => String(entry)).filter((entry) => !strippedProperties.has(entry));
|
|
49707
49733
|
return values.length > 0 ? values : void 0;
|
|
49708
49734
|
}
|
|
49709
49735
|
function hasUnsupported(child2) {
|
|
@@ -49719,11 +49745,31 @@ function rootDialect(root, version, schemaRecord) {
|
|
|
49719
49745
|
if (declared.includes("draft-07")) return DRAFT_07_SCHEMA_URI;
|
|
49720
49746
|
return DRAFT_2020_12_SCHEMA_URI;
|
|
49721
49747
|
}
|
|
49722
|
-
function
|
|
49723
|
-
const
|
|
49724
|
-
if (
|
|
49748
|
+
function registerDef(ctx, ref) {
|
|
49749
|
+
const existing = ctx.defs.get(ref);
|
|
49750
|
+
if (existing) return existing;
|
|
49751
|
+
if (ctx.defs.size >= MAX_REFERENCED_SCHEMAS) {
|
|
49752
|
+
const entry2 = { name: "overflow", unsupported: `OpenAPI schema reference graph exceeded ${MAX_REFERENCED_SCHEMAS} referenced schemas` };
|
|
49753
|
+
return entry2;
|
|
49754
|
+
}
|
|
49755
|
+
const entry = { name: `d${ctx.defs.size}` };
|
|
49756
|
+
ctx.defs.set(ref, entry);
|
|
49757
|
+
const resolved = resolvePointer(ctx.root, ref);
|
|
49758
|
+
if (resolved === void 0) {
|
|
49759
|
+
entry.unsupported = `Unresolved OpenAPI $ref: ${ref}`;
|
|
49760
|
+
return entry;
|
|
49761
|
+
}
|
|
49762
|
+
const normalized = normalizeSchema(ctx, resolved, { depth: 0, rootSchema: false });
|
|
49763
|
+
const bad = hasUnsupported(normalized);
|
|
49764
|
+
if (bad) entry.unsupported = bad;
|
|
49765
|
+
else entry.schema = normalized;
|
|
49766
|
+
return entry;
|
|
49767
|
+
}
|
|
49768
|
+
function normalizeSchema(ctx, schema2, options) {
|
|
49769
|
+
const depth = options.depth;
|
|
49770
|
+
if (depth > 50) return unsupported("OpenAPI schema nesting depth exceeded 50");
|
|
49725
49771
|
if (Array.isArray(schema2)) {
|
|
49726
|
-
const normalized2 = schema2.map((entry) => normalizeSchema(
|
|
49772
|
+
const normalized2 = schema2.map((entry) => normalizeSchema(ctx, entry, { depth: depth + 1, rootSchema: false }));
|
|
49727
49773
|
const bad = normalized2.map(hasUnsupported).find(Boolean);
|
|
49728
49774
|
return bad ? unsupported(bad) : normalized2;
|
|
49729
49775
|
}
|
|
@@ -49732,40 +49778,74 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49732
49778
|
const ref = typeof record.$ref === "string" ? record.$ref : "";
|
|
49733
49779
|
if (ref) {
|
|
49734
49780
|
if (!ref.startsWith("#/")) return unsupported(`External OpenAPI $ref remained unresolved: ${ref}`);
|
|
49735
|
-
const
|
|
49736
|
-
|
|
49737
|
-
|
|
49738
|
-
|
|
49739
|
-
|
|
49740
|
-
|
|
49741
|
-
|
|
49742
|
-
|
|
49743
|
-
|
|
49744
|
-
|
|
49745
|
-
|
|
49746
|
-
const
|
|
49747
|
-
if (
|
|
49748
|
-
|
|
49749
|
-
|
|
49750
|
-
|
|
49751
|
-
|
|
49752
|
-
rootSchema: false,
|
|
49753
|
-
stack: [...stack, ref]
|
|
49754
|
-
});
|
|
49781
|
+
const siblingEntries = ctx.version === "3.1" ? Object.entries(record).filter(([key]) => key !== "$ref") : [];
|
|
49782
|
+
const inlineStack = options.inlineStack ?? [];
|
|
49783
|
+
if (options.rootSchema && siblingEntries.length === 0 && !inlineStack.includes(ref)) {
|
|
49784
|
+
const resolved = resolvePointer(ctx.root, ref);
|
|
49785
|
+
if (resolved === void 0) return unsupported(`Unresolved OpenAPI $ref: ${ref}`);
|
|
49786
|
+
const inlined = normalizeSchema(ctx, resolved, { depth, rootSchema: true, inlineStack: [...inlineStack, ref] });
|
|
49787
|
+
const bad = hasUnsupported(inlined);
|
|
49788
|
+
return bad ? unsupported(bad) : inlined;
|
|
49789
|
+
}
|
|
49790
|
+
const entry = registerDef(ctx, ref);
|
|
49791
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
49792
|
+
const refNode = { $ref: `#/$defs/${entry.name}` };
|
|
49793
|
+
if (siblingEntries.length === 0) {
|
|
49794
|
+
if (options.rootSchema) refNode.$schema = ctx.dialect;
|
|
49795
|
+
return refNode;
|
|
49796
|
+
}
|
|
49797
|
+
const siblingSchema = normalizeSchema(ctx, Object.fromEntries(siblingEntries), { depth: depth + 1, rootSchema: false });
|
|
49755
49798
|
const siblingUnsupported = hasUnsupported(siblingSchema);
|
|
49756
49799
|
if (siblingUnsupported) return unsupported(siblingUnsupported);
|
|
49757
|
-
const wrapper = { allOf: [
|
|
49758
|
-
if (options.rootSchema) wrapper.$schema =
|
|
49800
|
+
const wrapper = { allOf: [refNode, siblingSchema] };
|
|
49801
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
49759
49802
|
return wrapper;
|
|
49760
49803
|
}
|
|
49761
49804
|
const sourceSchema = { ...record };
|
|
49762
|
-
const nullable = sourceSchema.nullable === true &&
|
|
49805
|
+
const nullable = sourceSchema.nullable === true && ctx.version === "3.0";
|
|
49763
49806
|
delete sourceSchema.nullable;
|
|
49764
|
-
const
|
|
49807
|
+
const discriminator = asRecord3(sourceSchema.discriminator);
|
|
49808
|
+
if (discriminator && typeof discriminator.propertyName === "string" && discriminator.propertyName) {
|
|
49809
|
+
const propertyName = discriminator.propertyName;
|
|
49810
|
+
const branchKey = Array.isArray(sourceSchema.oneOf) ? "oneOf" : Array.isArray(sourceSchema.anyOf) ? "anyOf" : "";
|
|
49811
|
+
const members = branchKey ? sourceSchema[branchKey] : [];
|
|
49812
|
+
const memberRefs = members.map((member) => {
|
|
49813
|
+
const memberRecord = asRecord3(member);
|
|
49814
|
+
const memberRef = memberRecord && Object.keys(memberRecord).length === 1 && typeof memberRecord.$ref === "string" ? memberRecord.$ref : "";
|
|
49815
|
+
return memberRef.startsWith("#/") ? memberRef : "";
|
|
49816
|
+
});
|
|
49817
|
+
if (branchKey && members.length > 0 && memberRefs.every(Boolean)) {
|
|
49818
|
+
const valuesByRef = /* @__PURE__ */ new Map();
|
|
49819
|
+
const refByMappingKey = /* @__PURE__ */ new Map();
|
|
49820
|
+
for (const [value, target] of Object.entries(asRecord3(discriminator.mapping) ?? {})) {
|
|
49821
|
+
if (typeof target !== "string" || !target) continue;
|
|
49822
|
+
const targetRef = target.startsWith("#/") ? target : `#/components/schemas/${target}`;
|
|
49823
|
+
valuesByRef.set(targetRef, [...valuesByRef.get(targetRef) ?? [], value]);
|
|
49824
|
+
refByMappingKey.set(value, targetRef);
|
|
49825
|
+
}
|
|
49826
|
+
sourceSchema[branchKey] = members.map((member, index) => {
|
|
49827
|
+
const memberRef = memberRefs[index] ?? "";
|
|
49828
|
+
const tail = (memberRef.split("/").pop() ?? "").replace(/~1/g, "/").replace(/~0/g, "~");
|
|
49829
|
+
const values = [...valuesByRef.get(memberRef) ?? []];
|
|
49830
|
+
const tailClaimedElsewhere = refByMappingKey.has(tail) && refByMappingKey.get(tail) !== memberRef;
|
|
49831
|
+
if (tail && !tailClaimedElsewhere && !values.includes(tail)) values.push(tail);
|
|
49832
|
+
const dispatch = values.length === 1 ? { const: values[0] } : { enum: values };
|
|
49833
|
+
return { allOf: [member, { type: "object", required: [propertyName], properties: { [propertyName]: dispatch } }] };
|
|
49834
|
+
});
|
|
49835
|
+
} else {
|
|
49836
|
+
ctx.notes.add("discriminator");
|
|
49837
|
+
}
|
|
49838
|
+
}
|
|
49839
|
+
const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
|
|
49840
|
+
const strippedProperties = /* @__PURE__ */ new Set();
|
|
49765
49841
|
const rawProperties = asRecord3(sourceSchema.properties);
|
|
49766
49842
|
if (rawProperties) {
|
|
49767
49843
|
for (const [propertyName, propertySchema] of Object.entries(rawProperties)) {
|
|
49768
|
-
|
|
49844
|
+
let flagSource = asRecord3(propertySchema);
|
|
49845
|
+
if (typeof flagSource?.$ref === "string" && flagSource.$ref.startsWith("#/")) {
|
|
49846
|
+
flagSource = asRecord3(resolvePointer(ctx.root, flagSource.$ref)) ?? flagSource;
|
|
49847
|
+
}
|
|
49848
|
+
if (flagSource?.[directionStrippedFlag] === true) strippedProperties.add(propertyName);
|
|
49769
49849
|
}
|
|
49770
49850
|
}
|
|
49771
49851
|
const normalized = {};
|
|
@@ -49792,17 +49872,69 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49792
49872
|
delete normalized.maximum;
|
|
49793
49873
|
continue;
|
|
49794
49874
|
}
|
|
49875
|
+
if (key === "format") {
|
|
49876
|
+
if (typeof value === "string" && ASSERTED_FORMATS.has(value)) normalized.format = value;
|
|
49877
|
+
continue;
|
|
49878
|
+
}
|
|
49795
49879
|
if (!ASSERTION_KEYS.has(key)) return unsupported(`Unsupported OpenAPI schema keyword: ${key}`);
|
|
49796
|
-
if (
|
|
49880
|
+
if (ctx.dialect === DRAFT_07_SCHEMA_URI && DRAFT_2020_12_ONLY_KEYS.has(key)) {
|
|
49881
|
+
return unsupported(`${key} requires the JSON Schema 2020-12 dialect`);
|
|
49882
|
+
}
|
|
49883
|
+
if (ctx.dialect === DRAFT_2020_12_SCHEMA_URI && DRAFT_07_ONLY_KEYS.has(key)) {
|
|
49884
|
+
return unsupported(`${key} is a draft-07 keyword and is unsupported under JSON Schema 2020-12`);
|
|
49885
|
+
}
|
|
49886
|
+
if (key === "items" && Array.isArray(value) && ctx.version === "3.0") {
|
|
49797
49887
|
return unsupported("Tuple array items are unsupported in OpenAPI 3.0");
|
|
49798
49888
|
}
|
|
49889
|
+
if (key === "patternProperties" || key === "dependentSchemas") {
|
|
49890
|
+
const map2 = asRecord3(value);
|
|
49891
|
+
if (!map2) continue;
|
|
49892
|
+
const next = {};
|
|
49893
|
+
for (const [mapKey, mapSchema] of Object.entries(map2)) {
|
|
49894
|
+
const child3 = normalizeSchema(ctx, mapSchema, { depth: depth + 1, rootSchema: false });
|
|
49895
|
+
const bad2 = hasUnsupported(child3);
|
|
49896
|
+
if (bad2) return unsupported(bad2);
|
|
49897
|
+
next[mapKey] = child3;
|
|
49898
|
+
}
|
|
49899
|
+
normalized[key] = next;
|
|
49900
|
+
continue;
|
|
49901
|
+
}
|
|
49902
|
+
if (key === "dependentRequired") {
|
|
49903
|
+
const map2 = asRecord3(value);
|
|
49904
|
+
if (!map2) continue;
|
|
49905
|
+
for (const names of Object.values(map2)) {
|
|
49906
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
|
|
49907
|
+
return unsupported("dependentRequired requires string array values");
|
|
49908
|
+
}
|
|
49909
|
+
}
|
|
49910
|
+
normalized.dependentRequired = value;
|
|
49911
|
+
continue;
|
|
49912
|
+
}
|
|
49913
|
+
if (key === "dependencies") {
|
|
49914
|
+
const map2 = asRecord3(value);
|
|
49915
|
+
if (!map2) continue;
|
|
49916
|
+
const next = {};
|
|
49917
|
+
for (const [mapKey, dependency] of Object.entries(map2)) {
|
|
49918
|
+
if (Array.isArray(dependency)) {
|
|
49919
|
+
if (dependency.some((name) => typeof name !== "string")) return unsupported("dependencies requires string array or schema values");
|
|
49920
|
+
next[mapKey] = dependency;
|
|
49921
|
+
continue;
|
|
49922
|
+
}
|
|
49923
|
+
const child3 = normalizeSchema(ctx, dependency, { depth: depth + 1, rootSchema: false });
|
|
49924
|
+
const bad2 = hasUnsupported(child3);
|
|
49925
|
+
if (bad2) return unsupported(bad2);
|
|
49926
|
+
next[mapKey] = child3;
|
|
49927
|
+
}
|
|
49928
|
+
normalized.dependencies = next;
|
|
49929
|
+
continue;
|
|
49930
|
+
}
|
|
49799
49931
|
if (key === "properties") {
|
|
49800
49932
|
const properties = asRecord3(value);
|
|
49801
49933
|
if (!properties) continue;
|
|
49802
49934
|
const nextProperties = {};
|
|
49803
49935
|
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
49804
|
-
if (
|
|
49805
|
-
const child3 = normalizeSchema(
|
|
49936
|
+
if (strippedProperties.has(propertyName)) continue;
|
|
49937
|
+
const child3 = normalizeSchema(ctx, propertySchema, { depth: depth + 1, rootSchema: false });
|
|
49806
49938
|
const bad2 = hasUnsupported(child3);
|
|
49807
49939
|
if (bad2) return unsupported(bad2);
|
|
49808
49940
|
nextProperties[propertyName] = child3;
|
|
@@ -49811,16 +49943,46 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49811
49943
|
continue;
|
|
49812
49944
|
}
|
|
49813
49945
|
if (key === "required") {
|
|
49814
|
-
const required =
|
|
49946
|
+
const required = mergeRequiredWithoutStripped(value, strippedProperties);
|
|
49815
49947
|
if (required) normalized.required = required;
|
|
49816
49948
|
continue;
|
|
49817
49949
|
}
|
|
49818
|
-
const child2 = normalizeSchema(
|
|
49950
|
+
const child2 = normalizeSchema(ctx, value, { depth: depth + 1, rootSchema: false });
|
|
49819
49951
|
const bad = hasUnsupported(child2);
|
|
49820
49952
|
if (bad) return unsupported(bad);
|
|
49821
49953
|
normalized[key] = child2;
|
|
49822
49954
|
}
|
|
49823
|
-
if (
|
|
49955
|
+
if (typeof normalized.minimum === "number" && typeof normalized.exclusiveMinimum === "number") {
|
|
49956
|
+
if (normalized.exclusiveMinimum >= normalized.minimum) delete normalized.minimum;
|
|
49957
|
+
else delete normalized.exclusiveMinimum;
|
|
49958
|
+
}
|
|
49959
|
+
if (typeof normalized.maximum === "number" && typeof normalized.exclusiveMaximum === "number") {
|
|
49960
|
+
if (normalized.exclusiveMaximum <= normalized.maximum) delete normalized.maximum;
|
|
49961
|
+
else delete normalized.exclusiveMaximum;
|
|
49962
|
+
}
|
|
49963
|
+
if (sourceSchema.format === "int32") {
|
|
49964
|
+
const type2 = normalized.type;
|
|
49965
|
+
const isInteger2 = type2 === "integer" || Array.isArray(type2) && type2.includes("integer");
|
|
49966
|
+
if (isInteger2) {
|
|
49967
|
+
if (typeof normalized.exclusiveMinimum === "number") {
|
|
49968
|
+
if (normalized.exclusiveMinimum < INT32_MIN) {
|
|
49969
|
+
delete normalized.exclusiveMinimum;
|
|
49970
|
+
normalized.minimum = INT32_MIN;
|
|
49971
|
+
}
|
|
49972
|
+
} else if (typeof normalized.minimum !== "number" || normalized.minimum < INT32_MIN) {
|
|
49973
|
+
normalized.minimum = INT32_MIN;
|
|
49974
|
+
}
|
|
49975
|
+
if (typeof normalized.exclusiveMaximum === "number") {
|
|
49976
|
+
if (normalized.exclusiveMaximum > INT32_MAX) {
|
|
49977
|
+
delete normalized.exclusiveMaximum;
|
|
49978
|
+
normalized.maximum = INT32_MAX;
|
|
49979
|
+
}
|
|
49980
|
+
} else if (typeof normalized.maximum !== "number" || normalized.maximum > INT32_MAX) {
|
|
49981
|
+
normalized.maximum = INT32_MAX;
|
|
49982
|
+
}
|
|
49983
|
+
}
|
|
49984
|
+
}
|
|
49985
|
+
if (options.rootSchema) normalized.$schema = ctx.dialect;
|
|
49824
49986
|
if (nullable) {
|
|
49825
49987
|
if (typeof normalized.type === "string") {
|
|
49826
49988
|
normalized.type = [normalized.type, "null"];
|
|
@@ -49832,23 +49994,108 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49832
49994
|
const wrappedSchema = { ...normalized };
|
|
49833
49995
|
delete wrappedSchema.$schema;
|
|
49834
49996
|
const wrapper = { anyOf: [{ type: "null" }, wrappedSchema] };
|
|
49835
|
-
if (options.rootSchema) wrapper.$schema =
|
|
49997
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
49836
49998
|
return wrapper;
|
|
49837
49999
|
}
|
|
49838
50000
|
}
|
|
49839
50001
|
return normalized;
|
|
49840
50002
|
}
|
|
49841
|
-
function
|
|
50003
|
+
function isSchemaGraphOverflow(packed) {
|
|
50004
|
+
return typeof packed.unsupported === "string" && packed.unsupported.startsWith("OpenAPI schema reference graph exceeded");
|
|
50005
|
+
}
|
|
50006
|
+
function packSchema(root, schema2, version, direction = "response") {
|
|
49842
50007
|
try {
|
|
50008
|
+
if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
|
|
50009
|
+
if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
|
|
49843
50010
|
const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
|
|
49844
|
-
const
|
|
50011
|
+
const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map(), notes: /* @__PURE__ */ new Set() };
|
|
50012
|
+
const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
|
|
49845
50013
|
const message = hasUnsupported(normalized);
|
|
49846
|
-
|
|
50014
|
+
if (message) return unsupported(message);
|
|
50015
|
+
const aliasTargets = /* @__PURE__ */ new Map();
|
|
50016
|
+
for (const entry of ctx.defs.values()) {
|
|
50017
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
50018
|
+
const entrySchema = asRecord3(entry.schema);
|
|
50019
|
+
const ref = entrySchema && Object.keys(entrySchema).length === 1 && typeof entrySchema.$ref === "string" ? entrySchema.$ref : "";
|
|
50020
|
+
if (ref.startsWith("#/$defs/")) aliasTargets.set(entry.name, ref.slice("#/$defs/".length));
|
|
50021
|
+
}
|
|
50022
|
+
for (const start of aliasTargets.keys()) {
|
|
50023
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50024
|
+
let current = start;
|
|
50025
|
+
while (current !== void 0 && aliasTargets.has(current)) {
|
|
50026
|
+
if (seen.has(current)) return unsupported("Self-referential alias schema is unsupported");
|
|
50027
|
+
seen.add(current);
|
|
50028
|
+
current = aliasTargets.get(current);
|
|
50029
|
+
}
|
|
50030
|
+
}
|
|
50031
|
+
if (ctx.defs.size > 0) {
|
|
50032
|
+
const normalizedRecord = asRecord3(normalized);
|
|
50033
|
+
if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
|
|
50034
|
+
normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
|
|
50035
|
+
}
|
|
50036
|
+
const packed = { schema: normalized };
|
|
50037
|
+
if (ctx.notes.size > 0) packed.notes = [...ctx.notes].sort();
|
|
50038
|
+
return packed;
|
|
49847
50039
|
} catch (error2) {
|
|
49848
50040
|
return unsupported(error2 instanceof Error ? error2.message : String(error2));
|
|
49849
50041
|
}
|
|
49850
50042
|
}
|
|
49851
50043
|
|
|
50044
|
+
// src/lib/spec/schema-validator-code.ts
|
|
50045
|
+
var import_schemasafe = __toESM(require_src(), 1);
|
|
50046
|
+
function compileSchemaValidator(schema2) {
|
|
50047
|
+
try {
|
|
50048
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
50049
|
+
includeErrors: false,
|
|
50050
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
50051
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
50052
|
+
// Plaid, and DigitalOcean public specs).
|
|
50053
|
+
allowUnusedKeywords: true,
|
|
50054
|
+
contentValidation: false,
|
|
50055
|
+
formatAssertion: true,
|
|
50056
|
+
isJSON: true,
|
|
50057
|
+
mode: "default",
|
|
50058
|
+
removeAdditional: false,
|
|
50059
|
+
requireSchema: true,
|
|
50060
|
+
requireStringValidation: false,
|
|
50061
|
+
useDefaults: false
|
|
50062
|
+
});
|
|
50063
|
+
return (value) => validate2(value);
|
|
50064
|
+
} catch {
|
|
50065
|
+
return null;
|
|
50066
|
+
}
|
|
50067
|
+
}
|
|
50068
|
+
function compileSchemaValidatorCode(schema2) {
|
|
50069
|
+
try {
|
|
50070
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
50071
|
+
includeErrors: true,
|
|
50072
|
+
allErrors: true,
|
|
50073
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
50074
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
50075
|
+
// Plaid, and DigitalOcean public specs).
|
|
50076
|
+
allowUnusedKeywords: true,
|
|
50077
|
+
contentValidation: false,
|
|
50078
|
+
formatAssertion: true,
|
|
50079
|
+
isJSON: true,
|
|
50080
|
+
mode: "default",
|
|
50081
|
+
removeAdditional: false,
|
|
50082
|
+
requireSchema: true,
|
|
50083
|
+
requireStringValidation: false,
|
|
50084
|
+
useDefaults: false
|
|
50085
|
+
});
|
|
50086
|
+
const source = validate2.toModule();
|
|
50087
|
+
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
50088
|
+
throw new Error("schemasafe generated forbidden dynamic code");
|
|
50089
|
+
}
|
|
50090
|
+
return source;
|
|
50091
|
+
} catch (error2) {
|
|
50092
|
+
throw new Error(
|
|
50093
|
+
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
50094
|
+
{ cause: error2 }
|
|
50095
|
+
);
|
|
50096
|
+
}
|
|
50097
|
+
}
|
|
50098
|
+
|
|
49852
50099
|
// src/lib/spec/contract-index.ts
|
|
49853
50100
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
49854
50101
|
function asRecord4(value) {
|
|
@@ -49924,7 +50171,7 @@ function collectSecurityApiKeys(root, operation) {
|
|
|
49924
50171
|
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
49925
50172
|
for (const schemeName of Object.keys(requirement)) {
|
|
49926
50173
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
49927
|
-
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header"].includes(String(scheme.in))) {
|
|
50174
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header", "cookie"].includes(String(scheme.in))) {
|
|
49928
50175
|
names.add(`${String(scheme.in)}:${scheme.name.toLowerCase()}`);
|
|
49929
50176
|
}
|
|
49930
50177
|
}
|
|
@@ -49946,12 +50193,197 @@ function collectSecuritySchemeWarnings(root, operation) {
|
|
|
49946
50193
|
for (const schemeName of Object.keys(requirement)) {
|
|
49947
50194
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
49948
50195
|
warnings.add(
|
|
49949
|
-
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven by dynamic contract tests`
|
|
50196
|
+
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven beyond credential presence by dynamic contract tests`
|
|
49950
50197
|
);
|
|
49951
50198
|
}
|
|
49952
50199
|
}
|
|
49953
50200
|
return [...warnings];
|
|
49954
50201
|
}
|
|
50202
|
+
function securityCheckFor(schemeName, scheme) {
|
|
50203
|
+
const kind = securitySchemeKind(scheme);
|
|
50204
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["header", "query", "cookie"].includes(String(scheme.in))) {
|
|
50205
|
+
return { scheme: schemeName, kind, checkable: true, in: String(scheme.in), name: scheme.name };
|
|
50206
|
+
}
|
|
50207
|
+
if (scheme?.type === "http") {
|
|
50208
|
+
const httpScheme = String(scheme.scheme || "").toLowerCase();
|
|
50209
|
+
if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
|
|
50210
|
+
if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
|
|
50211
|
+
if (httpScheme) return { scheme: schemeName, kind, checkable: true, prefix: `${httpScheme.charAt(0).toUpperCase()}${httpScheme.slice(1)} ` };
|
|
50212
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
50213
|
+
}
|
|
50214
|
+
if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
|
|
50215
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
50216
|
+
}
|
|
50217
|
+
return { scheme: schemeName, kind, checkable: false };
|
|
50218
|
+
}
|
|
50219
|
+
function collectSecurityRuntimeChecks(root, operation) {
|
|
50220
|
+
const securitySchemes = asRecord4(asRecord4(root.components)?.securitySchemes);
|
|
50221
|
+
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
50222
|
+
const alternatives = [];
|
|
50223
|
+
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
50224
|
+
const schemeNames = Object.keys(requirement);
|
|
50225
|
+
if (schemeNames.length === 0) return void 0;
|
|
50226
|
+
alternatives.push(schemeNames.map((schemeName) => securityCheckFor(schemeName, resolveInternalRef(root, securitySchemes?.[schemeName]))));
|
|
50227
|
+
}
|
|
50228
|
+
if (alternatives.some((alternative) => alternative.length > 0 && alternative.every((check) => !check.checkable))) return void 0;
|
|
50229
|
+
return alternatives.length > 0 ? alternatives : void 0;
|
|
50230
|
+
}
|
|
50231
|
+
function resolvedParameters(root, pathItem, operation) {
|
|
50232
|
+
return [...asArray2(pathItem.parameters), ...asArray2(operation.parameters)].map((rawParam) => {
|
|
50233
|
+
try {
|
|
50234
|
+
return resolveInternalRef(root, rawParam);
|
|
50235
|
+
} catch {
|
|
50236
|
+
return null;
|
|
50237
|
+
}
|
|
50238
|
+
}).filter((param) => Boolean(param));
|
|
50239
|
+
}
|
|
50240
|
+
var DEFAULT_PARAM_STYLES = { query: "form", path: "simple", header: "simple", cookie: "form" };
|
|
50241
|
+
var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
|
|
50242
|
+
function isIgnoredParameter(location2, name) {
|
|
50243
|
+
return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
|
|
50244
|
+
}
|
|
50245
|
+
function jsonContentParameterMedia(param) {
|
|
50246
|
+
const content = asRecord4(param.content);
|
|
50247
|
+
if (!content) return void 0;
|
|
50248
|
+
const entries = Object.entries(content);
|
|
50249
|
+
if (entries.length !== 1) return void 0;
|
|
50250
|
+
const [contentType, mediaObject] = entries[0];
|
|
50251
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50252
|
+
if (!isJsonBaseType(base)) return void 0;
|
|
50253
|
+
const schema2 = asRecord4(mediaObject)?.schema;
|
|
50254
|
+
return schema2 === void 0 ? void 0 : schema2;
|
|
50255
|
+
}
|
|
50256
|
+
function collectSerializationWarnings(root, pathItem, operation, decodedKeys) {
|
|
50257
|
+
const warnings = [];
|
|
50258
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
50259
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
50260
|
+
const name = String(param.name || "");
|
|
50261
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
50262
|
+
if (!name || !defaultStyle || isIgnoredParameter(location2, name)) continue;
|
|
50263
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
50264
|
+
const defaultExplode = style === "form";
|
|
50265
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
50266
|
+
const unvalidatedContent = param.content !== void 0 && (jsonContentParameterMedia(param) === void 0 || location2 !== "query" && location2 !== "header");
|
|
50267
|
+
if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || unvalidatedContent) {
|
|
50268
|
+
if (decodedKeys.has(`${location2}:${name.toLowerCase()}`) && param.allowReserved !== true && param.content === void 0) continue;
|
|
50269
|
+
warnings.push(`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: parameter ${location2}:${name} declares non-default style, explode, allowReserved, or content and its serialization is not validated`);
|
|
50270
|
+
}
|
|
50271
|
+
}
|
|
50272
|
+
return warnings;
|
|
50273
|
+
}
|
|
50274
|
+
var SCALAR_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
50275
|
+
function packedScalarSchema(packed) {
|
|
50276
|
+
if (packed.unsupported || packed.schema === void 0) return void 0;
|
|
50277
|
+
const record = asRecord4(packed.schema);
|
|
50278
|
+
if (!record) return void 0;
|
|
50279
|
+
const types2 = Array.isArray(record.type) ? record.type : [record.type];
|
|
50280
|
+
if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
|
|
50281
|
+
return packed.schema;
|
|
50282
|
+
}
|
|
50283
|
+
function packedArrayItemsSchema(packed) {
|
|
50284
|
+
if (packed.unsupported || packed.schema === void 0) return void 0;
|
|
50285
|
+
const record = asRecord4(packed.schema);
|
|
50286
|
+
if (!record) return void 0;
|
|
50287
|
+
const types2 = Array.isArray(record.type) ? record.type : [record.type];
|
|
50288
|
+
if (types2.length !== 1 || types2[0] !== "array") return void 0;
|
|
50289
|
+
if (record.prefixItems !== void 0 || Array.isArray(record.items)) return void 0;
|
|
50290
|
+
if (record.items === void 0) return {};
|
|
50291
|
+
const items = asRecord4(record.items);
|
|
50292
|
+
if (!items || typeof items.$ref === "string") return void 0;
|
|
50293
|
+
const itemTypes = Array.isArray(items.type) ? items.type : [items.type];
|
|
50294
|
+
if (!itemTypes.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
|
|
50295
|
+
return items;
|
|
50296
|
+
}
|
|
50297
|
+
var QUERY_ARRAY_DECODES = {
|
|
50298
|
+
"form:true": "multi",
|
|
50299
|
+
"form:false": "csv",
|
|
50300
|
+
"spaceDelimited:false": "ssv",
|
|
50301
|
+
"pipeDelimited:false": "pipes"
|
|
50302
|
+
};
|
|
50303
|
+
function collectParameterChecks(root, pathItem, operation, version, operationId, pathTemplate, warnings) {
|
|
50304
|
+
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
50305
|
+
const checks = [];
|
|
50306
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50307
|
+
const orderedParams = [...asArray2(operation.parameters), ...asArray2(pathItem.parameters)].map((rawParam) => {
|
|
50308
|
+
try {
|
|
50309
|
+
return resolveInternalRef(root, rawParam);
|
|
50310
|
+
} catch {
|
|
50311
|
+
return null;
|
|
50312
|
+
}
|
|
50313
|
+
}).filter((param) => Boolean(param));
|
|
50314
|
+
for (const param of orderedParams) {
|
|
50315
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
50316
|
+
if (location2 !== "query" && location2 !== "header" && location2 !== "path" && location2 !== "cookie") continue;
|
|
50317
|
+
const name = String(param.name || "");
|
|
50318
|
+
if (!name || isIgnoredParameter(location2, name)) continue;
|
|
50319
|
+
const key = `${location2}:${name.toLowerCase()}`;
|
|
50320
|
+
if (seen.has(key)) continue;
|
|
50321
|
+
seen.add(key);
|
|
50322
|
+
if (securityKeys.has(key)) continue;
|
|
50323
|
+
const contentMedia = jsonContentParameterMedia(param);
|
|
50324
|
+
if (contentMedia !== void 0 && (location2 === "query" || location2 === "header")) {
|
|
50325
|
+
const packed2 = packSchema(root, contentMedia, version, "request");
|
|
50326
|
+
warnings.push(...packNoteWarnings(packed2, `parameter ${location2}:${name} of ${operationId}`));
|
|
50327
|
+
if (packed2.unsupported) {
|
|
50328
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed2.unsupported})`);
|
|
50329
|
+
} else if (packed2.schema !== void 0) {
|
|
50330
|
+
const check2 = { in: location2, name, required: param.required === true, content: true, schema: packed2.schema };
|
|
50331
|
+
if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
|
|
50332
|
+
checks.push(check2);
|
|
50333
|
+
}
|
|
50334
|
+
continue;
|
|
50335
|
+
}
|
|
50336
|
+
if (param.content !== void 0 || param.schema === void 0) continue;
|
|
50337
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
50338
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
50339
|
+
const defaultExplode = style === "form";
|
|
50340
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
50341
|
+
const defaultSerialization = style === defaultStyle && explode === defaultExplode;
|
|
50342
|
+
const packed = packSchema(root, param.schema, version);
|
|
50343
|
+
const noteWarnings = packNoteWarnings(packed, `parameter ${location2}:${name} of ${operationId}`);
|
|
50344
|
+
if (defaultSerialization) warnings.push(...noteWarnings);
|
|
50345
|
+
if (packed.unsupported) {
|
|
50346
|
+
if (defaultSerialization) warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
|
|
50347
|
+
continue;
|
|
50348
|
+
}
|
|
50349
|
+
if (location2 === "path" && !pathTemplate.split("/").includes(`{${name}}`)) continue;
|
|
50350
|
+
const scalarSchema = packedScalarSchema(packed);
|
|
50351
|
+
if (scalarSchema !== void 0) {
|
|
50352
|
+
if (!defaultSerialization) continue;
|
|
50353
|
+
const check2 = { in: location2, name, required: param.required === true, schema: scalarSchema };
|
|
50354
|
+
if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
|
|
50355
|
+
checks.push(check2);
|
|
50356
|
+
continue;
|
|
50357
|
+
}
|
|
50358
|
+
if (location2 !== "query" && location2 !== "header") continue;
|
|
50359
|
+
const items = packedArrayItemsSchema(packed);
|
|
50360
|
+
if (items === void 0) continue;
|
|
50361
|
+
const decode = location2 === "query" ? QUERY_ARRAY_DECODES[`${style}:${explode}`] : style === "simple" && !explode ? "csv" : void 0;
|
|
50362
|
+
if (!decode) continue;
|
|
50363
|
+
if (!defaultSerialization) warnings.push(...noteWarnings);
|
|
50364
|
+
const check = { in: location2, name, required: param.required === true, schema: packed.schema, decode, items };
|
|
50365
|
+
if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
|
|
50366
|
+
checks.push(check);
|
|
50367
|
+
}
|
|
50368
|
+
return checks.length > 0 ? checks : void 0;
|
|
50369
|
+
}
|
|
50370
|
+
function packNoteWarnings(packed, context) {
|
|
50371
|
+
return (packed.notes ?? []).map(
|
|
50372
|
+
(note) => note === "discriminator" ? `CONTRACT_DISCRIMINATOR_NOT_VALIDATED: discriminator on ${context} has no sibling oneOf/anyOf of internal $ref members and is not validated` : `CONTRACT_SCHEMA_NOT_COMPILED: ${note} on ${context} is not validated`
|
|
50373
|
+
);
|
|
50374
|
+
}
|
|
50375
|
+
function collectDeclaredQueryParameters(root, pathItem, operation) {
|
|
50376
|
+
const names = /* @__PURE__ */ new Set();
|
|
50377
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
50378
|
+
if (String(param.in || "").toLowerCase() !== "query") continue;
|
|
50379
|
+
const name = String(param.name || "");
|
|
50380
|
+
if (name) names.add(name.toLowerCase());
|
|
50381
|
+
}
|
|
50382
|
+
for (const key of collectSecurityApiKeys(root, operation)) {
|
|
50383
|
+
if (key.startsWith("query:")) names.add(key.slice("query:".length));
|
|
50384
|
+
}
|
|
50385
|
+
return [...names];
|
|
50386
|
+
}
|
|
49955
50387
|
function collectParameters(root, pathItem, operation) {
|
|
49956
50388
|
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
49957
50389
|
const requirements = [];
|
|
@@ -49961,9 +50393,9 @@ function collectParameters(root, pathItem, operation) {
|
|
|
49961
50393
|
const param = resolveInternalRef(root, rawParam);
|
|
49962
50394
|
if (!param) continue;
|
|
49963
50395
|
const location2 = String(param.in || "").toLowerCase();
|
|
49964
|
-
if (!["path", "query", "header"].includes(location2)) continue;
|
|
50396
|
+
if (!["path", "query", "header", "cookie"].includes(location2)) continue;
|
|
49965
50397
|
const name = String(param.name || "");
|
|
49966
|
-
if (!name || param.required !== true) continue;
|
|
50398
|
+
if (!name || param.required !== true || isIgnoredParameter(location2, name)) continue;
|
|
49967
50399
|
const key = `${location2}:${name.toLowerCase()}`;
|
|
49968
50400
|
if (seen.has(key)) continue;
|
|
49969
50401
|
seen.add(key);
|
|
@@ -49975,36 +50407,263 @@ function collectParameters(root, pathItem, operation) {
|
|
|
49975
50407
|
}
|
|
49976
50408
|
return requirements;
|
|
49977
50409
|
}
|
|
49978
|
-
|
|
50410
|
+
var BODY_FIELD_RULE_TYPES = /* @__PURE__ */ new Set(["application/x-www-form-urlencoded", "multipart/form-data"]);
|
|
50411
|
+
function isJsonBaseType(base) {
|
|
50412
|
+
return base === "application/json" || /\+json$/.test(base);
|
|
50413
|
+
}
|
|
50414
|
+
function mergeObjectSchema(root, rawSchema, depth) {
|
|
50415
|
+
if (depth > 10) return null;
|
|
50416
|
+
let schema2;
|
|
50417
|
+
try {
|
|
50418
|
+
schema2 = resolveInternalRef(root, rawSchema);
|
|
50419
|
+
} catch {
|
|
50420
|
+
return null;
|
|
50421
|
+
}
|
|
50422
|
+
if (!schema2) return null;
|
|
50423
|
+
const merged = {
|
|
50424
|
+
required: asArray2(schema2.required).map((entry) => String(entry)).filter(Boolean),
|
|
50425
|
+
properties: { ...asRecord4(schema2.properties) ?? {} }
|
|
50426
|
+
};
|
|
50427
|
+
for (const member of asArray2(schema2.allOf)) {
|
|
50428
|
+
const child2 = mergeObjectSchema(root, member, depth + 1);
|
|
50429
|
+
if (!child2) continue;
|
|
50430
|
+
merged.required.push(...child2.required);
|
|
50431
|
+
merged.properties = { ...merged.properties, ...child2.properties };
|
|
50432
|
+
}
|
|
50433
|
+
merged.required = [...new Set(merged.required)];
|
|
50434
|
+
return merged;
|
|
50435
|
+
}
|
|
50436
|
+
function propertyIsReadOnly(root, properties, name) {
|
|
50437
|
+
try {
|
|
50438
|
+
return resolveInternalRef(root, properties[name])?.readOnly === true;
|
|
50439
|
+
} catch {
|
|
50440
|
+
return false;
|
|
50441
|
+
}
|
|
50442
|
+
}
|
|
50443
|
+
function propertyIsBinary(root, properties, name) {
|
|
50444
|
+
let schema2;
|
|
50445
|
+
try {
|
|
50446
|
+
schema2 = resolveInternalRef(root, properties[name]);
|
|
50447
|
+
} catch {
|
|
50448
|
+
return false;
|
|
50449
|
+
}
|
|
50450
|
+
if (!schema2) return false;
|
|
50451
|
+
if (schema2.format === "binary") return true;
|
|
50452
|
+
if (typeof schema2.contentMediaType !== "string" || typeof schema2.contentEncoding === "string") return false;
|
|
50453
|
+
const media = schema2.contentMediaType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50454
|
+
if (!media) return false;
|
|
50455
|
+
return media !== "application/json" && !media.endsWith("+json") && !media.startsWith("text/");
|
|
50456
|
+
}
|
|
50457
|
+
function fieldEncodings(root, base, mediaObject, properties) {
|
|
50458
|
+
const declared = asRecord4(mediaObject?.encoding);
|
|
50459
|
+
const encodings = {};
|
|
50460
|
+
for (const name of Object.keys(properties)) {
|
|
50461
|
+
if (base === "multipart/form-data" && propertyIsBinary(root, properties, name)) {
|
|
50462
|
+
encodings[name] = { ...encodings[name], binary: true };
|
|
50463
|
+
}
|
|
50464
|
+
}
|
|
50465
|
+
if (declared) {
|
|
50466
|
+
for (const [name, rawEncoding] of Object.entries(declared)) {
|
|
50467
|
+
const encoding = asRecord4(rawEncoding);
|
|
50468
|
+
if (!encoding) continue;
|
|
50469
|
+
const entry = { ...encodings[name] };
|
|
50470
|
+
if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
|
|
50471
|
+
entry.contentType = encoding.contentType.toLowerCase();
|
|
50472
|
+
}
|
|
50473
|
+
if (base === "multipart/form-data" && asRecord4(encoding.headers)) {
|
|
50474
|
+
entry.hasHeaders = true;
|
|
50475
|
+
}
|
|
50476
|
+
if (base === "application/x-www-form-urlencoded") {
|
|
50477
|
+
const style = typeof encoding.style === "string" ? encoding.style : "form";
|
|
50478
|
+
const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
|
|
50479
|
+
if (style !== "form" || explode !== (style === "form") || encoding.allowReserved === true) {
|
|
50480
|
+
entry.nonDefaultSerialization = true;
|
|
50481
|
+
}
|
|
50482
|
+
}
|
|
50483
|
+
if (Object.keys(entry).length > 0) encodings[name] = entry;
|
|
50484
|
+
}
|
|
50485
|
+
}
|
|
50486
|
+
return Object.keys(encodings).length > 0 ? encodings : void 0;
|
|
50487
|
+
}
|
|
50488
|
+
function formFieldSchemas(root, version, properties, context, warnings) {
|
|
50489
|
+
const schemas = {};
|
|
50490
|
+
for (const name of Object.keys(properties)) {
|
|
50491
|
+
const packed = packSchema(root, properties[name], version, "request");
|
|
50492
|
+
warnings.push(...packNoteWarnings(packed, `field ${name} of ${context}`));
|
|
50493
|
+
if (packed.unsupported) {
|
|
50494
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: field ${name} of ${context} skipped (${packed.unsupported})`);
|
|
50495
|
+
continue;
|
|
50496
|
+
}
|
|
50497
|
+
if (packed.schema === void 0) continue;
|
|
50498
|
+
const schema2 = packedScalarSchema(packed);
|
|
50499
|
+
if (schema2 !== void 0) schemas[name] = schema2;
|
|
50500
|
+
}
|
|
50501
|
+
return Object.keys(schemas).length > 0 ? schemas : void 0;
|
|
50502
|
+
}
|
|
50503
|
+
function requestBodyFieldRules(root, content, version, operationId, warnings) {
|
|
50504
|
+
const rules = {};
|
|
50505
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
50506
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50507
|
+
if (!isJsonBaseType(base) && !BODY_FIELD_RULE_TYPES.has(base)) continue;
|
|
50508
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
50509
|
+
const merged = mergeObjectSchema(root, mediaRecord?.schema, 0);
|
|
50510
|
+
if (!merged) continue;
|
|
50511
|
+
const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
|
|
50512
|
+
const required = merged.required.filter((name) => !propertyIsReadOnly(root, merged.properties, name));
|
|
50513
|
+
const formRules = BODY_FIELD_RULE_TYPES.has(base);
|
|
50514
|
+
const encodings = formRules ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
|
|
50515
|
+
const fieldSchemas = formRules ? formFieldSchemas(root, version, merged.properties, `request body ${contentType} of ${operationId}`, warnings) : void 0;
|
|
50516
|
+
if (required.length > 0 || readOnly.length > 0 || encodings || fieldSchemas) {
|
|
50517
|
+
const rule = { required, readOnly };
|
|
50518
|
+
if (encodings) rule.encodings = encodings;
|
|
50519
|
+
if (fieldSchemas) rule.fieldSchemas = fieldSchemas;
|
|
50520
|
+
rules[base] = rule;
|
|
50521
|
+
}
|
|
50522
|
+
}
|
|
50523
|
+
return Object.keys(rules).length > 0 ? rules : void 0;
|
|
50524
|
+
}
|
|
50525
|
+
function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
|
|
50526
|
+
const schemas = {};
|
|
50527
|
+
const exampleWarnings = /* @__PURE__ */ new Set();
|
|
50528
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
50529
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50530
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
50531
|
+
const schema2 = mediaRecord?.schema;
|
|
50532
|
+
if (!isJsonBaseType(base)) {
|
|
50533
|
+
if (schema2 !== void 0 && !BODY_FIELD_RULE_TYPES.has(base)) {
|
|
50534
|
+
warnings.push(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated at runtime`);
|
|
50535
|
+
}
|
|
50536
|
+
continue;
|
|
50537
|
+
}
|
|
50538
|
+
if (schema2 === void 0) continue;
|
|
50539
|
+
const packed = packSchema(root, schema2, version, "request");
|
|
50540
|
+
warnings.push(...packNoteWarnings(packed, `request body ${contentType} of ${operationId}`));
|
|
50541
|
+
if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
|
|
50542
|
+
if (packed.unsupported) {
|
|
50543
|
+
warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
|
|
50544
|
+
continue;
|
|
50545
|
+
}
|
|
50546
|
+
if (packed.schema !== void 0) schemas[base] = packed.schema;
|
|
50547
|
+
}
|
|
50548
|
+
warnings.push(...exampleWarnings);
|
|
50549
|
+
return Object.keys(schemas).length > 0 ? schemas : void 0;
|
|
50550
|
+
}
|
|
50551
|
+
function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
49979
50552
|
const body = resolveInternalRef(root, operation.requestBody);
|
|
49980
|
-
if (!body
|
|
50553
|
+
if (!body) return void 0;
|
|
49981
50554
|
const content = asRecord4(body.content);
|
|
50555
|
+
const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
|
|
50556
|
+
if (fieldRules) {
|
|
50557
|
+
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
50558
|
+
for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
|
|
50559
|
+
if (encoding.nonDefaultSerialization) {
|
|
50560
|
+
warnings.push(
|
|
50561
|
+
`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: ${base} request body field ${field} on ${operationId} declares non-default encoding style, explode, or allowReserved and its serialization is not validated`
|
|
50562
|
+
);
|
|
50563
|
+
}
|
|
50564
|
+
if (encoding.hasHeaders) {
|
|
50565
|
+
warnings.push(
|
|
50566
|
+
`CONTRACT_ENCODING_HEADERS_NOT_VALIDATED: ${base} request body field ${field} on ${operationId} declares per-part headers that generated formdata entries cannot carry`
|
|
50567
|
+
);
|
|
50568
|
+
}
|
|
50569
|
+
}
|
|
50570
|
+
}
|
|
50571
|
+
}
|
|
49982
50572
|
return {
|
|
49983
|
-
required: true,
|
|
49984
|
-
contentTypes: content ? Object.keys(content) : []
|
|
50573
|
+
required: body.required === true,
|
|
50574
|
+
contentTypes: content ? Object.keys(content) : [],
|
|
50575
|
+
fieldRules,
|
|
50576
|
+
jsonSchemas: content ? requestBodyJsonSchemas(root, content, version, operationId, warnings) : void 0
|
|
49985
50577
|
};
|
|
49986
50578
|
}
|
|
49987
|
-
function
|
|
50579
|
+
function exampleCandidates(root, mediaObject) {
|
|
50580
|
+
const candidates = [];
|
|
50581
|
+
if ("example" in mediaObject) candidates.push({ label: "example", value: mediaObject.example });
|
|
50582
|
+
const examples = asRecord4(mediaObject.examples);
|
|
50583
|
+
if (examples) {
|
|
50584
|
+
for (const [name, rawExample] of Object.entries(examples)) {
|
|
50585
|
+
let example;
|
|
50586
|
+
try {
|
|
50587
|
+
example = resolveInternalRef(root, rawExample);
|
|
50588
|
+
} catch {
|
|
50589
|
+
continue;
|
|
50590
|
+
}
|
|
50591
|
+
if (example && "value" in example) candidates.push({ label: `examples.${name}`, value: example.value });
|
|
50592
|
+
}
|
|
50593
|
+
}
|
|
50594
|
+
return candidates;
|
|
50595
|
+
}
|
|
50596
|
+
function validateExamples(root, mediaObject, packed, contentType, context, warnings) {
|
|
50597
|
+
if (packed.schema === void 0 || packed.unsupported) return;
|
|
50598
|
+
const candidates = exampleCandidates(root, mediaObject);
|
|
50599
|
+
if (candidates.length === 0) return;
|
|
50600
|
+
const validate2 = compileSchemaValidator(packed.schema);
|
|
50601
|
+
if (!validate2) return;
|
|
50602
|
+
for (const candidate of candidates) {
|
|
50603
|
+
if (!validate2(candidate.value)) {
|
|
50604
|
+
warnings.add(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for ${contentType} on ${context} does not match its schema`);
|
|
50605
|
+
}
|
|
50606
|
+
}
|
|
50607
|
+
}
|
|
50608
|
+
function responseContent(root, version, response, context, warnings) {
|
|
49988
50609
|
const content = asRecord4(response.content);
|
|
49989
50610
|
if (!content) return {};
|
|
49990
50611
|
const media = {};
|
|
49991
50612
|
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
49992
|
-
const
|
|
49993
|
-
|
|
50613
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
50614
|
+
const schema2 = mediaRecord?.schema;
|
|
50615
|
+
let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
|
|
50616
|
+
for (const warning2 of packNoteWarnings(packed, `response ${contentType} of ${context}`)) warnings.add(warning2);
|
|
50617
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
50618
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
|
|
50619
|
+
packed = {};
|
|
50620
|
+
}
|
|
50621
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50622
|
+
if (mediaRecord && isJsonBaseType(base)) validateExamples(root, mediaRecord, packed, contentType, context, warnings);
|
|
50623
|
+
media[contentType] = packed;
|
|
49994
50624
|
}
|
|
49995
50625
|
return media;
|
|
49996
50626
|
}
|
|
49997
|
-
function responseHeaders(root, version, response) {
|
|
50627
|
+
function responseHeaders(root, version, response, context, warnings) {
|
|
49998
50628
|
const headers = asRecord4(response.headers);
|
|
49999
50629
|
if (!headers) return [];
|
|
50000
|
-
|
|
50630
|
+
const entries = [];
|
|
50631
|
+
for (const [name, rawHeader] of Object.entries(headers)) {
|
|
50632
|
+
if (name.toLowerCase() === "content-type") continue;
|
|
50001
50633
|
const header = resolveInternalRef(root, rawHeader);
|
|
50002
|
-
if (!header)
|
|
50634
|
+
if (!header) {
|
|
50635
|
+
entries.push({ name, required: true, unsupported: "Unresolved response header" });
|
|
50636
|
+
continue;
|
|
50637
|
+
}
|
|
50003
50638
|
const required = header.required === true;
|
|
50004
|
-
if (header.content)
|
|
50005
|
-
|
|
50006
|
-
|
|
50007
|
-
|
|
50639
|
+
if (header.content) {
|
|
50640
|
+
entries.push({ name, required, unsupported: "OpenAPI response header content is unsupported" });
|
|
50641
|
+
continue;
|
|
50642
|
+
}
|
|
50643
|
+
if (!header.schema) {
|
|
50644
|
+
entries.push({ name, required });
|
|
50645
|
+
continue;
|
|
50646
|
+
}
|
|
50647
|
+
const packed = packSchema(root, header.schema, version);
|
|
50648
|
+
for (const warning2 of packNoteWarnings(packed, `response header ${name} of ${context}`)) warnings.add(warning2);
|
|
50649
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
50650
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
|
|
50651
|
+
entries.push({ name, required });
|
|
50652
|
+
continue;
|
|
50653
|
+
}
|
|
50654
|
+
if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
|
|
50655
|
+
const items = packedArrayItemsSchema(packed);
|
|
50656
|
+
if (items !== void 0) {
|
|
50657
|
+
entries.push({ name, required, schema: packed.schema, items });
|
|
50658
|
+
continue;
|
|
50659
|
+
}
|
|
50660
|
+
warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
|
|
50661
|
+
entries.push({ name, required });
|
|
50662
|
+
continue;
|
|
50663
|
+
}
|
|
50664
|
+
entries.push({ name, required, ...packed });
|
|
50665
|
+
}
|
|
50666
|
+
return entries;
|
|
50008
50667
|
}
|
|
50009
50668
|
function buildContractIndex(root) {
|
|
50010
50669
|
if (root.swagger === "2.0") throw new Error("CONTRACT_UNSUPPORTED_OPENAPI_VERSION: Dynamic contract tests require OpenAPI 3.0 or 3.1 (found swagger 2.0)");
|
|
@@ -50029,11 +50688,23 @@ function buildContractIndex(root) {
|
|
|
50029
50688
|
throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path8} must define at least one response`);
|
|
50030
50689
|
}
|
|
50031
50690
|
const contractResponses = {};
|
|
50691
|
+
const responseWarnings = /* @__PURE__ */ new Set();
|
|
50032
50692
|
for (const [status, rawResponse] of Object.entries(responses)) {
|
|
50033
50693
|
const response = resolveInternalRef(root, rawResponse);
|
|
50034
50694
|
if (!response) continue;
|
|
50035
|
-
|
|
50036
|
-
|
|
50695
|
+
if (asRecord4(response.links)) {
|
|
50696
|
+
responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path8}`);
|
|
50697
|
+
}
|
|
50698
|
+
const responseContext = `${lowerMethod.toUpperCase()} ${path8} status ${status}`;
|
|
50699
|
+
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
50700
|
+
for (const [contentType, media] of Object.entries(content)) {
|
|
50701
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50702
|
+
const schemaType = asRecord4(media.schema)?.type;
|
|
50703
|
+
if (!isJsonBaseType(base) && media.schema !== void 0 && !media.unsupported && schemaType !== "string") {
|
|
50704
|
+
responseWarnings.add(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: response schema for ${contentType} on ${responseContext} is not validated at runtime`);
|
|
50705
|
+
}
|
|
50706
|
+
}
|
|
50707
|
+
const headers = responseHeaders(root, version, response, responseContext, responseWarnings);
|
|
50037
50708
|
contractResponses[normalizeResponseKey(status)] = {
|
|
50038
50709
|
content,
|
|
50039
50710
|
hasBody: Object.keys(content).length > 0,
|
|
@@ -50044,21 +50715,45 @@ function buildContractIndex(root) {
|
|
|
50044
50715
|
path8,
|
|
50045
50716
|
...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path8))
|
|
50046
50717
|
].map(normalizePath))];
|
|
50718
|
+
const operationId = `${lowerMethod.toUpperCase()} ${path8}`;
|
|
50047
50719
|
const opWarnings = [];
|
|
50720
|
+
opWarnings.push(...responseWarnings);
|
|
50048
50721
|
opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
|
|
50722
|
+
const parameterChecks = collectParameterChecks(root, pathItem, operation, version, operationId, path8, opWarnings);
|
|
50723
|
+
const checkedKeys = new Set((parameterChecks ?? []).map((check) => `${check.in}:${check.name.toLowerCase()}`));
|
|
50724
|
+
const decodedKeys = new Set((parameterChecks ?? []).filter((check) => check.decode).map((check) => `${check.in}:${check.name.toLowerCase()}`));
|
|
50725
|
+
opWarnings.push(...collectSerializationWarnings(root, pathItem, operation, decodedKeys));
|
|
50726
|
+
if (operation.deprecated === true) {
|
|
50727
|
+
opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path8} is marked deprecated in the OpenAPI document`);
|
|
50728
|
+
}
|
|
50049
50729
|
const requiredParameters = collectParameters(root, pathItem, operation);
|
|
50050
50730
|
for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
|
|
50051
50731
|
opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
|
|
50052
50732
|
}
|
|
50733
|
+
for (const parameter of requiredParameters.filter((entry) => entry.in === "cookie" && !entry.securityDerived)) {
|
|
50734
|
+
opWarnings.push(checkedKeys.has(`cookie:${parameter.name.toLowerCase()}`) ? `CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not included in generated requests; the runtime test fails until the cookie is supplied at send time` : `CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not included in generated requests and its value is not runtime-validated`);
|
|
50735
|
+
}
|
|
50736
|
+
const pathParamWarnings = /* @__PURE__ */ new Set();
|
|
50737
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
50738
|
+
if (String(param.in || "").toLowerCase() !== "path") continue;
|
|
50739
|
+
const name = String(param.name || "");
|
|
50740
|
+
if (name && !checkedKeys.has(`path:${name.toLowerCase()}`)) {
|
|
50741
|
+
pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
|
|
50742
|
+
}
|
|
50743
|
+
}
|
|
50744
|
+
opWarnings.push(...pathParamWarnings);
|
|
50053
50745
|
operations.push({
|
|
50054
|
-
id:
|
|
50746
|
+
id: operationId,
|
|
50055
50747
|
method: lowerMethod.toUpperCase(),
|
|
50056
50748
|
path: path8,
|
|
50057
50749
|
pointer: `/paths/${path8.replace(/~/g, "~0").replace(/\//g, "~1")}/${lowerMethod}`,
|
|
50058
50750
|
candidates,
|
|
50059
50751
|
responses: contractResponses,
|
|
50060
50752
|
requiredParameters,
|
|
50061
|
-
|
|
50753
|
+
declaredQueryParameters: collectDeclaredQueryParameters(root, pathItem, operation),
|
|
50754
|
+
parameterChecks,
|
|
50755
|
+
requestBody: collectRequestBody(root, operation, version, operationId, opWarnings),
|
|
50756
|
+
security: collectSecurityRuntimeChecks(root, operation),
|
|
50062
50757
|
warnings: opWarnings
|
|
50063
50758
|
});
|
|
50064
50759
|
}
|
|
@@ -50079,35 +50774,6 @@ function buildContractIndex(root) {
|
|
|
50079
50774
|
return { operations, version, warnings };
|
|
50080
50775
|
}
|
|
50081
50776
|
|
|
50082
|
-
// src/lib/spec/schema-validator-code.ts
|
|
50083
|
-
var import_schemasafe = __toESM(require_src(), 1);
|
|
50084
|
-
function compileSchemaValidatorCode(schema2) {
|
|
50085
|
-
try {
|
|
50086
|
-
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
50087
|
-
includeErrors: true,
|
|
50088
|
-
allErrors: true,
|
|
50089
|
-
contentValidation: false,
|
|
50090
|
-
formatAssertion: true,
|
|
50091
|
-
isJSON: true,
|
|
50092
|
-
mode: "default",
|
|
50093
|
-
removeAdditional: false,
|
|
50094
|
-
requireSchema: true,
|
|
50095
|
-
requireStringValidation: false,
|
|
50096
|
-
useDefaults: false
|
|
50097
|
-
});
|
|
50098
|
-
const source = validate2.toModule();
|
|
50099
|
-
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
50100
|
-
throw new Error("schemasafe generated forbidden dynamic code");
|
|
50101
|
-
}
|
|
50102
|
-
return source;
|
|
50103
|
-
} catch (error2) {
|
|
50104
|
-
throw new Error(
|
|
50105
|
-
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
50106
|
-
{ cause: error2 }
|
|
50107
|
-
);
|
|
50108
|
-
}
|
|
50109
|
-
}
|
|
50110
|
-
|
|
50111
50777
|
// src/lib/spec/collection-contracts.ts
|
|
50112
50778
|
var CONTRACT_SIZE_LIMITS = {
|
|
50113
50779
|
warnTestScriptBytes: 256e3,
|
|
@@ -50196,29 +50862,51 @@ function matchOperation(index, request) {
|
|
|
50196
50862
|
function assignValidator(lines, target, source) {
|
|
50197
50863
|
lines.push(`${target} = ${source};`);
|
|
50198
50864
|
}
|
|
50199
|
-
function
|
|
50865
|
+
function tryCompile(target, schema2, lines, warnings, context) {
|
|
50866
|
+
try {
|
|
50867
|
+
assignValidator(lines, target, compileSchemaValidatorCode(schema2));
|
|
50868
|
+
} catch (error2) {
|
|
50869
|
+
lines.push(`${target} = { skip: true };`);
|
|
50870
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: ${context} could not be compiled into a runtime validator (${error2 instanceof Error ? error2.message.slice(0, 160) : String(error2)})`);
|
|
50871
|
+
}
|
|
50872
|
+
}
|
|
50873
|
+
function buildValidatorAssignments(operation, warnings) {
|
|
50200
50874
|
const lines = ["var validators = {};"];
|
|
50875
|
+
const parameterChecks = operation.parameterChecks ?? [];
|
|
50876
|
+
if (parameterChecks.length > 0) {
|
|
50877
|
+
lines.push("var paramValidators = {};");
|
|
50878
|
+
for (const check of parameterChecks) {
|
|
50879
|
+
tryCompile(`paramValidators[${JSON.stringify(`${check.in}:${check.name.toLowerCase()}`)}]`, check.schema, lines, warnings, `parameter ${check.in}:${check.name} schema on ${operation.id}`);
|
|
50880
|
+
}
|
|
50881
|
+
}
|
|
50882
|
+
const bodySchemas = Object.entries(operation.requestBody?.jsonSchemas ?? {});
|
|
50883
|
+
if (bodySchemas.length > 0) {
|
|
50884
|
+
lines.push("var requestBodyValidators = {};");
|
|
50885
|
+
for (const [base, schema2] of bodySchemas) {
|
|
50886
|
+
tryCompile(`requestBodyValidators[${JSON.stringify(base)}]`, schema2, lines, warnings, `request body schema for ${base} on ${operation.id}`);
|
|
50887
|
+
}
|
|
50888
|
+
}
|
|
50201
50889
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
50202
50890
|
lines.push(`validators[${JSON.stringify(status)}] = validators[${JSON.stringify(status)}] || {};`);
|
|
50203
50891
|
for (const [mediaType, media] of Object.entries(response.content)) {
|
|
50204
50892
|
if (media.schema !== void 0 && !media.unsupported) {
|
|
50205
|
-
|
|
50893
|
+
tryCompile(`validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, media.schema, lines, warnings, `response schema for ${mediaType} on ${operation.id} status ${status}`);
|
|
50206
50894
|
}
|
|
50207
50895
|
}
|
|
50208
50896
|
for (const header of response.headers) {
|
|
50209
50897
|
if (header.schema !== void 0 && !header.unsupported) {
|
|
50210
50898
|
lines.push(`validators[${JSON.stringify(status)}].__headers = validators[${JSON.stringify(status)}].__headers || {};`);
|
|
50211
|
-
|
|
50899
|
+
tryCompile(`validators[${JSON.stringify(status)}].__headers[${JSON.stringify(header.name.toLowerCase())}]`, header.schema, lines, warnings, `response header ${header.name} schema on ${operation.id} status ${status}`);
|
|
50212
50900
|
}
|
|
50213
50901
|
}
|
|
50214
50902
|
}
|
|
50215
50903
|
return lines;
|
|
50216
50904
|
}
|
|
50217
|
-
function createContractScript(operation) {
|
|
50218
|
-
const contract = { method: operation.method, path: operation.path, responses: operation.responses };
|
|
50905
|
+
function createContractScript(operation, warnings = []) {
|
|
50906
|
+
const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks };
|
|
50219
50907
|
return [
|
|
50220
50908
|
`var contract = JSON.parse(${JSON.stringify(JSON.stringify(contract))});`,
|
|
50221
|
-
...buildValidatorAssignments(operation),
|
|
50909
|
+
...buildValidatorAssignments(operation, warnings),
|
|
50222
50910
|
"function selectedResponseContract() {",
|
|
50223
50911
|
" var status = String(pm.response.code);",
|
|
50224
50912
|
" if (contract.responses[status]) return { key: status, value: contract.responses[status] };",
|
|
@@ -50232,6 +50920,15 @@ function createContractScript(operation) {
|
|
|
50232
50920
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
50233
50921
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
50234
50922
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
50923
|
+
"function coerceBySchema(value, schema) {",
|
|
50924
|
+
" var type = schema && schema.type;",
|
|
50925
|
+
" var types = Array.isArray(type) ? type : [type];",
|
|
50926
|
+
' if ((types.indexOf("integer") !== -1 || types.indexOf("number") !== -1) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(String(value).trim())) return Number(value);',
|
|
50927
|
+
' if (types.indexOf("boolean") !== -1 && (value === "true" || value === "false")) return value === "true";',
|
|
50928
|
+
" return value;",
|
|
50929
|
+
"}",
|
|
50930
|
+
'function requestHeader(name) { var value = ""; pm.request.headers.each(function (header) { if (header && header.disabled !== true && String(header.key).toLowerCase() === String(name).toLowerCase()) value = String(header.value); }); return value; }',
|
|
50931
|
+
"function hasQueryParam(name) { var found = false; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === String(name).toLowerCase()) found = true; }); return found; }",
|
|
50235
50932
|
"function mediaScore(expected, actual) {",
|
|
50236
50933
|
" var e = mediaParts(expected); var a = mediaParts(actual);",
|
|
50237
50934
|
" if (!e.raw || !a.raw) return 0;",
|
|
@@ -50262,7 +50959,11 @@ function createContractScript(operation) {
|
|
|
50262
50959
|
" if (!actual) return;",
|
|
50263
50960
|
' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
|
|
50264
50961
|
" var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
|
|
50265
|
-
|
|
50962
|
+
" if (!headerValidator || headerValidator.skip) return;",
|
|
50963
|
+
" var expected;",
|
|
50964
|
+
' if (header.items) { var joined = String(actual).trim(); expected = joined === "" ? [] : joined.split(",").map(function (entry) { return coerceBySchema(entry.trim(), header.items); }); }',
|
|
50965
|
+
" else expected = coerceBySchema(actual, header.schema);",
|
|
50966
|
+
' if (!headerValidator(expected)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
|
|
50266
50967
|
" });",
|
|
50267
50968
|
"});",
|
|
50268
50969
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
@@ -50290,11 +50991,122 @@ function createContractScript(operation) {
|
|
|
50290
50991
|
' if (media.media.unsupported) pm.expect.fail("OpenAPI schema unsupported for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + media.media.unsupported);',
|
|
50291
50992
|
" if (!media.media.schema) { return; }",
|
|
50292
50993
|
" var validate = validators[selected.key] && validators[selected.key][media.expected];",
|
|
50994
|
+
" if (validate && validate.skip) return;",
|
|
50293
50995
|
' if (!validate) pm.expect.fail("OpenAPI schema validator was not generated for " + media.expected);',
|
|
50294
50996
|
' var actual = mediaParts(pm.response.headers.get("Content-Type") || "");',
|
|
50295
50997
|
" var value = isJsonSubtype(actual.subtype) ? pm.response.json() : responseText();",
|
|
50296
|
-
|
|
50998
|
+
// Non-JSON object-schema bodies skip schema validation instead of
|
|
50999
|
+
// failing; the index emits CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED so the
|
|
51000
|
+
// skip is visible at instrumentation time.
|
|
51001
|
+
' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") { return; }',
|
|
50297
51002
|
' if (!validate(value)) pm.expect.fail("OpenAPI schema validation failed for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + JSON.stringify(validate.errors || []));',
|
|
51003
|
+
"});",
|
|
51004
|
+
...operation.security ? [
|
|
51005
|
+
"pm.test('Request carries credentials required by OpenAPI security', function () {",
|
|
51006
|
+
" function satisfied(check) {",
|
|
51007
|
+
" if (!check.checkable) return true;",
|
|
51008
|
+
' if (check.in === "query") return hasQueryParam(check.name);',
|
|
51009
|
+
' if (check.in === "cookie") { if (pm.cookies && pm.cookies.has && pm.cookies.has(String(check.name))) return true; return requestHeader("Cookie").split(";").some(function (part) { return part.split("=")[0].trim() === String(check.name); }); }',
|
|
51010
|
+
' if (check.prefix) { return requestHeader("Authorization").toLowerCase().indexOf(check.prefix.toLowerCase()) === 0; }',
|
|
51011
|
+
' if (check.kind === "oauth2" || check.kind === "openIdConnect") { return Boolean(requestHeader("Authorization")) || hasQueryParam("access_token"); }',
|
|
51012
|
+
" return Boolean(requestHeader(check.name));",
|
|
51013
|
+
" }",
|
|
51014
|
+
" var alternatives = contract.security || [];",
|
|
51015
|
+
" var ok = alternatives.some(function (alternative) { return alternative.every(function (check) { return satisfied(check); }); });",
|
|
51016
|
+
' if (!ok) pm.expect.fail("Request did not carry credentials for any OpenAPI security requirement of " + contract.method + " " + contract.path + ": " + alternatives.map(function (alternative) { return alternative.map(function (check) { return check.scheme + " (" + check.kind + ")"; }).join(" + "); }).join(" | "));',
|
|
51017
|
+
"});"
|
|
51018
|
+
] : [],
|
|
51019
|
+
...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
|
|
51020
|
+
"pm.test('Request parameters match OpenAPI schemas', function () {",
|
|
51021
|
+
' function queryValue(name) { var value; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === name) value = param.value === null || param.value === undefined ? "" : String(param.value); }); return value; }',
|
|
51022
|
+
' function queryValues(name) { var values = []; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === name) values.push(param.value === null || param.value === undefined ? "" : String(param.value)); }); return values; }',
|
|
51023
|
+
' function headerValue(name) { var value; pm.request.headers.each(function (header) { if (header && header.disabled !== true && String(header.key).toLowerCase() === String(name).toLowerCase()) value = header.value === null || header.value === undefined ? "" : String(header.value); }); return value; }',
|
|
51024
|
+
' function cookieValue(name) { try { if (pm.cookies && pm.cookies.get) { var jar = pm.cookies.get(String(name)); if (jar !== null && jar !== undefined) return String(jar); } } catch (ignored) {} var raw = requestHeader("Cookie"); if (!raw) return undefined; var found; raw.split(";").forEach(function (part) { var split = part.indexOf("="); if (split === -1) return; if (part.slice(0, split).trim() === String(name)) found = part.slice(split + 1).trim(); }); return found; }',
|
|
51025
|
+
' function requestPathSegments() { var raw = ""; try { raw = typeof pm.request.url.getPath === "function" ? String(pm.request.url.getPath() || "") : ""; } catch (ignored) {} if (!raw) { var path = pm.request.url.path; raw = Array.isArray(path) ? "/" + path.join("/") : String(path || ""); } return raw.split("?")[0].split("#")[0].split("/").filter(function (segment) { return segment.length > 0; }); }',
|
|
51026
|
+
// Server path prefixes sit ahead of the template segments, so the
|
|
51027
|
+
// template aligns against the trailing request segments.
|
|
51028
|
+
' function pathParamValue(name) { var template = String(contract.path).split("/").filter(function (segment) { return segment.length > 0; }); var actual = requestPathSegments(); var offset = actual.length - template.length; if (offset < 0) return undefined; for (var i = 0; i < template.length; i += 1) { if (template[i] === "{" + name + "}") { var segment = actual[offset + i]; try { return decodeURIComponent(segment); } catch (ignored) { return segment; } } } return undefined; }',
|
|
51029
|
+
' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
|
|
51030
|
+
' function splitDelimited(value, decode) { if (decode === "csv") return value.split(","); if (decode === "ssv") return value.split(/%20| /); return value.split(/%7C|\\|/i); }',
|
|
51031
|
+
" function decodeComponent(value) { try { return decodeURIComponent(value); } catch (ignored) { return value; } }",
|
|
51032
|
+
" (contract.parameters || []).forEach(function (param) {",
|
|
51033
|
+
' var key = param.in + ":" + String(param.name).toLowerCase();',
|
|
51034
|
+
" var validate = paramValidators[key];",
|
|
51035
|
+
" if (!validate || validate.skip) return;",
|
|
51036
|
+
" var value;",
|
|
51037
|
+
' if (param.decode === "multi") {',
|
|
51038
|
+
" var entries = queryValues(String(param.name).toLowerCase());",
|
|
51039
|
+
' if (entries.length === 0) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
51040
|
+
' if (param.allowEmptyValue && entries.length === 1 && entries[0] === "") return;',
|
|
51041
|
+
" if (entries.some(isPlaceholder)) return;",
|
|
51042
|
+
" value = entries.map(function (entry) { return coerceBySchema(decodeComponent(entry), param.items); });",
|
|
51043
|
+
" } else if (param.decode) {",
|
|
51044
|
+
' var joined = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
|
|
51045
|
+
' if (joined === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
51046
|
+
' if (joined === "" && param.allowEmptyValue) return;',
|
|
51047
|
+
" if (isPlaceholder(joined)) return;",
|
|
51048
|
+
' var parts = joined === "" ? [] : splitDelimited(String(joined), param.decode);',
|
|
51049
|
+
// HTTP header lists allow optional whitespace after the comma, so
|
|
51050
|
+
// header-sourced items are trimmed; query values stay literal.
|
|
51051
|
+
' if (param.in === "header") parts = parts.map(function (entry) { return entry.trim(); });',
|
|
51052
|
+
" if (parts.some(isPlaceholder)) return;",
|
|
51053
|
+
// Query values arrive percent-encoded while the server validates the
|
|
51054
|
+
// decoded form; header values are never percent-encoded.
|
|
51055
|
+
' value = parts.map(function (entry) { return coerceBySchema(param.in === "query" ? decodeComponent(entry) : entry, param.items); });',
|
|
51056
|
+
' } else if (param.in === "path") {',
|
|
51057
|
+
" value = pathParamValue(param.name);",
|
|
51058
|
+
" if (value === undefined) return;",
|
|
51059
|
+
' if (isPlaceholder(value) || value.charAt(0) === ":" || value.charAt(0) === "{") return;',
|
|
51060
|
+
" value = coerceBySchema(value, param.schema);",
|
|
51061
|
+
' } else if (param.in === "cookie") {',
|
|
51062
|
+
" value = cookieValue(param.name);",
|
|
51063
|
+
' if (value === undefined) { if (param.required) pm.expect.fail("Required cookie parameter " + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
51064
|
+
" if (isPlaceholder(value)) return;",
|
|
51065
|
+
" value = coerceBySchema(value, param.schema);",
|
|
51066
|
+
" } else {",
|
|
51067
|
+
' value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
|
|
51068
|
+
' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
51069
|
+
' if (value === "" && param.allowEmptyValue) return;',
|
|
51070
|
+
" if (isPlaceholder(value)) return;",
|
|
51071
|
+
" if (param.content) {",
|
|
51072
|
+
" var parsed;",
|
|
51073
|
+
' try { parsed = JSON.parse(value); } catch (parseError) { pm.expect.fail("Parameter " + param.in + ":" + param.name + " declares JSON content but its value is not parseable JSON for " + contract.method + " " + contract.path); return; }',
|
|
51074
|
+
' if (!validate(parsed)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
51075
|
+
" return;",
|
|
51076
|
+
" }",
|
|
51077
|
+
" value = coerceBySchema(value, param.schema);",
|
|
51078
|
+
" }",
|
|
51079
|
+
' if (!validate(value)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
51080
|
+
" });",
|
|
51081
|
+
"});"
|
|
51082
|
+
] : [],
|
|
51083
|
+
...operation.requestBody?.jsonSchemas && Object.keys(operation.requestBody.jsonSchemas).length > 0 ? [
|
|
51084
|
+
"pm.test('Request body matches OpenAPI request schema', function () {",
|
|
51085
|
+
" var body = pm.request.body;",
|
|
51086
|
+
' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
|
|
51087
|
+
" if (!raw.trim()) return;",
|
|
51088
|
+
' if (/"<[^"<>]*>"/.test(raw) || raw.indexOf("{{") !== -1) return;',
|
|
51089
|
+
' var validate = requestBodyValidators[mediaBase(requestHeader("Content-Type"))];',
|
|
51090
|
+
" if (!validate || validate.skip) return;",
|
|
51091
|
+
" var parsed;",
|
|
51092
|
+
// Unquoted generator placeholders such as {"count": <long>} break
|
|
51093
|
+
// JSON.parse; a parse failure alongside an angle-bracket token is
|
|
51094
|
+
// treated as a placeholder body rather than drift.
|
|
51095
|
+
' try { parsed = JSON.parse(raw); } catch (error) { if (/<[A-Za-z][A-Za-z0-9_ -]*>/.test(raw)) return; pm.expect.fail("Request body for " + contract.method + " " + contract.path + " is not valid JSON: " + error); return; }',
|
|
51096
|
+
' if (!validate(parsed)) pm.expect.fail("Request body failed OpenAPI request schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
51097
|
+
"});"
|
|
51098
|
+
] : [],
|
|
51099
|
+
"pm.test('Content-Length is consistent with OpenAPI body expectations', function () {",
|
|
51100
|
+
' var raw = pm.response.headers.get("Content-Length");',
|
|
51101
|
+
" if (raw === null || raw === undefined) return;",
|
|
51102
|
+
// Combined duplicate Content-Length values ("5, 5") fail the integer
|
|
51103
|
+
// syntax check on purpose: RFC 9110 tolerates identical repeats, but a
|
|
51104
|
+
// contract test surfacing them is strict by design.
|
|
51105
|
+
' if (!/^[0-9]+$/.test(String(raw).trim())) pm.expect.fail("Content-Length header is not a non-negative integer: " + raw);',
|
|
51106
|
+
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
51107
|
+
' if (pm.response.headers.get("Content-Encoding")) return;',
|
|
51108
|
+
" var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
|
|
51109
|
+
' if (mustBeEmpty && Number(String(raw).trim()) !== 0) pm.expect.fail("OpenAPI defines no response body for " + contract.method + " " + contract.path + " status " + pm.response.code + " but Content-Length was " + raw);',
|
|
50298
51110
|
"});"
|
|
50299
51111
|
];
|
|
50300
51112
|
}
|
|
@@ -50387,14 +51199,160 @@ function assertStaticRequestShape(operation, request) {
|
|
|
50387
51199
|
if (!contentType) {
|
|
50388
51200
|
throw new Error(`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} missing required request Content-Type`);
|
|
50389
51201
|
}
|
|
50390
|
-
const actual = contentType.toLowerCase().split(";")[0]?.trim();
|
|
50391
|
-
const matches = operation.requestBody.contentTypes.some((expected) => expected.toLowerCase()
|
|
51202
|
+
const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
51203
|
+
const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
|
|
50392
51204
|
if (!matches) {
|
|
50393
51205
|
throw new Error(
|
|
50394
51206
|
`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} request Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
|
|
50395
51207
|
);
|
|
50396
51208
|
}
|
|
50397
51209
|
}
|
|
51210
|
+
const warnings = collectStaticBodyWarnings(operation, request, contentType);
|
|
51211
|
+
if (operation.requestBody && !operation.requestBody.required && operation.requestBody.contentTypes.length > 0 && hasRequestBody(request) && contentType) {
|
|
51212
|
+
const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
51213
|
+
const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
|
|
51214
|
+
if (!matches) {
|
|
51215
|
+
warnings.push(
|
|
51216
|
+
`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} optional request body Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
|
|
51217
|
+
);
|
|
51218
|
+
}
|
|
51219
|
+
}
|
|
51220
|
+
return warnings;
|
|
51221
|
+
}
|
|
51222
|
+
function requestBodyFieldNames(request, base) {
|
|
51223
|
+
const body = asRecord5(request.body);
|
|
51224
|
+
if (!body) return void 0;
|
|
51225
|
+
if (base === "application/json" || /\+json$/.test(base)) {
|
|
51226
|
+
if (body.mode !== "raw" || typeof body.raw !== "string") return void 0;
|
|
51227
|
+
let parsed;
|
|
51228
|
+
try {
|
|
51229
|
+
parsed = JSON.parse(body.raw);
|
|
51230
|
+
} catch {
|
|
51231
|
+
return void 0;
|
|
51232
|
+
}
|
|
51233
|
+
const record = asRecord5(parsed);
|
|
51234
|
+
return record ? Object.keys(record) : void 0;
|
|
51235
|
+
}
|
|
51236
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
51237
|
+
if (!mode || !Array.isArray(body[mode])) return void 0;
|
|
51238
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true).map((entry) => String(entry.key || "")).filter(Boolean);
|
|
51239
|
+
}
|
|
51240
|
+
function requestBodyEntries(request, base) {
|
|
51241
|
+
const body = asRecord5(request.body);
|
|
51242
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
51243
|
+
if (!body || !mode || !Array.isArray(body[mode])) return void 0;
|
|
51244
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true);
|
|
51245
|
+
}
|
|
51246
|
+
function mediaTypeMatchesPattern(pattern, actual) {
|
|
51247
|
+
return pattern.split(",").some((candidate) => {
|
|
51248
|
+
const entry = (candidate.split(";")[0] ?? "").trim();
|
|
51249
|
+
if (!entry) return false;
|
|
51250
|
+
if (entry === "*/*" || entry === actual) return true;
|
|
51251
|
+
if (entry.endsWith("/*")) return actual.startsWith(entry.slice(0, -1));
|
|
51252
|
+
return false;
|
|
51253
|
+
});
|
|
51254
|
+
}
|
|
51255
|
+
function isJsonEncodingContentType(declared) {
|
|
51256
|
+
return declared.split(",").some((candidate) => {
|
|
51257
|
+
const entry = (candidate.split(";")[0] ?? "").trim();
|
|
51258
|
+
return entry === "application/json" || entry.endsWith("+json");
|
|
51259
|
+
});
|
|
51260
|
+
}
|
|
51261
|
+
function isPlaceholderValue(value) {
|
|
51262
|
+
return /^<[^>]*>$/.test(value.trim()) || value.includes("{{");
|
|
51263
|
+
}
|
|
51264
|
+
function collectStaticEncodingWarnings(operation, request, base, rule) {
|
|
51265
|
+
const encodings = rule.encodings;
|
|
51266
|
+
if (!encodings) return [];
|
|
51267
|
+
const multipart = base === "multipart/form-data";
|
|
51268
|
+
const entries = requestBodyEntries(request, base);
|
|
51269
|
+
if (!entries) return [];
|
|
51270
|
+
const warnings = [];
|
|
51271
|
+
const part = multipart ? "multipart" : "urlencoded";
|
|
51272
|
+
for (const [field, encoding] of Object.entries(encodings)) {
|
|
51273
|
+
for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
|
|
51274
|
+
if (multipart && encoding.binary && String(entry.type || "") !== "file") {
|
|
51275
|
+
warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
|
|
51276
|
+
}
|
|
51277
|
+
if (multipart && encoding.contentType) {
|
|
51278
|
+
const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
|
|
51279
|
+
if (!actual) {
|
|
51280
|
+
warnings.push(
|
|
51281
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
|
|
51282
|
+
);
|
|
51283
|
+
} else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
|
|
51284
|
+
warnings.push(
|
|
51285
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
|
|
51286
|
+
);
|
|
51287
|
+
}
|
|
51288
|
+
}
|
|
51289
|
+
if (encoding.contentType && isJsonEncodingContentType(encoding.contentType) && String(entry.type || "text") !== "file") {
|
|
51290
|
+
const value = typeof entry.value === "string" ? entry.value : "";
|
|
51291
|
+
if (value.trim() && !isPlaceholderValue(value)) {
|
|
51292
|
+
try {
|
|
51293
|
+
JSON.parse(value);
|
|
51294
|
+
} catch {
|
|
51295
|
+
warnings.push(
|
|
51296
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated ${part} field ${field} declares JSON encoding ${encoding.contentType} but its value is not parseable JSON`
|
|
51297
|
+
);
|
|
51298
|
+
}
|
|
51299
|
+
}
|
|
51300
|
+
}
|
|
51301
|
+
}
|
|
51302
|
+
}
|
|
51303
|
+
return warnings;
|
|
51304
|
+
}
|
|
51305
|
+
function coerceFormValue(value, schema2) {
|
|
51306
|
+
const record = asRecord5(schema2);
|
|
51307
|
+
const type2 = record?.type;
|
|
51308
|
+
const types2 = Array.isArray(type2) ? type2 : [type2];
|
|
51309
|
+
if ((types2.includes("integer") || types2.includes("number")) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(value.trim())) return Number(value);
|
|
51310
|
+
if (types2.includes("boolean") && (value === "true" || value === "false")) return value === "true";
|
|
51311
|
+
return value;
|
|
51312
|
+
}
|
|
51313
|
+
function collectStaticFieldSchemaWarnings(operation, request, base, rule) {
|
|
51314
|
+
const fieldSchemas = rule.fieldSchemas;
|
|
51315
|
+
if (!fieldSchemas) return [];
|
|
51316
|
+
const entries = requestBodyEntries(request, base);
|
|
51317
|
+
if (!entries) return [];
|
|
51318
|
+
const part = base === "multipart/form-data" ? "multipart" : "urlencoded";
|
|
51319
|
+
const warnings = [];
|
|
51320
|
+
for (const [field, schema2] of Object.entries(fieldSchemas)) {
|
|
51321
|
+
const validate2 = compileSchemaValidator(schema2);
|
|
51322
|
+
if (!validate2) continue;
|
|
51323
|
+
for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
|
|
51324
|
+
if (String(entry.type || "text") === "file") continue;
|
|
51325
|
+
const value = typeof entry.value === "string" ? entry.value : "";
|
|
51326
|
+
if (!value.trim() || isPlaceholderValue(value)) continue;
|
|
51327
|
+
if (!validate2(coerceFormValue(value, schema2))) {
|
|
51328
|
+
warnings.push(`CONTRACT_FORM_FIELD_SCHEMA_MISMATCH: ${operation.id} generated ${part} field ${field} value does not match its schema`);
|
|
51329
|
+
}
|
|
51330
|
+
}
|
|
51331
|
+
}
|
|
51332
|
+
return warnings;
|
|
51333
|
+
}
|
|
51334
|
+
function collectStaticBodyWarnings(operation, request, contentType) {
|
|
51335
|
+
const rules = operation.requestBody?.fieldRules;
|
|
51336
|
+
if (!rules) return [];
|
|
51337
|
+
const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
|
|
51338
|
+
const rule = rules[base];
|
|
51339
|
+
if (!rule) return [];
|
|
51340
|
+
const warnings = [
|
|
51341
|
+
...collectStaticEncodingWarnings(operation, request, base, rule),
|
|
51342
|
+
...collectStaticFieldSchemaWarnings(operation, request, base, rule)
|
|
51343
|
+
];
|
|
51344
|
+
const names = requestBodyFieldNames(request, base);
|
|
51345
|
+
if (!names) return warnings;
|
|
51346
|
+
const present = new Set(names);
|
|
51347
|
+
const missing = rule.required.filter((name) => !present.has(name));
|
|
51348
|
+
if (missing.length > 0) {
|
|
51349
|
+
warnings.push(`CONTRACT_REQUEST_BODY_INCOMPLETE: ${operation.id} generated request body is missing required properties: ${missing.join(", ")}`);
|
|
51350
|
+
}
|
|
51351
|
+
const readOnlySent = rule.readOnly.filter((name) => present.has(name));
|
|
51352
|
+
if (readOnlySent.length > 0) {
|
|
51353
|
+
warnings.push(`CONTRACT_READONLY_PROPERTY_IN_REQUEST: ${operation.id} generated request body includes readOnly properties: ${readOnlySent.join(", ")}`);
|
|
51354
|
+
}
|
|
51355
|
+
return warnings;
|
|
50398
51356
|
}
|
|
50399
51357
|
function validateScript(script) {
|
|
50400
51358
|
const source = script.join("\n");
|
|
@@ -50443,9 +51401,12 @@ function instrumentContractCollection(collection, index) {
|
|
|
50443
51401
|
if (result.operation) {
|
|
50444
51402
|
const previous = covered.get(result.operation.id);
|
|
50445
51403
|
if (previous) throw new Error(`CONTRACT_DUPLICATE_OPERATION_REQUEST: ${result.operation.id} matched more than one generated request (${previous}, ${String(item.name || "<unnamed>")})`);
|
|
50446
|
-
assertStaticRequestShape(result.operation, request);
|
|
51404
|
+
warnings.push(...assertStaticRequestShape(result.operation, request));
|
|
51405
|
+
for (const name of [...requestQueryNames(request)].filter((entry) => !result.operation.declaredQueryParameters.includes(entry))) {
|
|
51406
|
+
warnings.push(`CONTRACT_UNDOCUMENTED_QUERY_PARAM: ${result.operation.id} generated request sends query parameter ${name} that the OpenAPI operation does not declare`);
|
|
51407
|
+
}
|
|
50447
51408
|
covered.set(result.operation.id, String(item.name || "<unnamed>"));
|
|
50448
|
-
script = createContractScript(result.operation);
|
|
51409
|
+
script = createContractScript(result.operation, warnings);
|
|
50449
51410
|
} else if (result.ambiguous && result.ambiguous.length > 0) {
|
|
50450
51411
|
script = createMappingFailureScript(`Ambiguous OpenAPI operation match for request ${result.method} ${result.path}: ${result.ambiguous.map((entry) => entry.id).join(", ")}`);
|
|
50451
51412
|
} else {
|