@postman-cse/onboarding-bootstrap 1.0.0 → 1.1.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 +21 -4
- package/dist/action.cjs +806 -104
- package/dist/cli.cjs +806 -104
- package/dist/index.cjs +806 -104
- package/package.json +1 -1
package/dist/action.cjs
CHANGED
|
@@ -49611,8 +49611,6 @@ var COMPATIBLE_SCHEMA_URIS = /* @__PURE__ */ new Set([
|
|
|
49611
49611
|
`${DRAFT_2020_12_SCHEMA_URI}#`
|
|
49612
49612
|
]);
|
|
49613
49613
|
var ASSERTION_KEYS = /* @__PURE__ */ new Set([
|
|
49614
|
-
"$defs",
|
|
49615
|
-
"$id",
|
|
49616
49614
|
"$ref",
|
|
49617
49615
|
"$schema",
|
|
49618
49616
|
"additionalItems",
|
|
@@ -49657,6 +49655,10 @@ var ASSERTION_KEYS = /* @__PURE__ */ new Set([
|
|
|
49657
49655
|
]);
|
|
49658
49656
|
var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
49659
49657
|
"title",
|
|
49658
|
+
"$comment",
|
|
49659
|
+
"$defs",
|
|
49660
|
+
"definitions",
|
|
49661
|
+
"$id",
|
|
49660
49662
|
"description",
|
|
49661
49663
|
"default",
|
|
49662
49664
|
"example",
|
|
@@ -49667,8 +49669,32 @@ var STRIP_KEYS = /* @__PURE__ */ new Set([
|
|
|
49667
49669
|
"xml",
|
|
49668
49670
|
"externalDocs",
|
|
49669
49671
|
"discriminator",
|
|
49670
|
-
"
|
|
49672
|
+
"contentEncoding",
|
|
49673
|
+
"contentMediaType",
|
|
49674
|
+
"contentSchema"
|
|
49671
49675
|
]);
|
|
49676
|
+
var ASSERTED_FORMATS = /* @__PURE__ */ new Set([
|
|
49677
|
+
"date-time",
|
|
49678
|
+
"date",
|
|
49679
|
+
"time",
|
|
49680
|
+
"duration",
|
|
49681
|
+
"email",
|
|
49682
|
+
"hostname",
|
|
49683
|
+
"ipv4",
|
|
49684
|
+
"ipv6",
|
|
49685
|
+
"uri",
|
|
49686
|
+
"uri-reference",
|
|
49687
|
+
"uri-template",
|
|
49688
|
+
"uuid",
|
|
49689
|
+
"json-pointer",
|
|
49690
|
+
"relative-json-pointer",
|
|
49691
|
+
"regex"
|
|
49692
|
+
]);
|
|
49693
|
+
var INT32_MIN = -2147483648;
|
|
49694
|
+
var INT32_MAX = 2147483647;
|
|
49695
|
+
var MAX_REFERENCED_SCHEMAS = 400;
|
|
49696
|
+
var DRAFT_2020_12_ONLY_KEYS = /* @__PURE__ */ new Set(["prefixItems", "dependentRequired", "dependentSchemas", "minContains", "maxContains", "unevaluatedItems", "unevaluatedProperties"]);
|
|
49697
|
+
var DRAFT_07_ONLY_KEYS = /* @__PURE__ */ new Set(["dependencies", "additionalItems"]);
|
|
49672
49698
|
function asRecord3(value) {
|
|
49673
49699
|
if (!value || typeof value !== "object" || Array.isArray(value)) return null;
|
|
49674
49700
|
return value;
|
|
@@ -49686,8 +49712,8 @@ function resolvePointer(root, ref) {
|
|
|
49686
49712
|
function unsupported(message) {
|
|
49687
49713
|
return { unsupported: message };
|
|
49688
49714
|
}
|
|
49689
|
-
function
|
|
49690
|
-
const values = asArray(required).map((entry) => String(entry)).filter((entry) => !
|
|
49715
|
+
function mergeRequiredWithoutStripped(required, strippedProperties) {
|
|
49716
|
+
const values = asArray(required).map((entry) => String(entry)).filter((entry) => !strippedProperties.has(entry));
|
|
49691
49717
|
return values.length > 0 ? values : void 0;
|
|
49692
49718
|
}
|
|
49693
49719
|
function hasUnsupported(child2) {
|
|
@@ -49703,11 +49729,31 @@ function rootDialect(root, version, schemaRecord) {
|
|
|
49703
49729
|
if (declared.includes("draft-07")) return DRAFT_07_SCHEMA_URI;
|
|
49704
49730
|
return DRAFT_2020_12_SCHEMA_URI;
|
|
49705
49731
|
}
|
|
49706
|
-
function
|
|
49707
|
-
const
|
|
49708
|
-
if (
|
|
49732
|
+
function registerDef(ctx, ref) {
|
|
49733
|
+
const existing = ctx.defs.get(ref);
|
|
49734
|
+
if (existing) return existing;
|
|
49735
|
+
if (ctx.defs.size >= MAX_REFERENCED_SCHEMAS) {
|
|
49736
|
+
const entry2 = { name: "overflow", unsupported: `OpenAPI schema reference graph exceeded ${MAX_REFERENCED_SCHEMAS} referenced schemas` };
|
|
49737
|
+
return entry2;
|
|
49738
|
+
}
|
|
49739
|
+
const entry = { name: `d${ctx.defs.size}` };
|
|
49740
|
+
ctx.defs.set(ref, entry);
|
|
49741
|
+
const resolved = resolvePointer(ctx.root, ref);
|
|
49742
|
+
if (resolved === void 0) {
|
|
49743
|
+
entry.unsupported = `Unresolved OpenAPI $ref: ${ref}`;
|
|
49744
|
+
return entry;
|
|
49745
|
+
}
|
|
49746
|
+
const normalized = normalizeSchema(ctx, resolved, { depth: 0, rootSchema: false });
|
|
49747
|
+
const bad = hasUnsupported(normalized);
|
|
49748
|
+
if (bad) entry.unsupported = bad;
|
|
49749
|
+
else entry.schema = normalized;
|
|
49750
|
+
return entry;
|
|
49751
|
+
}
|
|
49752
|
+
function normalizeSchema(ctx, schema2, options) {
|
|
49753
|
+
const depth = options.depth;
|
|
49754
|
+
if (depth > 50) return unsupported("OpenAPI schema nesting depth exceeded 50");
|
|
49709
49755
|
if (Array.isArray(schema2)) {
|
|
49710
|
-
const normalized2 = schema2.map((entry) => normalizeSchema(
|
|
49756
|
+
const normalized2 = schema2.map((entry) => normalizeSchema(ctx, entry, { depth: depth + 1, rootSchema: false }));
|
|
49711
49757
|
const bad = normalized2.map(hasUnsupported).find(Boolean);
|
|
49712
49758
|
return bad ? unsupported(bad) : normalized2;
|
|
49713
49759
|
}
|
|
@@ -49716,40 +49762,42 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49716
49762
|
const ref = typeof record.$ref === "string" ? record.$ref : "";
|
|
49717
49763
|
if (ref) {
|
|
49718
49764
|
if (!ref.startsWith("#/")) return unsupported(`External OpenAPI $ref remained unresolved: ${ref}`);
|
|
49719
|
-
const
|
|
49720
|
-
|
|
49721
|
-
|
|
49722
|
-
|
|
49723
|
-
|
|
49724
|
-
|
|
49725
|
-
|
|
49726
|
-
|
|
49727
|
-
|
|
49728
|
-
|
|
49729
|
-
|
|
49730
|
-
const
|
|
49731
|
-
if (
|
|
49732
|
-
|
|
49733
|
-
|
|
49734
|
-
|
|
49735
|
-
|
|
49736
|
-
rootSchema: false,
|
|
49737
|
-
stack: [...stack, ref]
|
|
49738
|
-
});
|
|
49765
|
+
const siblingEntries = ctx.version === "3.1" ? Object.entries(record).filter(([key]) => key !== "$ref") : [];
|
|
49766
|
+
const inlineStack = options.inlineStack ?? [];
|
|
49767
|
+
if (options.rootSchema && siblingEntries.length === 0 && !inlineStack.includes(ref)) {
|
|
49768
|
+
const resolved = resolvePointer(ctx.root, ref);
|
|
49769
|
+
if (resolved === void 0) return unsupported(`Unresolved OpenAPI $ref: ${ref}`);
|
|
49770
|
+
const inlined = normalizeSchema(ctx, resolved, { depth, rootSchema: true, inlineStack: [...inlineStack, ref] });
|
|
49771
|
+
const bad = hasUnsupported(inlined);
|
|
49772
|
+
return bad ? unsupported(bad) : inlined;
|
|
49773
|
+
}
|
|
49774
|
+
const entry = registerDef(ctx, ref);
|
|
49775
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
49776
|
+
const refNode = { $ref: `#/$defs/${entry.name}` };
|
|
49777
|
+
if (siblingEntries.length === 0) {
|
|
49778
|
+
if (options.rootSchema) refNode.$schema = ctx.dialect;
|
|
49779
|
+
return refNode;
|
|
49780
|
+
}
|
|
49781
|
+
const siblingSchema = normalizeSchema(ctx, Object.fromEntries(siblingEntries), { depth: depth + 1, rootSchema: false });
|
|
49739
49782
|
const siblingUnsupported = hasUnsupported(siblingSchema);
|
|
49740
49783
|
if (siblingUnsupported) return unsupported(siblingUnsupported);
|
|
49741
|
-
const wrapper = { allOf: [
|
|
49742
|
-
if (options.rootSchema) wrapper.$schema =
|
|
49784
|
+
const wrapper = { allOf: [refNode, siblingSchema] };
|
|
49785
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
49743
49786
|
return wrapper;
|
|
49744
49787
|
}
|
|
49745
49788
|
const sourceSchema = { ...record };
|
|
49746
|
-
const nullable = sourceSchema.nullable === true &&
|
|
49789
|
+
const nullable = sourceSchema.nullable === true && ctx.version === "3.0";
|
|
49747
49790
|
delete sourceSchema.nullable;
|
|
49748
|
-
const
|
|
49791
|
+
const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
|
|
49792
|
+
const strippedProperties = /* @__PURE__ */ new Set();
|
|
49749
49793
|
const rawProperties = asRecord3(sourceSchema.properties);
|
|
49750
49794
|
if (rawProperties) {
|
|
49751
49795
|
for (const [propertyName, propertySchema] of Object.entries(rawProperties)) {
|
|
49752
|
-
|
|
49796
|
+
let flagSource = asRecord3(propertySchema);
|
|
49797
|
+
if (typeof flagSource?.$ref === "string" && flagSource.$ref.startsWith("#/")) {
|
|
49798
|
+
flagSource = asRecord3(resolvePointer(ctx.root, flagSource.$ref)) ?? flagSource;
|
|
49799
|
+
}
|
|
49800
|
+
if (flagSource?.[directionStrippedFlag] === true) strippedProperties.add(propertyName);
|
|
49753
49801
|
}
|
|
49754
49802
|
}
|
|
49755
49803
|
const normalized = {};
|
|
@@ -49776,17 +49824,69 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49776
49824
|
delete normalized.maximum;
|
|
49777
49825
|
continue;
|
|
49778
49826
|
}
|
|
49827
|
+
if (key === "format") {
|
|
49828
|
+
if (typeof value === "string" && ASSERTED_FORMATS.has(value)) normalized.format = value;
|
|
49829
|
+
continue;
|
|
49830
|
+
}
|
|
49779
49831
|
if (!ASSERTION_KEYS.has(key)) return unsupported(`Unsupported OpenAPI schema keyword: ${key}`);
|
|
49780
|
-
if (
|
|
49832
|
+
if (ctx.dialect === DRAFT_07_SCHEMA_URI && DRAFT_2020_12_ONLY_KEYS.has(key)) {
|
|
49833
|
+
return unsupported(`${key} requires the JSON Schema 2020-12 dialect`);
|
|
49834
|
+
}
|
|
49835
|
+
if (ctx.dialect === DRAFT_2020_12_SCHEMA_URI && DRAFT_07_ONLY_KEYS.has(key)) {
|
|
49836
|
+
return unsupported(`${key} is a draft-07 keyword and is unsupported under JSON Schema 2020-12`);
|
|
49837
|
+
}
|
|
49838
|
+
if (key === "items" && Array.isArray(value) && ctx.version === "3.0") {
|
|
49781
49839
|
return unsupported("Tuple array items are unsupported in OpenAPI 3.0");
|
|
49782
49840
|
}
|
|
49841
|
+
if (key === "patternProperties" || key === "dependentSchemas") {
|
|
49842
|
+
const map2 = asRecord3(value);
|
|
49843
|
+
if (!map2) continue;
|
|
49844
|
+
const next = {};
|
|
49845
|
+
for (const [mapKey, mapSchema] of Object.entries(map2)) {
|
|
49846
|
+
const child3 = normalizeSchema(ctx, mapSchema, { depth: depth + 1, rootSchema: false });
|
|
49847
|
+
const bad2 = hasUnsupported(child3);
|
|
49848
|
+
if (bad2) return unsupported(bad2);
|
|
49849
|
+
next[mapKey] = child3;
|
|
49850
|
+
}
|
|
49851
|
+
normalized[key] = next;
|
|
49852
|
+
continue;
|
|
49853
|
+
}
|
|
49854
|
+
if (key === "dependentRequired") {
|
|
49855
|
+
const map2 = asRecord3(value);
|
|
49856
|
+
if (!map2) continue;
|
|
49857
|
+
for (const names of Object.values(map2)) {
|
|
49858
|
+
if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
|
|
49859
|
+
return unsupported("dependentRequired requires string array values");
|
|
49860
|
+
}
|
|
49861
|
+
}
|
|
49862
|
+
normalized.dependentRequired = value;
|
|
49863
|
+
continue;
|
|
49864
|
+
}
|
|
49865
|
+
if (key === "dependencies") {
|
|
49866
|
+
const map2 = asRecord3(value);
|
|
49867
|
+
if (!map2) continue;
|
|
49868
|
+
const next = {};
|
|
49869
|
+
for (const [mapKey, dependency] of Object.entries(map2)) {
|
|
49870
|
+
if (Array.isArray(dependency)) {
|
|
49871
|
+
if (dependency.some((name) => typeof name !== "string")) return unsupported("dependencies requires string array or schema values");
|
|
49872
|
+
next[mapKey] = dependency;
|
|
49873
|
+
continue;
|
|
49874
|
+
}
|
|
49875
|
+
const child3 = normalizeSchema(ctx, dependency, { depth: depth + 1, rootSchema: false });
|
|
49876
|
+
const bad2 = hasUnsupported(child3);
|
|
49877
|
+
if (bad2) return unsupported(bad2);
|
|
49878
|
+
next[mapKey] = child3;
|
|
49879
|
+
}
|
|
49880
|
+
normalized.dependencies = next;
|
|
49881
|
+
continue;
|
|
49882
|
+
}
|
|
49783
49883
|
if (key === "properties") {
|
|
49784
49884
|
const properties = asRecord3(value);
|
|
49785
49885
|
if (!properties) continue;
|
|
49786
49886
|
const nextProperties = {};
|
|
49787
49887
|
for (const [propertyName, propertySchema] of Object.entries(properties)) {
|
|
49788
|
-
if (
|
|
49789
|
-
const child3 = normalizeSchema(
|
|
49888
|
+
if (strippedProperties.has(propertyName)) continue;
|
|
49889
|
+
const child3 = normalizeSchema(ctx, propertySchema, { depth: depth + 1, rootSchema: false });
|
|
49790
49890
|
const bad2 = hasUnsupported(child3);
|
|
49791
49891
|
if (bad2) return unsupported(bad2);
|
|
49792
49892
|
nextProperties[propertyName] = child3;
|
|
@@ -49795,16 +49895,46 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49795
49895
|
continue;
|
|
49796
49896
|
}
|
|
49797
49897
|
if (key === "required") {
|
|
49798
|
-
const required =
|
|
49898
|
+
const required = mergeRequiredWithoutStripped(value, strippedProperties);
|
|
49799
49899
|
if (required) normalized.required = required;
|
|
49800
49900
|
continue;
|
|
49801
49901
|
}
|
|
49802
|
-
const child2 = normalizeSchema(
|
|
49902
|
+
const child2 = normalizeSchema(ctx, value, { depth: depth + 1, rootSchema: false });
|
|
49803
49903
|
const bad = hasUnsupported(child2);
|
|
49804
49904
|
if (bad) return unsupported(bad);
|
|
49805
49905
|
normalized[key] = child2;
|
|
49806
49906
|
}
|
|
49807
|
-
if (
|
|
49907
|
+
if (typeof normalized.minimum === "number" && typeof normalized.exclusiveMinimum === "number") {
|
|
49908
|
+
if (normalized.exclusiveMinimum >= normalized.minimum) delete normalized.minimum;
|
|
49909
|
+
else delete normalized.exclusiveMinimum;
|
|
49910
|
+
}
|
|
49911
|
+
if (typeof normalized.maximum === "number" && typeof normalized.exclusiveMaximum === "number") {
|
|
49912
|
+
if (normalized.exclusiveMaximum <= normalized.maximum) delete normalized.maximum;
|
|
49913
|
+
else delete normalized.exclusiveMaximum;
|
|
49914
|
+
}
|
|
49915
|
+
if (sourceSchema.format === "int32") {
|
|
49916
|
+
const type2 = normalized.type;
|
|
49917
|
+
const isInteger2 = type2 === "integer" || Array.isArray(type2) && type2.includes("integer");
|
|
49918
|
+
if (isInteger2) {
|
|
49919
|
+
if (typeof normalized.exclusiveMinimum === "number") {
|
|
49920
|
+
if (normalized.exclusiveMinimum < INT32_MIN) {
|
|
49921
|
+
delete normalized.exclusiveMinimum;
|
|
49922
|
+
normalized.minimum = INT32_MIN;
|
|
49923
|
+
}
|
|
49924
|
+
} else if (typeof normalized.minimum !== "number" || normalized.minimum < INT32_MIN) {
|
|
49925
|
+
normalized.minimum = INT32_MIN;
|
|
49926
|
+
}
|
|
49927
|
+
if (typeof normalized.exclusiveMaximum === "number") {
|
|
49928
|
+
if (normalized.exclusiveMaximum > INT32_MAX) {
|
|
49929
|
+
delete normalized.exclusiveMaximum;
|
|
49930
|
+
normalized.maximum = INT32_MAX;
|
|
49931
|
+
}
|
|
49932
|
+
} else if (typeof normalized.maximum !== "number" || normalized.maximum > INT32_MAX) {
|
|
49933
|
+
normalized.maximum = INT32_MAX;
|
|
49934
|
+
}
|
|
49935
|
+
}
|
|
49936
|
+
}
|
|
49937
|
+
if (options.rootSchema) normalized.$schema = ctx.dialect;
|
|
49808
49938
|
if (nullable) {
|
|
49809
49939
|
if (typeof normalized.type === "string") {
|
|
49810
49940
|
normalized.type = [normalized.type, "null"];
|
|
@@ -49816,23 +49946,106 @@ function normalizeSchema(root, schema2, options) {
|
|
|
49816
49946
|
const wrappedSchema = { ...normalized };
|
|
49817
49947
|
delete wrappedSchema.$schema;
|
|
49818
49948
|
const wrapper = { anyOf: [{ type: "null" }, wrappedSchema] };
|
|
49819
|
-
if (options.rootSchema) wrapper.$schema =
|
|
49949
|
+
if (options.rootSchema) wrapper.$schema = ctx.dialect;
|
|
49820
49950
|
return wrapper;
|
|
49821
49951
|
}
|
|
49822
49952
|
}
|
|
49823
49953
|
return normalized;
|
|
49824
49954
|
}
|
|
49825
|
-
function
|
|
49955
|
+
function isSchemaGraphOverflow(packed) {
|
|
49956
|
+
return typeof packed.unsupported === "string" && packed.unsupported.startsWith("OpenAPI schema reference graph exceeded");
|
|
49957
|
+
}
|
|
49958
|
+
function packSchema(root, schema2, version, direction = "response") {
|
|
49826
49959
|
try {
|
|
49960
|
+
if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
|
|
49961
|
+
if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
|
|
49827
49962
|
const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
|
|
49828
|
-
const
|
|
49963
|
+
const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map() };
|
|
49964
|
+
const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
|
|
49829
49965
|
const message = hasUnsupported(normalized);
|
|
49830
|
-
|
|
49966
|
+
if (message) return unsupported(message);
|
|
49967
|
+
const aliasTargets = /* @__PURE__ */ new Map();
|
|
49968
|
+
for (const entry of ctx.defs.values()) {
|
|
49969
|
+
if (entry.unsupported) return unsupported(entry.unsupported);
|
|
49970
|
+
const entrySchema = asRecord3(entry.schema);
|
|
49971
|
+
const ref = entrySchema && Object.keys(entrySchema).length === 1 && typeof entrySchema.$ref === "string" ? entrySchema.$ref : "";
|
|
49972
|
+
if (ref.startsWith("#/$defs/")) aliasTargets.set(entry.name, ref.slice("#/$defs/".length));
|
|
49973
|
+
}
|
|
49974
|
+
for (const start of aliasTargets.keys()) {
|
|
49975
|
+
const seen = /* @__PURE__ */ new Set();
|
|
49976
|
+
let current = start;
|
|
49977
|
+
while (current !== void 0 && aliasTargets.has(current)) {
|
|
49978
|
+
if (seen.has(current)) return unsupported("Self-referential alias schema is unsupported");
|
|
49979
|
+
seen.add(current);
|
|
49980
|
+
current = aliasTargets.get(current);
|
|
49981
|
+
}
|
|
49982
|
+
}
|
|
49983
|
+
if (ctx.defs.size > 0) {
|
|
49984
|
+
const normalizedRecord = asRecord3(normalized);
|
|
49985
|
+
if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
|
|
49986
|
+
normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
|
|
49987
|
+
}
|
|
49988
|
+
return { schema: normalized };
|
|
49831
49989
|
} catch (error2) {
|
|
49832
49990
|
return unsupported(error2 instanceof Error ? error2.message : String(error2));
|
|
49833
49991
|
}
|
|
49834
49992
|
}
|
|
49835
49993
|
|
|
49994
|
+
// src/lib/spec/schema-validator-code.ts
|
|
49995
|
+
var import_schemasafe = __toESM(require_src(), 1);
|
|
49996
|
+
function compileSchemaValidator(schema2) {
|
|
49997
|
+
try {
|
|
49998
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
49999
|
+
includeErrors: false,
|
|
50000
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
50001
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
50002
|
+
// Plaid, and DigitalOcean public specs).
|
|
50003
|
+
allowUnusedKeywords: true,
|
|
50004
|
+
contentValidation: false,
|
|
50005
|
+
formatAssertion: true,
|
|
50006
|
+
isJSON: true,
|
|
50007
|
+
mode: "default",
|
|
50008
|
+
removeAdditional: false,
|
|
50009
|
+
requireSchema: true,
|
|
50010
|
+
requireStringValidation: false,
|
|
50011
|
+
useDefaults: false
|
|
50012
|
+
});
|
|
50013
|
+
return (value) => validate2(value);
|
|
50014
|
+
} catch {
|
|
50015
|
+
return null;
|
|
50016
|
+
}
|
|
50017
|
+
}
|
|
50018
|
+
function compileSchemaValidatorCode(schema2) {
|
|
50019
|
+
try {
|
|
50020
|
+
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
50021
|
+
includeErrors: true,
|
|
50022
|
+
allErrors: true,
|
|
50023
|
+
// Real-world specs legally mix enum/const with other keywords; without
|
|
50024
|
+
// this flag schemasafe refuses to compile them (observed in Stripe,
|
|
50025
|
+
// Plaid, and DigitalOcean public specs).
|
|
50026
|
+
allowUnusedKeywords: true,
|
|
50027
|
+
contentValidation: false,
|
|
50028
|
+
formatAssertion: true,
|
|
50029
|
+
isJSON: true,
|
|
50030
|
+
mode: "default",
|
|
50031
|
+
removeAdditional: false,
|
|
50032
|
+
requireSchema: true,
|
|
50033
|
+
requireStringValidation: false,
|
|
50034
|
+
useDefaults: false
|
|
50035
|
+
});
|
|
50036
|
+
const source = validate2.toModule();
|
|
50037
|
+
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
50038
|
+
throw new Error("schemasafe generated forbidden dynamic code");
|
|
50039
|
+
}
|
|
50040
|
+
return source;
|
|
50041
|
+
} catch (error2) {
|
|
50042
|
+
throw new Error(
|
|
50043
|
+
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
50044
|
+
{ cause: error2 }
|
|
50045
|
+
);
|
|
50046
|
+
}
|
|
50047
|
+
}
|
|
50048
|
+
|
|
49836
50049
|
// src/lib/spec/contract-index.ts
|
|
49837
50050
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
|
|
49838
50051
|
function asRecord4(value) {
|
|
@@ -49908,7 +50121,7 @@ function collectSecurityApiKeys(root, operation) {
|
|
|
49908
50121
|
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
49909
50122
|
for (const schemeName of Object.keys(requirement)) {
|
|
49910
50123
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
49911
|
-
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header"].includes(String(scheme.in))) {
|
|
50124
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header", "cookie"].includes(String(scheme.in))) {
|
|
49912
50125
|
names.add(`${String(scheme.in)}:${scheme.name.toLowerCase()}`);
|
|
49913
50126
|
}
|
|
49914
50127
|
}
|
|
@@ -49930,12 +50143,130 @@ function collectSecuritySchemeWarnings(root, operation) {
|
|
|
49930
50143
|
for (const schemeName of Object.keys(requirement)) {
|
|
49931
50144
|
const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
|
|
49932
50145
|
warnings.add(
|
|
49933
|
-
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven by dynamic contract tests`
|
|
50146
|
+
`CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven beyond credential presence by dynamic contract tests`
|
|
49934
50147
|
);
|
|
49935
50148
|
}
|
|
49936
50149
|
}
|
|
49937
50150
|
return [...warnings];
|
|
49938
50151
|
}
|
|
50152
|
+
function securityCheckFor(schemeName, scheme) {
|
|
50153
|
+
const kind = securitySchemeKind(scheme);
|
|
50154
|
+
if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["header", "query", "cookie"].includes(String(scheme.in))) {
|
|
50155
|
+
return { scheme: schemeName, kind, checkable: true, in: String(scheme.in), name: scheme.name };
|
|
50156
|
+
}
|
|
50157
|
+
if (scheme?.type === "http") {
|
|
50158
|
+
const httpScheme = String(scheme.scheme || "").toLowerCase();
|
|
50159
|
+
if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
|
|
50160
|
+
if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
|
|
50161
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
50162
|
+
}
|
|
50163
|
+
if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
|
|
50164
|
+
return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
|
|
50165
|
+
}
|
|
50166
|
+
return { scheme: schemeName, kind, checkable: false };
|
|
50167
|
+
}
|
|
50168
|
+
function collectSecurityRuntimeChecks(root, operation) {
|
|
50169
|
+
const securitySchemes = asRecord4(asRecord4(root.components)?.securitySchemes);
|
|
50170
|
+
const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
|
|
50171
|
+
const alternatives = [];
|
|
50172
|
+
for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
|
|
50173
|
+
const schemeNames = Object.keys(requirement);
|
|
50174
|
+
if (schemeNames.length === 0) return void 0;
|
|
50175
|
+
alternatives.push(schemeNames.map((schemeName) => securityCheckFor(schemeName, resolveInternalRef(root, securitySchemes?.[schemeName]))));
|
|
50176
|
+
}
|
|
50177
|
+
if (alternatives.some((alternative) => alternative.length > 0 && alternative.every((check) => !check.checkable))) return void 0;
|
|
50178
|
+
return alternatives.length > 0 ? alternatives : void 0;
|
|
50179
|
+
}
|
|
50180
|
+
function resolvedParameters(root, pathItem, operation) {
|
|
50181
|
+
return [...asArray2(pathItem.parameters), ...asArray2(operation.parameters)].map((rawParam) => {
|
|
50182
|
+
try {
|
|
50183
|
+
return resolveInternalRef(root, rawParam);
|
|
50184
|
+
} catch {
|
|
50185
|
+
return null;
|
|
50186
|
+
}
|
|
50187
|
+
}).filter((param) => Boolean(param));
|
|
50188
|
+
}
|
|
50189
|
+
var DEFAULT_PARAM_STYLES = { query: "form", path: "simple", header: "simple", cookie: "form" };
|
|
50190
|
+
var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
|
|
50191
|
+
function isIgnoredParameter(location2, name) {
|
|
50192
|
+
return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
|
|
50193
|
+
}
|
|
50194
|
+
function collectSerializationWarnings(root, pathItem, operation) {
|
|
50195
|
+
const warnings = [];
|
|
50196
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
50197
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
50198
|
+
const name = String(param.name || "");
|
|
50199
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
50200
|
+
if (!name || !defaultStyle || isIgnoredParameter(location2, name)) continue;
|
|
50201
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
50202
|
+
const defaultExplode = style === "form";
|
|
50203
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
50204
|
+
if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || param.content !== void 0) {
|
|
50205
|
+
warnings.push(`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: parameter ${location2}:${name} declares non-default style, explode, allowReserved, or content and its serialization is not validated`);
|
|
50206
|
+
}
|
|
50207
|
+
}
|
|
50208
|
+
return warnings;
|
|
50209
|
+
}
|
|
50210
|
+
var SCALAR_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
|
|
50211
|
+
function packedScalarSchema(packed) {
|
|
50212
|
+
if (packed.unsupported || packed.schema === void 0) return void 0;
|
|
50213
|
+
const record = asRecord4(packed.schema);
|
|
50214
|
+
if (!record) return void 0;
|
|
50215
|
+
const types2 = Array.isArray(record.type) ? record.type : [record.type];
|
|
50216
|
+
if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
|
|
50217
|
+
return packed.schema;
|
|
50218
|
+
}
|
|
50219
|
+
function collectParameterChecks(root, pathItem, operation, version, operationId, warnings) {
|
|
50220
|
+
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
50221
|
+
const checks = [];
|
|
50222
|
+
const seen = /* @__PURE__ */ new Set();
|
|
50223
|
+
const orderedParams = [...asArray2(operation.parameters), ...asArray2(pathItem.parameters)].map((rawParam) => {
|
|
50224
|
+
try {
|
|
50225
|
+
return resolveInternalRef(root, rawParam);
|
|
50226
|
+
} catch {
|
|
50227
|
+
return null;
|
|
50228
|
+
}
|
|
50229
|
+
}).filter((param) => Boolean(param));
|
|
50230
|
+
for (const param of orderedParams) {
|
|
50231
|
+
const location2 = String(param.in || "").toLowerCase();
|
|
50232
|
+
if (location2 !== "query" && location2 !== "header") continue;
|
|
50233
|
+
const name = String(param.name || "");
|
|
50234
|
+
if (!name || isIgnoredParameter(location2, name)) continue;
|
|
50235
|
+
const key = `${location2}:${name.toLowerCase()}`;
|
|
50236
|
+
if (seen.has(key)) continue;
|
|
50237
|
+
seen.add(key);
|
|
50238
|
+
if (securityKeys.has(key)) continue;
|
|
50239
|
+
if (param.content !== void 0 || param.schema === void 0) continue;
|
|
50240
|
+
const defaultStyle = DEFAULT_PARAM_STYLES[location2];
|
|
50241
|
+
const style = typeof param.style === "string" ? param.style : defaultStyle;
|
|
50242
|
+
const defaultExplode = style === "form";
|
|
50243
|
+
const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
|
|
50244
|
+
if (style !== defaultStyle || explode !== defaultExplode) continue;
|
|
50245
|
+
const packed = packSchema(root, param.schema, version);
|
|
50246
|
+
if (packed.unsupported) {
|
|
50247
|
+
warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
|
|
50248
|
+
continue;
|
|
50249
|
+
}
|
|
50250
|
+
const schema2 = packedScalarSchema(packed);
|
|
50251
|
+
if (schema2 === void 0) continue;
|
|
50252
|
+
const check = { in: location2, name, required: param.required === true, schema: schema2 };
|
|
50253
|
+
if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
|
|
50254
|
+
checks.push(check);
|
|
50255
|
+
}
|
|
50256
|
+
return checks.length > 0 ? checks : void 0;
|
|
50257
|
+
}
|
|
50258
|
+
function collectDeclaredQueryParameters(root, pathItem, operation) {
|
|
50259
|
+
const names = /* @__PURE__ */ new Set();
|
|
50260
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
50261
|
+
if (String(param.in || "").toLowerCase() !== "query") continue;
|
|
50262
|
+
const name = String(param.name || "");
|
|
50263
|
+
if (name) names.add(name.toLowerCase());
|
|
50264
|
+
}
|
|
50265
|
+
for (const key of collectSecurityApiKeys(root, operation)) {
|
|
50266
|
+
if (key.startsWith("query:")) names.add(key.slice("query:".length));
|
|
50267
|
+
}
|
|
50268
|
+
return [...names];
|
|
50269
|
+
}
|
|
49939
50270
|
function collectParameters(root, pathItem, operation) {
|
|
49940
50271
|
const securityKeys = collectSecurityApiKeys(root, operation);
|
|
49941
50272
|
const requirements = [];
|
|
@@ -49945,9 +50276,9 @@ function collectParameters(root, pathItem, operation) {
|
|
|
49945
50276
|
const param = resolveInternalRef(root, rawParam);
|
|
49946
50277
|
if (!param) continue;
|
|
49947
50278
|
const location2 = String(param.in || "").toLowerCase();
|
|
49948
|
-
if (!["path", "query", "header"].includes(location2)) continue;
|
|
50279
|
+
if (!["path", "query", "header", "cookie"].includes(location2)) continue;
|
|
49949
50280
|
const name = String(param.name || "");
|
|
49950
|
-
if (!name || param.required !== true) continue;
|
|
50281
|
+
if (!name || param.required !== true || isIgnoredParameter(location2, name)) continue;
|
|
49951
50282
|
const key = `${location2}:${name.toLowerCase()}`;
|
|
49952
50283
|
if (seen.has(key)) continue;
|
|
49953
50284
|
seen.add(key);
|
|
@@ -49959,36 +50290,226 @@ function collectParameters(root, pathItem, operation) {
|
|
|
49959
50290
|
}
|
|
49960
50291
|
return requirements;
|
|
49961
50292
|
}
|
|
49962
|
-
|
|
50293
|
+
var BODY_FIELD_RULE_TYPES = /* @__PURE__ */ new Set(["application/x-www-form-urlencoded", "multipart/form-data"]);
|
|
50294
|
+
function isJsonBaseType(base) {
|
|
50295
|
+
return base === "application/json" || /\+json$/.test(base);
|
|
50296
|
+
}
|
|
50297
|
+
function mergeObjectSchema(root, rawSchema, depth) {
|
|
50298
|
+
if (depth > 10) return null;
|
|
50299
|
+
let schema2;
|
|
50300
|
+
try {
|
|
50301
|
+
schema2 = resolveInternalRef(root, rawSchema);
|
|
50302
|
+
} catch {
|
|
50303
|
+
return null;
|
|
50304
|
+
}
|
|
50305
|
+
if (!schema2) return null;
|
|
50306
|
+
const merged = {
|
|
50307
|
+
required: asArray2(schema2.required).map((entry) => String(entry)).filter(Boolean),
|
|
50308
|
+
properties: { ...asRecord4(schema2.properties) ?? {} }
|
|
50309
|
+
};
|
|
50310
|
+
for (const member of asArray2(schema2.allOf)) {
|
|
50311
|
+
const child2 = mergeObjectSchema(root, member, depth + 1);
|
|
50312
|
+
if (!child2) continue;
|
|
50313
|
+
merged.required.push(...child2.required);
|
|
50314
|
+
merged.properties = { ...merged.properties, ...child2.properties };
|
|
50315
|
+
}
|
|
50316
|
+
merged.required = [...new Set(merged.required)];
|
|
50317
|
+
return merged;
|
|
50318
|
+
}
|
|
50319
|
+
function propertyIsReadOnly(root, properties, name) {
|
|
50320
|
+
try {
|
|
50321
|
+
return resolveInternalRef(root, properties[name])?.readOnly === true;
|
|
50322
|
+
} catch {
|
|
50323
|
+
return false;
|
|
50324
|
+
}
|
|
50325
|
+
}
|
|
50326
|
+
function propertyIsBinary(root, properties, name) {
|
|
50327
|
+
let schema2;
|
|
50328
|
+
try {
|
|
50329
|
+
schema2 = resolveInternalRef(root, properties[name]);
|
|
50330
|
+
} catch {
|
|
50331
|
+
return false;
|
|
50332
|
+
}
|
|
50333
|
+
if (!schema2) return false;
|
|
50334
|
+
if (schema2.format === "binary") return true;
|
|
50335
|
+
return typeof schema2.contentMediaType === "string" && schema2.contentMediaType.toLowerCase().startsWith("application/octet-stream");
|
|
50336
|
+
}
|
|
50337
|
+
function fieldEncodings(root, base, mediaObject, properties) {
|
|
50338
|
+
const declared = asRecord4(mediaObject?.encoding);
|
|
50339
|
+
const encodings = {};
|
|
50340
|
+
for (const name of Object.keys(properties)) {
|
|
50341
|
+
if (base === "multipart/form-data" && propertyIsBinary(root, properties, name)) {
|
|
50342
|
+
encodings[name] = { ...encodings[name], binary: true };
|
|
50343
|
+
}
|
|
50344
|
+
}
|
|
50345
|
+
if (declared) {
|
|
50346
|
+
for (const [name, rawEncoding] of Object.entries(declared)) {
|
|
50347
|
+
const encoding = asRecord4(rawEncoding);
|
|
50348
|
+
if (!encoding) continue;
|
|
50349
|
+
const entry = { ...encodings[name] };
|
|
50350
|
+
if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
|
|
50351
|
+
entry.contentType = encoding.contentType.toLowerCase();
|
|
50352
|
+
}
|
|
50353
|
+
if (base === "application/x-www-form-urlencoded") {
|
|
50354
|
+
const style = typeof encoding.style === "string" ? encoding.style : "form";
|
|
50355
|
+
const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
|
|
50356
|
+
if (style !== "form" || explode !== (style === "form") || encoding.allowReserved === true) {
|
|
50357
|
+
entry.nonDefaultSerialization = true;
|
|
50358
|
+
}
|
|
50359
|
+
}
|
|
50360
|
+
if (Object.keys(entry).length > 0) encodings[name] = entry;
|
|
50361
|
+
}
|
|
50362
|
+
}
|
|
50363
|
+
return Object.keys(encodings).length > 0 ? encodings : void 0;
|
|
50364
|
+
}
|
|
50365
|
+
function requestBodyFieldRules(root, content) {
|
|
50366
|
+
const rules = {};
|
|
50367
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
50368
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50369
|
+
if (!isJsonBaseType(base) && !BODY_FIELD_RULE_TYPES.has(base)) continue;
|
|
50370
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
50371
|
+
const merged = mergeObjectSchema(root, mediaRecord?.schema, 0);
|
|
50372
|
+
if (!merged) continue;
|
|
50373
|
+
const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
|
|
50374
|
+
const required = merged.required.filter((name) => !propertyIsReadOnly(root, merged.properties, name));
|
|
50375
|
+
const encodings = BODY_FIELD_RULE_TYPES.has(base) ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
|
|
50376
|
+
if (required.length > 0 || readOnly.length > 0 || encodings) {
|
|
50377
|
+
const rule = { required, readOnly };
|
|
50378
|
+
if (encodings) rule.encodings = encodings;
|
|
50379
|
+
rules[base] = rule;
|
|
50380
|
+
}
|
|
50381
|
+
}
|
|
50382
|
+
return Object.keys(rules).length > 0 ? rules : void 0;
|
|
50383
|
+
}
|
|
50384
|
+
function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
|
|
50385
|
+
const schemas = {};
|
|
50386
|
+
const exampleWarnings = /* @__PURE__ */ new Set();
|
|
50387
|
+
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
50388
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50389
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
50390
|
+
const schema2 = mediaRecord?.schema;
|
|
50391
|
+
if (!isJsonBaseType(base)) {
|
|
50392
|
+
if (schema2 !== void 0 && !BODY_FIELD_RULE_TYPES.has(base)) {
|
|
50393
|
+
warnings.push(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated at runtime`);
|
|
50394
|
+
}
|
|
50395
|
+
continue;
|
|
50396
|
+
}
|
|
50397
|
+
if (schema2 === void 0) continue;
|
|
50398
|
+
const packed = packSchema(root, schema2, version, "request");
|
|
50399
|
+
if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
|
|
50400
|
+
if (packed.unsupported) {
|
|
50401
|
+
warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
|
|
50402
|
+
continue;
|
|
50403
|
+
}
|
|
50404
|
+
if (packed.schema !== void 0) schemas[base] = packed.schema;
|
|
50405
|
+
}
|
|
50406
|
+
warnings.push(...exampleWarnings);
|
|
50407
|
+
return Object.keys(schemas).length > 0 ? schemas : void 0;
|
|
50408
|
+
}
|
|
50409
|
+
function collectRequestBody(root, operation, version, operationId, warnings) {
|
|
49963
50410
|
const body = resolveInternalRef(root, operation.requestBody);
|
|
49964
|
-
if (!body
|
|
50411
|
+
if (!body) return void 0;
|
|
49965
50412
|
const content = asRecord4(body.content);
|
|
50413
|
+
const fieldRules = content ? requestBodyFieldRules(root, content) : void 0;
|
|
50414
|
+
if (fieldRules) {
|
|
50415
|
+
for (const [base, rule] of Object.entries(fieldRules)) {
|
|
50416
|
+
for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
|
|
50417
|
+
if (encoding.nonDefaultSerialization) {
|
|
50418
|
+
warnings.push(
|
|
50419
|
+
`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`
|
|
50420
|
+
);
|
|
50421
|
+
}
|
|
50422
|
+
}
|
|
50423
|
+
}
|
|
50424
|
+
}
|
|
49966
50425
|
return {
|
|
49967
|
-
required: true,
|
|
49968
|
-
contentTypes: content ? Object.keys(content) : []
|
|
50426
|
+
required: body.required === true,
|
|
50427
|
+
contentTypes: content ? Object.keys(content) : [],
|
|
50428
|
+
fieldRules,
|
|
50429
|
+
jsonSchemas: content ? requestBodyJsonSchemas(root, content, version, operationId, warnings) : void 0
|
|
49969
50430
|
};
|
|
49970
50431
|
}
|
|
49971
|
-
function
|
|
50432
|
+
function exampleCandidates(root, mediaObject) {
|
|
50433
|
+
const candidates = [];
|
|
50434
|
+
if ("example" in mediaObject) candidates.push({ label: "example", value: mediaObject.example });
|
|
50435
|
+
const examples = asRecord4(mediaObject.examples);
|
|
50436
|
+
if (examples) {
|
|
50437
|
+
for (const [name, rawExample] of Object.entries(examples)) {
|
|
50438
|
+
let example;
|
|
50439
|
+
try {
|
|
50440
|
+
example = resolveInternalRef(root, rawExample);
|
|
50441
|
+
} catch {
|
|
50442
|
+
continue;
|
|
50443
|
+
}
|
|
50444
|
+
if (example && "value" in example) candidates.push({ label: `examples.${name}`, value: example.value });
|
|
50445
|
+
}
|
|
50446
|
+
}
|
|
50447
|
+
return candidates;
|
|
50448
|
+
}
|
|
50449
|
+
function validateExamples(root, mediaObject, packed, contentType, context, warnings) {
|
|
50450
|
+
if (packed.schema === void 0 || packed.unsupported) return;
|
|
50451
|
+
const candidates = exampleCandidates(root, mediaObject);
|
|
50452
|
+
if (candidates.length === 0) return;
|
|
50453
|
+
const validate2 = compileSchemaValidator(packed.schema);
|
|
50454
|
+
if (!validate2) return;
|
|
50455
|
+
for (const candidate of candidates) {
|
|
50456
|
+
if (!validate2(candidate.value)) {
|
|
50457
|
+
warnings.add(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for ${contentType} on ${context} does not match its schema`);
|
|
50458
|
+
}
|
|
50459
|
+
}
|
|
50460
|
+
}
|
|
50461
|
+
function responseContent(root, version, response, context, warnings) {
|
|
49972
50462
|
const content = asRecord4(response.content);
|
|
49973
50463
|
if (!content) return {};
|
|
49974
50464
|
const media = {};
|
|
49975
50465
|
for (const [contentType, mediaObject] of Object.entries(content)) {
|
|
49976
|
-
const
|
|
49977
|
-
|
|
50466
|
+
const mediaRecord = asRecord4(mediaObject);
|
|
50467
|
+
const schema2 = mediaRecord?.schema;
|
|
50468
|
+
let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
|
|
50469
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
50470
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
|
|
50471
|
+
packed = {};
|
|
50472
|
+
}
|
|
50473
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50474
|
+
if (mediaRecord && isJsonBaseType(base)) validateExamples(root, mediaRecord, packed, contentType, context, warnings);
|
|
50475
|
+
media[contentType] = packed;
|
|
49978
50476
|
}
|
|
49979
50477
|
return media;
|
|
49980
50478
|
}
|
|
49981
|
-
function responseHeaders(root, version, response) {
|
|
50479
|
+
function responseHeaders(root, version, response, context, warnings) {
|
|
49982
50480
|
const headers = asRecord4(response.headers);
|
|
49983
50481
|
if (!headers) return [];
|
|
49984
|
-
|
|
50482
|
+
const entries = [];
|
|
50483
|
+
for (const [name, rawHeader] of Object.entries(headers)) {
|
|
50484
|
+
if (name.toLowerCase() === "content-type") continue;
|
|
49985
50485
|
const header = resolveInternalRef(root, rawHeader);
|
|
49986
|
-
if (!header)
|
|
50486
|
+
if (!header) {
|
|
50487
|
+
entries.push({ name, required: true, unsupported: "Unresolved response header" });
|
|
50488
|
+
continue;
|
|
50489
|
+
}
|
|
49987
50490
|
const required = header.required === true;
|
|
49988
|
-
if (header.content)
|
|
49989
|
-
|
|
49990
|
-
|
|
49991
|
-
|
|
50491
|
+
if (header.content) {
|
|
50492
|
+
entries.push({ name, required, unsupported: "OpenAPI response header content is unsupported" });
|
|
50493
|
+
continue;
|
|
50494
|
+
}
|
|
50495
|
+
if (!header.schema) {
|
|
50496
|
+
entries.push({ name, required });
|
|
50497
|
+
continue;
|
|
50498
|
+
}
|
|
50499
|
+
const packed = packSchema(root, header.schema, version);
|
|
50500
|
+
if (isSchemaGraphOverflow(packed)) {
|
|
50501
|
+
warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
|
|
50502
|
+
entries.push({ name, required });
|
|
50503
|
+
continue;
|
|
50504
|
+
}
|
|
50505
|
+
if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
|
|
50506
|
+
warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
|
|
50507
|
+
entries.push({ name, required });
|
|
50508
|
+
continue;
|
|
50509
|
+
}
|
|
50510
|
+
entries.push({ name, required, ...packed });
|
|
50511
|
+
}
|
|
50512
|
+
return entries;
|
|
49992
50513
|
}
|
|
49993
50514
|
function buildContractIndex(root) {
|
|
49994
50515
|
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)");
|
|
@@ -50013,11 +50534,23 @@ function buildContractIndex(root) {
|
|
|
50013
50534
|
throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path8} must define at least one response`);
|
|
50014
50535
|
}
|
|
50015
50536
|
const contractResponses = {};
|
|
50537
|
+
const responseWarnings = /* @__PURE__ */ new Set();
|
|
50016
50538
|
for (const [status, rawResponse] of Object.entries(responses)) {
|
|
50017
50539
|
const response = resolveInternalRef(root, rawResponse);
|
|
50018
50540
|
if (!response) continue;
|
|
50019
|
-
|
|
50020
|
-
|
|
50541
|
+
if (asRecord4(response.links)) {
|
|
50542
|
+
responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path8}`);
|
|
50543
|
+
}
|
|
50544
|
+
const responseContext = `${lowerMethod.toUpperCase()} ${path8} status ${status}`;
|
|
50545
|
+
const content = responseContent(root, version, response, responseContext, responseWarnings);
|
|
50546
|
+
for (const [contentType, media] of Object.entries(content)) {
|
|
50547
|
+
const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
|
|
50548
|
+
const schemaType = asRecord4(media.schema)?.type;
|
|
50549
|
+
if (!isJsonBaseType(base) && media.schema !== void 0 && !media.unsupported && schemaType !== "string") {
|
|
50550
|
+
responseWarnings.add(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: response schema for ${contentType} on ${responseContext} is not validated at runtime`);
|
|
50551
|
+
}
|
|
50552
|
+
}
|
|
50553
|
+
const headers = responseHeaders(root, version, response, responseContext, responseWarnings);
|
|
50021
50554
|
contractResponses[normalizeResponseKey(status)] = {
|
|
50022
50555
|
content,
|
|
50023
50556
|
hasBody: Object.keys(content).length > 0,
|
|
@@ -50029,11 +50562,26 @@ function buildContractIndex(root) {
|
|
|
50029
50562
|
...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path8))
|
|
50030
50563
|
].map(normalizePath))];
|
|
50031
50564
|
const opWarnings = [];
|
|
50565
|
+
opWarnings.push(...responseWarnings);
|
|
50032
50566
|
opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
|
|
50567
|
+
opWarnings.push(...collectSerializationWarnings(root, pathItem, operation));
|
|
50568
|
+
if (operation.deprecated === true) {
|
|
50569
|
+
opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path8} is marked deprecated in the OpenAPI document`);
|
|
50570
|
+
}
|
|
50033
50571
|
const requiredParameters = collectParameters(root, pathItem, operation);
|
|
50034
50572
|
for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
|
|
50035
50573
|
opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
|
|
50036
50574
|
}
|
|
50575
|
+
for (const parameter of requiredParameters.filter((entry) => entry.in === "cookie" && !entry.securityDerived)) {
|
|
50576
|
+
opWarnings.push(`CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not statically required in generated requests`);
|
|
50577
|
+
}
|
|
50578
|
+
const pathParamWarnings = /* @__PURE__ */ new Set();
|
|
50579
|
+
for (const param of resolvedParameters(root, pathItem, operation)) {
|
|
50580
|
+
if (String(param.in || "").toLowerCase() !== "path") continue;
|
|
50581
|
+
const name = String(param.name || "");
|
|
50582
|
+
if (name) pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
|
|
50583
|
+
}
|
|
50584
|
+
opWarnings.push(...pathParamWarnings);
|
|
50037
50585
|
operations.push({
|
|
50038
50586
|
id: `${lowerMethod.toUpperCase()} ${path8}`,
|
|
50039
50587
|
method: lowerMethod.toUpperCase(),
|
|
@@ -50042,7 +50590,10 @@ function buildContractIndex(root) {
|
|
|
50042
50590
|
candidates,
|
|
50043
50591
|
responses: contractResponses,
|
|
50044
50592
|
requiredParameters,
|
|
50045
|
-
|
|
50593
|
+
declaredQueryParameters: collectDeclaredQueryParameters(root, pathItem, operation),
|
|
50594
|
+
parameterChecks: collectParameterChecks(root, pathItem, operation, version, `${lowerMethod.toUpperCase()} ${path8}`, opWarnings),
|
|
50595
|
+
requestBody: collectRequestBody(root, operation, version, `${lowerMethod.toUpperCase()} ${path8}`, opWarnings),
|
|
50596
|
+
security: collectSecurityRuntimeChecks(root, operation),
|
|
50046
50597
|
warnings: opWarnings
|
|
50047
50598
|
});
|
|
50048
50599
|
}
|
|
@@ -50063,35 +50614,6 @@ function buildContractIndex(root) {
|
|
|
50063
50614
|
return { operations, version, warnings };
|
|
50064
50615
|
}
|
|
50065
50616
|
|
|
50066
|
-
// src/lib/spec/schema-validator-code.ts
|
|
50067
|
-
var import_schemasafe = __toESM(require_src(), 1);
|
|
50068
|
-
function compileSchemaValidatorCode(schema2) {
|
|
50069
|
-
try {
|
|
50070
|
-
const validate2 = (0, import_schemasafe.validator)(schema2, {
|
|
50071
|
-
includeErrors: true,
|
|
50072
|
-
allErrors: true,
|
|
50073
|
-
contentValidation: false,
|
|
50074
|
-
formatAssertion: true,
|
|
50075
|
-
isJSON: true,
|
|
50076
|
-
mode: "default",
|
|
50077
|
-
removeAdditional: false,
|
|
50078
|
-
requireSchema: true,
|
|
50079
|
-
requireStringValidation: false,
|
|
50080
|
-
useDefaults: false
|
|
50081
|
-
});
|
|
50082
|
-
const source = validate2.toModule();
|
|
50083
|
-
if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
|
|
50084
|
-
throw new Error("schemasafe generated forbidden dynamic code");
|
|
50085
|
-
}
|
|
50086
|
-
return source;
|
|
50087
|
-
} catch (error2) {
|
|
50088
|
-
throw new Error(
|
|
50089
|
-
`CONTRACT_SCHEMA_COMPILE_FAILED: ${error2 instanceof Error ? error2.message : String(error2)}`,
|
|
50090
|
-
{ cause: error2 }
|
|
50091
|
-
);
|
|
50092
|
-
}
|
|
50093
|
-
}
|
|
50094
|
-
|
|
50095
50617
|
// src/lib/spec/collection-contracts.ts
|
|
50096
50618
|
var CONTRACT_SIZE_LIMITS = {
|
|
50097
50619
|
warnTestScriptBytes: 256e3,
|
|
@@ -50180,29 +50702,51 @@ function matchOperation(index, request) {
|
|
|
50180
50702
|
function assignValidator(lines, target, source) {
|
|
50181
50703
|
lines.push(`${target} = ${source};`);
|
|
50182
50704
|
}
|
|
50183
|
-
function
|
|
50705
|
+
function tryCompile(target, schema2, lines, warnings, context) {
|
|
50706
|
+
try {
|
|
50707
|
+
assignValidator(lines, target, compileSchemaValidatorCode(schema2));
|
|
50708
|
+
} catch (error2) {
|
|
50709
|
+
lines.push(`${target} = { skip: true };`);
|
|
50710
|
+
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)})`);
|
|
50711
|
+
}
|
|
50712
|
+
}
|
|
50713
|
+
function buildValidatorAssignments(operation, warnings) {
|
|
50184
50714
|
const lines = ["var validators = {};"];
|
|
50715
|
+
const parameterChecks = operation.parameterChecks ?? [];
|
|
50716
|
+
if (parameterChecks.length > 0) {
|
|
50717
|
+
lines.push("var paramValidators = {};");
|
|
50718
|
+
for (const check of parameterChecks) {
|
|
50719
|
+
tryCompile(`paramValidators[${JSON.stringify(`${check.in}:${check.name.toLowerCase()}`)}]`, check.schema, lines, warnings, `parameter ${check.in}:${check.name} schema on ${operation.id}`);
|
|
50720
|
+
}
|
|
50721
|
+
}
|
|
50722
|
+
const bodySchemas = Object.entries(operation.requestBody?.jsonSchemas ?? {});
|
|
50723
|
+
if (bodySchemas.length > 0) {
|
|
50724
|
+
lines.push("var requestBodyValidators = {};");
|
|
50725
|
+
for (const [base, schema2] of bodySchemas) {
|
|
50726
|
+
tryCompile(`requestBodyValidators[${JSON.stringify(base)}]`, schema2, lines, warnings, `request body schema for ${base} on ${operation.id}`);
|
|
50727
|
+
}
|
|
50728
|
+
}
|
|
50185
50729
|
for (const [status, response] of Object.entries(operation.responses)) {
|
|
50186
50730
|
lines.push(`validators[${JSON.stringify(status)}] = validators[${JSON.stringify(status)}] || {};`);
|
|
50187
50731
|
for (const [mediaType, media] of Object.entries(response.content)) {
|
|
50188
50732
|
if (media.schema !== void 0 && !media.unsupported) {
|
|
50189
|
-
|
|
50733
|
+
tryCompile(`validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, media.schema, lines, warnings, `response schema for ${mediaType} on ${operation.id} status ${status}`);
|
|
50190
50734
|
}
|
|
50191
50735
|
}
|
|
50192
50736
|
for (const header of response.headers) {
|
|
50193
50737
|
if (header.schema !== void 0 && !header.unsupported) {
|
|
50194
50738
|
lines.push(`validators[${JSON.stringify(status)}].__headers = validators[${JSON.stringify(status)}].__headers || {};`);
|
|
50195
|
-
|
|
50739
|
+
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}`);
|
|
50196
50740
|
}
|
|
50197
50741
|
}
|
|
50198
50742
|
}
|
|
50199
50743
|
return lines;
|
|
50200
50744
|
}
|
|
50201
|
-
function createContractScript(operation) {
|
|
50202
|
-
const contract = { method: operation.method, path: operation.path, responses: operation.responses };
|
|
50745
|
+
function createContractScript(operation, warnings = []) {
|
|
50746
|
+
const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks };
|
|
50203
50747
|
return [
|
|
50204
50748
|
`var contract = JSON.parse(${JSON.stringify(JSON.stringify(contract))});`,
|
|
50205
|
-
...buildValidatorAssignments(operation),
|
|
50749
|
+
...buildValidatorAssignments(operation, warnings),
|
|
50206
50750
|
"function selectedResponseContract() {",
|
|
50207
50751
|
" var status = String(pm.response.code);",
|
|
50208
50752
|
" if (contract.responses[status]) return { key: status, value: contract.responses[status] };",
|
|
@@ -50216,6 +50760,15 @@ function createContractScript(operation) {
|
|
|
50216
50760
|
'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
|
|
50217
50761
|
'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
|
|
50218
50762
|
'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
|
|
50763
|
+
"function coerceBySchema(value, schema) {",
|
|
50764
|
+
" var type = schema && schema.type;",
|
|
50765
|
+
" var types = Array.isArray(type) ? type : [type];",
|
|
50766
|
+
' if ((types.indexOf("integer") !== -1 || types.indexOf("number") !== -1) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(String(value).trim())) return Number(value);',
|
|
50767
|
+
' if (types.indexOf("boolean") !== -1 && (value === "true" || value === "false")) return value === "true";',
|
|
50768
|
+
" return value;",
|
|
50769
|
+
"}",
|
|
50770
|
+
'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; }',
|
|
50771
|
+
"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; }",
|
|
50219
50772
|
"function mediaScore(expected, actual) {",
|
|
50220
50773
|
" var e = mediaParts(expected); var a = mediaParts(actual);",
|
|
50221
50774
|
" if (!e.raw || !a.raw) return 0;",
|
|
@@ -50246,7 +50799,8 @@ function createContractScript(operation) {
|
|
|
50246
50799
|
" if (!actual) return;",
|
|
50247
50800
|
' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
|
|
50248
50801
|
" var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
|
|
50249
|
-
|
|
50802
|
+
" if (headerValidator && headerValidator.skip) return;",
|
|
50803
|
+
' if (headerValidator && !headerValidator(coerceBySchema(actual, header.schema))) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
|
|
50250
50804
|
" });",
|
|
50251
50805
|
"});",
|
|
50252
50806
|
"pm.test('Response body matches OpenAPI body contract', function () {",
|
|
@@ -50274,11 +50828,75 @@ function createContractScript(operation) {
|
|
|
50274
50828
|
' if (media.media.unsupported) pm.expect.fail("OpenAPI schema unsupported for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + media.media.unsupported);',
|
|
50275
50829
|
" if (!media.media.schema) { return; }",
|
|
50276
50830
|
" var validate = validators[selected.key] && validators[selected.key][media.expected];",
|
|
50831
|
+
" if (validate && validate.skip) return;",
|
|
50277
50832
|
' if (!validate) pm.expect.fail("OpenAPI schema validator was not generated for " + media.expected);',
|
|
50278
50833
|
' var actual = mediaParts(pm.response.headers.get("Content-Type") || "");',
|
|
50279
50834
|
" var value = isJsonSubtype(actual.subtype) ? pm.response.json() : responseText();",
|
|
50280
|
-
|
|
50835
|
+
// Non-JSON object-schema bodies skip schema validation instead of
|
|
50836
|
+
// failing; the index emits CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED so the
|
|
50837
|
+
// skip is visible at instrumentation time.
|
|
50838
|
+
' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") { return; }',
|
|
50281
50839
|
' if (!validate(value)) pm.expect.fail("OpenAPI schema validation failed for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + JSON.stringify(validate.errors || []));',
|
|
50840
|
+
"});",
|
|
50841
|
+
...operation.security ? [
|
|
50842
|
+
"pm.test('Request carries credentials required by OpenAPI security', function () {",
|
|
50843
|
+
" function satisfied(check) {",
|
|
50844
|
+
" if (!check.checkable) return true;",
|
|
50845
|
+
' if (check.in === "query") return hasQueryParam(check.name);',
|
|
50846
|
+
' 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); }); }',
|
|
50847
|
+
' if (check.prefix) { return requestHeader("Authorization").toLowerCase().indexOf(check.prefix.toLowerCase()) === 0; }',
|
|
50848
|
+
' if (check.kind === "oauth2" || check.kind === "openIdConnect") { return Boolean(requestHeader("Authorization")) || hasQueryParam("access_token"); }',
|
|
50849
|
+
" return Boolean(requestHeader(check.name));",
|
|
50850
|
+
" }",
|
|
50851
|
+
" var alternatives = contract.security || [];",
|
|
50852
|
+
" var ok = alternatives.some(function (alternative) { return alternative.every(function (check) { return satisfied(check); }); });",
|
|
50853
|
+
' 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(" | "));',
|
|
50854
|
+
"});"
|
|
50855
|
+
] : [],
|
|
50856
|
+
...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
|
|
50857
|
+
"pm.test('Request parameters match OpenAPI schemas', function () {",
|
|
50858
|
+
' 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; }',
|
|
50859
|
+
' 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; }',
|
|
50860
|
+
' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
|
|
50861
|
+
" (contract.parameters || []).forEach(function (param) {",
|
|
50862
|
+
' var key = param.in + ":" + String(param.name).toLowerCase();',
|
|
50863
|
+
" var validate = paramValidators[key];",
|
|
50864
|
+
" if (!validate || validate.skip) return;",
|
|
50865
|
+
' var value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
|
|
50866
|
+
' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
|
|
50867
|
+
' if (value === "" && param.allowEmptyValue) return;',
|
|
50868
|
+
" if (isPlaceholder(value)) return;",
|
|
50869
|
+
' if (!validate(coerceBySchema(value, param.schema))) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
50870
|
+
" });",
|
|
50871
|
+
"});"
|
|
50872
|
+
] : [],
|
|
50873
|
+
...operation.requestBody?.jsonSchemas && Object.keys(operation.requestBody.jsonSchemas).length > 0 ? [
|
|
50874
|
+
"pm.test('Request body matches OpenAPI request schema', function () {",
|
|
50875
|
+
" var body = pm.request.body;",
|
|
50876
|
+
' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
|
|
50877
|
+
" if (!raw.trim()) return;",
|
|
50878
|
+
' if (/"<[^"<>]*>"/.test(raw) || raw.indexOf("{{") !== -1) return;',
|
|
50879
|
+
' var validate = requestBodyValidators[mediaBase(requestHeader("Content-Type"))];',
|
|
50880
|
+
" if (!validate || validate.skip) return;",
|
|
50881
|
+
" var parsed;",
|
|
50882
|
+
// Unquoted generator placeholders such as {"count": <long>} break
|
|
50883
|
+
// JSON.parse; a parse failure alongside an angle-bracket token is
|
|
50884
|
+
// treated as a placeholder body rather than drift.
|
|
50885
|
+
' 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; }',
|
|
50886
|
+
' if (!validate(parsed)) pm.expect.fail("Request body failed OpenAPI request schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
|
|
50887
|
+
"});"
|
|
50888
|
+
] : [],
|
|
50889
|
+
"pm.test('Content-Length is consistent with OpenAPI body expectations', function () {",
|
|
50890
|
+
' var raw = pm.response.headers.get("Content-Length");',
|
|
50891
|
+
" if (raw === null || raw === undefined) return;",
|
|
50892
|
+
// Combined duplicate Content-Length values ("5, 5") fail the integer
|
|
50893
|
+
// syntax check on purpose: RFC 9110 tolerates identical repeats, but a
|
|
50894
|
+
// contract test surfacing them is strict by design.
|
|
50895
|
+
' if (!/^[0-9]+$/.test(String(raw).trim())) pm.expect.fail("Content-Length header is not a non-negative integer: " + raw);',
|
|
50896
|
+
' if (contract.method === "HEAD" || pm.response.code === 304) return;',
|
|
50897
|
+
' if (pm.response.headers.get("Content-Encoding")) return;',
|
|
50898
|
+
" var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
|
|
50899
|
+
' 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);',
|
|
50282
50900
|
"});"
|
|
50283
50901
|
];
|
|
50284
50902
|
}
|
|
@@ -50379,6 +50997,87 @@ function assertStaticRequestShape(operation, request) {
|
|
|
50379
50997
|
);
|
|
50380
50998
|
}
|
|
50381
50999
|
}
|
|
51000
|
+
return collectStaticBodyWarnings(operation, request, contentType);
|
|
51001
|
+
}
|
|
51002
|
+
function requestBodyFieldNames(request, base) {
|
|
51003
|
+
const body = asRecord5(request.body);
|
|
51004
|
+
if (!body) return void 0;
|
|
51005
|
+
if (base === "application/json" || /\+json$/.test(base)) {
|
|
51006
|
+
if (body.mode !== "raw" || typeof body.raw !== "string") return void 0;
|
|
51007
|
+
let parsed;
|
|
51008
|
+
try {
|
|
51009
|
+
parsed = JSON.parse(body.raw);
|
|
51010
|
+
} catch {
|
|
51011
|
+
return void 0;
|
|
51012
|
+
}
|
|
51013
|
+
const record = asRecord5(parsed);
|
|
51014
|
+
return record ? Object.keys(record) : void 0;
|
|
51015
|
+
}
|
|
51016
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
51017
|
+
if (!mode || !Array.isArray(body[mode])) return void 0;
|
|
51018
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true).map((entry) => String(entry.key || "")).filter(Boolean);
|
|
51019
|
+
}
|
|
51020
|
+
function requestBodyEntries(request, base) {
|
|
51021
|
+
const body = asRecord5(request.body);
|
|
51022
|
+
const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
|
|
51023
|
+
if (!body || !mode || !Array.isArray(body[mode])) return void 0;
|
|
51024
|
+
return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true);
|
|
51025
|
+
}
|
|
51026
|
+
function mediaTypeMatchesPattern(pattern, actual) {
|
|
51027
|
+
return pattern.split(",").some((candidate) => {
|
|
51028
|
+
const entry = (candidate.split(";")[0] ?? "").trim();
|
|
51029
|
+
if (!entry) return false;
|
|
51030
|
+
if (entry === "*/*" || entry === actual) return true;
|
|
51031
|
+
if (entry.endsWith("/*")) return actual.startsWith(entry.slice(0, -1));
|
|
51032
|
+
return false;
|
|
51033
|
+
});
|
|
51034
|
+
}
|
|
51035
|
+
function collectStaticEncodingWarnings(operation, request, base, rule) {
|
|
51036
|
+
const encodings = rule.encodings;
|
|
51037
|
+
if (!encodings || base !== "multipart/form-data") return [];
|
|
51038
|
+
const entries = requestBodyEntries(request, base);
|
|
51039
|
+
if (!entries) return [];
|
|
51040
|
+
const warnings = [];
|
|
51041
|
+
for (const [field, encoding] of Object.entries(encodings)) {
|
|
51042
|
+
const entry = entries.find((candidate) => String(candidate.key || "") === field);
|
|
51043
|
+
if (!entry) continue;
|
|
51044
|
+
if (encoding.binary && String(entry.type || "") !== "file") {
|
|
51045
|
+
warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
|
|
51046
|
+
}
|
|
51047
|
+
if (encoding.contentType) {
|
|
51048
|
+
const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
|
|
51049
|
+
if (!actual) {
|
|
51050
|
+
warnings.push(
|
|
51051
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
|
|
51052
|
+
);
|
|
51053
|
+
} else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
|
|
51054
|
+
warnings.push(
|
|
51055
|
+
`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
|
|
51056
|
+
);
|
|
51057
|
+
}
|
|
51058
|
+
}
|
|
51059
|
+
}
|
|
51060
|
+
return warnings;
|
|
51061
|
+
}
|
|
51062
|
+
function collectStaticBodyWarnings(operation, request, contentType) {
|
|
51063
|
+
const rules = operation.requestBody?.fieldRules;
|
|
51064
|
+
if (!rules) return [];
|
|
51065
|
+
const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
|
|
51066
|
+
const rule = rules[base];
|
|
51067
|
+
if (!rule) return [];
|
|
51068
|
+
const warnings = [...collectStaticEncodingWarnings(operation, request, base, rule)];
|
|
51069
|
+
const names = requestBodyFieldNames(request, base);
|
|
51070
|
+
if (!names) return warnings;
|
|
51071
|
+
const present = new Set(names);
|
|
51072
|
+
const missing = rule.required.filter((name) => !present.has(name));
|
|
51073
|
+
if (missing.length > 0) {
|
|
51074
|
+
warnings.push(`CONTRACT_REQUEST_BODY_INCOMPLETE: ${operation.id} generated request body is missing required properties: ${missing.join(", ")}`);
|
|
51075
|
+
}
|
|
51076
|
+
const readOnlySent = rule.readOnly.filter((name) => present.has(name));
|
|
51077
|
+
if (readOnlySent.length > 0) {
|
|
51078
|
+
warnings.push(`CONTRACT_READONLY_PROPERTY_IN_REQUEST: ${operation.id} generated request body includes readOnly properties: ${readOnlySent.join(", ")}`);
|
|
51079
|
+
}
|
|
51080
|
+
return warnings;
|
|
50382
51081
|
}
|
|
50383
51082
|
function validateScript(script) {
|
|
50384
51083
|
const source = script.join("\n");
|
|
@@ -50427,9 +51126,12 @@ function instrumentContractCollection(collection, index) {
|
|
|
50427
51126
|
if (result.operation) {
|
|
50428
51127
|
const previous = covered.get(result.operation.id);
|
|
50429
51128
|
if (previous) throw new Error(`CONTRACT_DUPLICATE_OPERATION_REQUEST: ${result.operation.id} matched more than one generated request (${previous}, ${String(item.name || "<unnamed>")})`);
|
|
50430
|
-
assertStaticRequestShape(result.operation, request);
|
|
51129
|
+
warnings.push(...assertStaticRequestShape(result.operation, request));
|
|
51130
|
+
for (const name of [...requestQueryNames(request)].filter((entry) => !result.operation.declaredQueryParameters.includes(entry))) {
|
|
51131
|
+
warnings.push(`CONTRACT_UNDOCUMENTED_QUERY_PARAM: ${result.operation.id} generated request sends query parameter ${name} that the OpenAPI operation does not declare`);
|
|
51132
|
+
}
|
|
50431
51133
|
covered.set(result.operation.id, String(item.name || "<unnamed>"));
|
|
50432
|
-
script = createContractScript(result.operation);
|
|
51134
|
+
script = createContractScript(result.operation, warnings);
|
|
50433
51135
|
} else if (result.ambiguous && result.ambiguous.length > 0) {
|
|
50434
51136
|
script = createMappingFailureScript(`Ambiguous OpenAPI operation match for request ${result.method} ${result.path}: ${result.ambiguous.map((entry) => entry.id).join(", ")}`);
|
|
50435
51137
|
} else {
|