@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/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
- "format"
49672
+ "contentEncoding",
49673
+ "contentMediaType",
49674
+ "contentSchema"
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"
49671
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 mergeRequiredWithoutWriteOnly(required, writeOnlyProperties) {
49690
- const values = asArray(required).map((entry) => String(entry)).filter((entry) => !writeOnlyProperties.has(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 normalizeSchema(root, schema2, options) {
49707
- const depth = options.depth ?? 0;
49708
- if (depth > 50) return unsupported("OpenAPI schema reference depth exceeded 50");
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(root, entry, { ...options, depth: depth + 1, rootSchema: false }));
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,74 @@ 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 stack = options.stack ?? [];
49720
- if (stack.includes(ref)) return unsupported(`Recursive OpenAPI $ref is unsupported: ${ref}`);
49721
- const resolved = resolvePointer(root, ref);
49722
- if (resolved === void 0) return unsupported(`Unresolved OpenAPI $ref: ${ref}`);
49723
- const siblingEntries = Object.entries(record).filter(([key]) => key !== "$ref");
49724
- const resolvedSchema = normalizeSchema(root, resolved, {
49725
- ...options,
49726
- depth: depth + 1,
49727
- rootSchema: false,
49728
- stack: [...stack, ref]
49729
- });
49730
- const resolvedUnsupported = hasUnsupported(resolvedSchema);
49731
- if (resolvedUnsupported) return unsupported(resolvedUnsupported);
49732
- if (options.version === "3.0" || siblingEntries.length === 0) return resolvedSchema;
49733
- const siblingSchema = normalizeSchema(root, Object.fromEntries(siblingEntries), {
49734
- ...options,
49735
- depth: depth + 1,
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: [resolvedSchema, siblingSchema] };
49742
- if (options.rootSchema) wrapper.$schema = options.dialect ?? rootDialect(root, options.version, record);
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 && options.version === "3.0";
49789
+ const nullable = sourceSchema.nullable === true && ctx.version === "3.0";
49747
49790
  delete sourceSchema.nullable;
49748
- const writeOnlyProperties = /* @__PURE__ */ new Set();
49791
+ const discriminator = asRecord3(sourceSchema.discriminator);
49792
+ if (discriminator && typeof discriminator.propertyName === "string" && discriminator.propertyName) {
49793
+ const propertyName = discriminator.propertyName;
49794
+ const branchKey = Array.isArray(sourceSchema.oneOf) ? "oneOf" : Array.isArray(sourceSchema.anyOf) ? "anyOf" : "";
49795
+ const members = branchKey ? sourceSchema[branchKey] : [];
49796
+ const memberRefs = members.map((member) => {
49797
+ const memberRecord = asRecord3(member);
49798
+ const memberRef = memberRecord && Object.keys(memberRecord).length === 1 && typeof memberRecord.$ref === "string" ? memberRecord.$ref : "";
49799
+ return memberRef.startsWith("#/") ? memberRef : "";
49800
+ });
49801
+ if (branchKey && members.length > 0 && memberRefs.every(Boolean)) {
49802
+ const valuesByRef = /* @__PURE__ */ new Map();
49803
+ const refByMappingKey = /* @__PURE__ */ new Map();
49804
+ for (const [value, target] of Object.entries(asRecord3(discriminator.mapping) ?? {})) {
49805
+ if (typeof target !== "string" || !target) continue;
49806
+ const targetRef = target.startsWith("#/") ? target : `#/components/schemas/${target}`;
49807
+ valuesByRef.set(targetRef, [...valuesByRef.get(targetRef) ?? [], value]);
49808
+ refByMappingKey.set(value, targetRef);
49809
+ }
49810
+ sourceSchema[branchKey] = members.map((member, index) => {
49811
+ const memberRef = memberRefs[index] ?? "";
49812
+ const tail = (memberRef.split("/").pop() ?? "").replace(/~1/g, "/").replace(/~0/g, "~");
49813
+ const values = [...valuesByRef.get(memberRef) ?? []];
49814
+ const tailClaimedElsewhere = refByMappingKey.has(tail) && refByMappingKey.get(tail) !== memberRef;
49815
+ if (tail && !tailClaimedElsewhere && !values.includes(tail)) values.push(tail);
49816
+ const dispatch = values.length === 1 ? { const: values[0] } : { enum: values };
49817
+ return { allOf: [member, { type: "object", required: [propertyName], properties: { [propertyName]: dispatch } }] };
49818
+ });
49819
+ } else {
49820
+ ctx.notes.add("discriminator");
49821
+ }
49822
+ }
49823
+ const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
49824
+ const strippedProperties = /* @__PURE__ */ new Set();
49749
49825
  const rawProperties = asRecord3(sourceSchema.properties);
49750
49826
  if (rawProperties) {
49751
49827
  for (const [propertyName, propertySchema] of Object.entries(rawProperties)) {
49752
- if (asRecord3(propertySchema)?.writeOnly === true) writeOnlyProperties.add(propertyName);
49828
+ let flagSource = asRecord3(propertySchema);
49829
+ if (typeof flagSource?.$ref === "string" && flagSource.$ref.startsWith("#/")) {
49830
+ flagSource = asRecord3(resolvePointer(ctx.root, flagSource.$ref)) ?? flagSource;
49831
+ }
49832
+ if (flagSource?.[directionStrippedFlag] === true) strippedProperties.add(propertyName);
49753
49833
  }
49754
49834
  }
49755
49835
  const normalized = {};
@@ -49776,17 +49856,69 @@ function normalizeSchema(root, schema2, options) {
49776
49856
  delete normalized.maximum;
49777
49857
  continue;
49778
49858
  }
49859
+ if (key === "format") {
49860
+ if (typeof value === "string" && ASSERTED_FORMATS.has(value)) normalized.format = value;
49861
+ continue;
49862
+ }
49779
49863
  if (!ASSERTION_KEYS.has(key)) return unsupported(`Unsupported OpenAPI schema keyword: ${key}`);
49780
- if (key === "items" && Array.isArray(value) && options.version === "3.0") {
49864
+ if (ctx.dialect === DRAFT_07_SCHEMA_URI && DRAFT_2020_12_ONLY_KEYS.has(key)) {
49865
+ return unsupported(`${key} requires the JSON Schema 2020-12 dialect`);
49866
+ }
49867
+ if (ctx.dialect === DRAFT_2020_12_SCHEMA_URI && DRAFT_07_ONLY_KEYS.has(key)) {
49868
+ return unsupported(`${key} is a draft-07 keyword and is unsupported under JSON Schema 2020-12`);
49869
+ }
49870
+ if (key === "items" && Array.isArray(value) && ctx.version === "3.0") {
49781
49871
  return unsupported("Tuple array items are unsupported in OpenAPI 3.0");
49782
49872
  }
49873
+ if (key === "patternProperties" || key === "dependentSchemas") {
49874
+ const map2 = asRecord3(value);
49875
+ if (!map2) continue;
49876
+ const next = {};
49877
+ for (const [mapKey, mapSchema] of Object.entries(map2)) {
49878
+ const child3 = normalizeSchema(ctx, mapSchema, { depth: depth + 1, rootSchema: false });
49879
+ const bad2 = hasUnsupported(child3);
49880
+ if (bad2) return unsupported(bad2);
49881
+ next[mapKey] = child3;
49882
+ }
49883
+ normalized[key] = next;
49884
+ continue;
49885
+ }
49886
+ if (key === "dependentRequired") {
49887
+ const map2 = asRecord3(value);
49888
+ if (!map2) continue;
49889
+ for (const names of Object.values(map2)) {
49890
+ if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
49891
+ return unsupported("dependentRequired requires string array values");
49892
+ }
49893
+ }
49894
+ normalized.dependentRequired = value;
49895
+ continue;
49896
+ }
49897
+ if (key === "dependencies") {
49898
+ const map2 = asRecord3(value);
49899
+ if (!map2) continue;
49900
+ const next = {};
49901
+ for (const [mapKey, dependency] of Object.entries(map2)) {
49902
+ if (Array.isArray(dependency)) {
49903
+ if (dependency.some((name) => typeof name !== "string")) return unsupported("dependencies requires string array or schema values");
49904
+ next[mapKey] = dependency;
49905
+ continue;
49906
+ }
49907
+ const child3 = normalizeSchema(ctx, dependency, { depth: depth + 1, rootSchema: false });
49908
+ const bad2 = hasUnsupported(child3);
49909
+ if (bad2) return unsupported(bad2);
49910
+ next[mapKey] = child3;
49911
+ }
49912
+ normalized.dependencies = next;
49913
+ continue;
49914
+ }
49783
49915
  if (key === "properties") {
49784
49916
  const properties = asRecord3(value);
49785
49917
  if (!properties) continue;
49786
49918
  const nextProperties = {};
49787
49919
  for (const [propertyName, propertySchema] of Object.entries(properties)) {
49788
- if (writeOnlyProperties.has(propertyName)) continue;
49789
- const child3 = normalizeSchema(root, propertySchema, { ...options, depth: depth + 1, rootSchema: false });
49920
+ if (strippedProperties.has(propertyName)) continue;
49921
+ const child3 = normalizeSchema(ctx, propertySchema, { depth: depth + 1, rootSchema: false });
49790
49922
  const bad2 = hasUnsupported(child3);
49791
49923
  if (bad2) return unsupported(bad2);
49792
49924
  nextProperties[propertyName] = child3;
@@ -49795,16 +49927,46 @@ function normalizeSchema(root, schema2, options) {
49795
49927
  continue;
49796
49928
  }
49797
49929
  if (key === "required") {
49798
- const required = mergeRequiredWithoutWriteOnly(value, writeOnlyProperties);
49930
+ const required = mergeRequiredWithoutStripped(value, strippedProperties);
49799
49931
  if (required) normalized.required = required;
49800
49932
  continue;
49801
49933
  }
49802
- const child2 = normalizeSchema(root, value, { ...options, depth: depth + 1, rootSchema: false });
49934
+ const child2 = normalizeSchema(ctx, value, { depth: depth + 1, rootSchema: false });
49803
49935
  const bad = hasUnsupported(child2);
49804
49936
  if (bad) return unsupported(bad);
49805
49937
  normalized[key] = child2;
49806
49938
  }
49807
- if (options.rootSchema) normalized.$schema = options.dialect ?? rootDialect(root, options.version, record);
49939
+ if (typeof normalized.minimum === "number" && typeof normalized.exclusiveMinimum === "number") {
49940
+ if (normalized.exclusiveMinimum >= normalized.minimum) delete normalized.minimum;
49941
+ else delete normalized.exclusiveMinimum;
49942
+ }
49943
+ if (typeof normalized.maximum === "number" && typeof normalized.exclusiveMaximum === "number") {
49944
+ if (normalized.exclusiveMaximum <= normalized.maximum) delete normalized.maximum;
49945
+ else delete normalized.exclusiveMaximum;
49946
+ }
49947
+ if (sourceSchema.format === "int32") {
49948
+ const type2 = normalized.type;
49949
+ const isInteger2 = type2 === "integer" || Array.isArray(type2) && type2.includes("integer");
49950
+ if (isInteger2) {
49951
+ if (typeof normalized.exclusiveMinimum === "number") {
49952
+ if (normalized.exclusiveMinimum < INT32_MIN) {
49953
+ delete normalized.exclusiveMinimum;
49954
+ normalized.minimum = INT32_MIN;
49955
+ }
49956
+ } else if (typeof normalized.minimum !== "number" || normalized.minimum < INT32_MIN) {
49957
+ normalized.minimum = INT32_MIN;
49958
+ }
49959
+ if (typeof normalized.exclusiveMaximum === "number") {
49960
+ if (normalized.exclusiveMaximum > INT32_MAX) {
49961
+ delete normalized.exclusiveMaximum;
49962
+ normalized.maximum = INT32_MAX;
49963
+ }
49964
+ } else if (typeof normalized.maximum !== "number" || normalized.maximum > INT32_MAX) {
49965
+ normalized.maximum = INT32_MAX;
49966
+ }
49967
+ }
49968
+ }
49969
+ if (options.rootSchema) normalized.$schema = ctx.dialect;
49808
49970
  if (nullable) {
49809
49971
  if (typeof normalized.type === "string") {
49810
49972
  normalized.type = [normalized.type, "null"];
@@ -49816,23 +49978,108 @@ function normalizeSchema(root, schema2, options) {
49816
49978
  const wrappedSchema = { ...normalized };
49817
49979
  delete wrappedSchema.$schema;
49818
49980
  const wrapper = { anyOf: [{ type: "null" }, wrappedSchema] };
49819
- if (options.rootSchema) wrapper.$schema = options.dialect ?? rootDialect(root, options.version, record);
49981
+ if (options.rootSchema) wrapper.$schema = ctx.dialect;
49820
49982
  return wrapper;
49821
49983
  }
49822
49984
  }
49823
49985
  return normalized;
49824
49986
  }
49825
- function packSchema(root, schema2, version) {
49987
+ function isSchemaGraphOverflow(packed) {
49988
+ return typeof packed.unsupported === "string" && packed.unsupported.startsWith("OpenAPI schema reference graph exceeded");
49989
+ }
49990
+ function packSchema(root, schema2, version, direction = "response") {
49826
49991
  try {
49992
+ if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
49993
+ if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
49827
49994
  const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
49828
- const normalized = normalizeSchema(root, schema2, { version, rootSchema: true, dialect });
49995
+ const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map(), notes: /* @__PURE__ */ new Set() };
49996
+ const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
49829
49997
  const message = hasUnsupported(normalized);
49830
- return message ? unsupported(message) : { schema: normalized };
49998
+ if (message) return unsupported(message);
49999
+ const aliasTargets = /* @__PURE__ */ new Map();
50000
+ for (const entry of ctx.defs.values()) {
50001
+ if (entry.unsupported) return unsupported(entry.unsupported);
50002
+ const entrySchema = asRecord3(entry.schema);
50003
+ const ref = entrySchema && Object.keys(entrySchema).length === 1 && typeof entrySchema.$ref === "string" ? entrySchema.$ref : "";
50004
+ if (ref.startsWith("#/$defs/")) aliasTargets.set(entry.name, ref.slice("#/$defs/".length));
50005
+ }
50006
+ for (const start of aliasTargets.keys()) {
50007
+ const seen = /* @__PURE__ */ new Set();
50008
+ let current = start;
50009
+ while (current !== void 0 && aliasTargets.has(current)) {
50010
+ if (seen.has(current)) return unsupported("Self-referential alias schema is unsupported");
50011
+ seen.add(current);
50012
+ current = aliasTargets.get(current);
50013
+ }
50014
+ }
50015
+ if (ctx.defs.size > 0) {
50016
+ const normalizedRecord = asRecord3(normalized);
50017
+ if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
50018
+ normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
50019
+ }
50020
+ const packed = { schema: normalized };
50021
+ if (ctx.notes.size > 0) packed.notes = [...ctx.notes].sort();
50022
+ return packed;
49831
50023
  } catch (error2) {
49832
50024
  return unsupported(error2 instanceof Error ? error2.message : String(error2));
49833
50025
  }
49834
50026
  }
49835
50027
 
50028
+ // src/lib/spec/schema-validator-code.ts
50029
+ var import_schemasafe = __toESM(require_src(), 1);
50030
+ function compileSchemaValidator(schema2) {
50031
+ try {
50032
+ const validate2 = (0, import_schemasafe.validator)(schema2, {
50033
+ includeErrors: false,
50034
+ // Real-world specs legally mix enum/const with other keywords; without
50035
+ // this flag schemasafe refuses to compile them (observed in Stripe,
50036
+ // Plaid, and DigitalOcean public specs).
50037
+ allowUnusedKeywords: true,
50038
+ contentValidation: false,
50039
+ formatAssertion: true,
50040
+ isJSON: true,
50041
+ mode: "default",
50042
+ removeAdditional: false,
50043
+ requireSchema: true,
50044
+ requireStringValidation: false,
50045
+ useDefaults: false
50046
+ });
50047
+ return (value) => validate2(value);
50048
+ } catch {
50049
+ return null;
50050
+ }
50051
+ }
50052
+ function compileSchemaValidatorCode(schema2) {
50053
+ try {
50054
+ const validate2 = (0, import_schemasafe.validator)(schema2, {
50055
+ includeErrors: true,
50056
+ allErrors: true,
50057
+ // Real-world specs legally mix enum/const with other keywords; without
50058
+ // this flag schemasafe refuses to compile them (observed in Stripe,
50059
+ // Plaid, and DigitalOcean public specs).
50060
+ allowUnusedKeywords: true,
50061
+ contentValidation: false,
50062
+ formatAssertion: true,
50063
+ isJSON: true,
50064
+ mode: "default",
50065
+ removeAdditional: false,
50066
+ requireSchema: true,
50067
+ requireStringValidation: false,
50068
+ useDefaults: false
50069
+ });
50070
+ const source = validate2.toModule();
50071
+ if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
50072
+ throw new Error("schemasafe generated forbidden dynamic code");
50073
+ }
50074
+ return source;
50075
+ } catch (error2) {
50076
+ throw new Error(
50077
+ `CONTRACT_SCHEMA_COMPILE_FAILED: ${error2 instanceof Error ? error2.message : String(error2)}`,
50078
+ { cause: error2 }
50079
+ );
50080
+ }
50081
+ }
50082
+
49836
50083
  // src/lib/spec/contract-index.ts
49837
50084
  var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
49838
50085
  function asRecord4(value) {
@@ -49908,7 +50155,7 @@ function collectSecurityApiKeys(root, operation) {
49908
50155
  for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
49909
50156
  for (const schemeName of Object.keys(requirement)) {
49910
50157
  const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
49911
- if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header"].includes(String(scheme.in))) {
50158
+ if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header", "cookie"].includes(String(scheme.in))) {
49912
50159
  names.add(`${String(scheme.in)}:${scheme.name.toLowerCase()}`);
49913
50160
  }
49914
50161
  }
@@ -49930,12 +50177,197 @@ function collectSecuritySchemeWarnings(root, operation) {
49930
50177
  for (const schemeName of Object.keys(requirement)) {
49931
50178
  const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
49932
50179
  warnings.add(
49933
- `CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven by dynamic contract tests`
50180
+ `CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven beyond credential presence by dynamic contract tests`
49934
50181
  );
49935
50182
  }
49936
50183
  }
49937
50184
  return [...warnings];
49938
50185
  }
50186
+ function securityCheckFor(schemeName, scheme) {
50187
+ const kind = securitySchemeKind(scheme);
50188
+ if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["header", "query", "cookie"].includes(String(scheme.in))) {
50189
+ return { scheme: schemeName, kind, checkable: true, in: String(scheme.in), name: scheme.name };
50190
+ }
50191
+ if (scheme?.type === "http") {
50192
+ const httpScheme = String(scheme.scheme || "").toLowerCase();
50193
+ if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
50194
+ if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
50195
+ if (httpScheme) return { scheme: schemeName, kind, checkable: true, prefix: `${httpScheme.charAt(0).toUpperCase()}${httpScheme.slice(1)} ` };
50196
+ return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
50197
+ }
50198
+ if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
50199
+ return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
50200
+ }
50201
+ return { scheme: schemeName, kind, checkable: false };
50202
+ }
50203
+ function collectSecurityRuntimeChecks(root, operation) {
50204
+ const securitySchemes = asRecord4(asRecord4(root.components)?.securitySchemes);
50205
+ const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
50206
+ const alternatives = [];
50207
+ for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
50208
+ const schemeNames = Object.keys(requirement);
50209
+ if (schemeNames.length === 0) return void 0;
50210
+ alternatives.push(schemeNames.map((schemeName) => securityCheckFor(schemeName, resolveInternalRef(root, securitySchemes?.[schemeName]))));
50211
+ }
50212
+ if (alternatives.some((alternative) => alternative.length > 0 && alternative.every((check) => !check.checkable))) return void 0;
50213
+ return alternatives.length > 0 ? alternatives : void 0;
50214
+ }
50215
+ function resolvedParameters(root, pathItem, operation) {
50216
+ return [...asArray2(pathItem.parameters), ...asArray2(operation.parameters)].map((rawParam) => {
50217
+ try {
50218
+ return resolveInternalRef(root, rawParam);
50219
+ } catch {
50220
+ return null;
50221
+ }
50222
+ }).filter((param) => Boolean(param));
50223
+ }
50224
+ var DEFAULT_PARAM_STYLES = { query: "form", path: "simple", header: "simple", cookie: "form" };
50225
+ var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
50226
+ function isIgnoredParameter(location2, name) {
50227
+ return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
50228
+ }
50229
+ function jsonContentParameterMedia(param) {
50230
+ const content = asRecord4(param.content);
50231
+ if (!content) return void 0;
50232
+ const entries = Object.entries(content);
50233
+ if (entries.length !== 1) return void 0;
50234
+ const [contentType, mediaObject] = entries[0];
50235
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
50236
+ if (!isJsonBaseType(base)) return void 0;
50237
+ const schema2 = asRecord4(mediaObject)?.schema;
50238
+ return schema2 === void 0 ? void 0 : schema2;
50239
+ }
50240
+ function collectSerializationWarnings(root, pathItem, operation, decodedKeys) {
50241
+ const warnings = [];
50242
+ for (const param of resolvedParameters(root, pathItem, operation)) {
50243
+ const location2 = String(param.in || "").toLowerCase();
50244
+ const name = String(param.name || "");
50245
+ const defaultStyle = DEFAULT_PARAM_STYLES[location2];
50246
+ if (!name || !defaultStyle || isIgnoredParameter(location2, name)) continue;
50247
+ const style = typeof param.style === "string" ? param.style : defaultStyle;
50248
+ const defaultExplode = style === "form";
50249
+ const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
50250
+ const unvalidatedContent = param.content !== void 0 && (jsonContentParameterMedia(param) === void 0 || location2 !== "query" && location2 !== "header");
50251
+ if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || unvalidatedContent) {
50252
+ if (decodedKeys.has(`${location2}:${name.toLowerCase()}`) && param.allowReserved !== true && param.content === void 0) continue;
50253
+ warnings.push(`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: parameter ${location2}:${name} declares non-default style, explode, allowReserved, or content and its serialization is not validated`);
50254
+ }
50255
+ }
50256
+ return warnings;
50257
+ }
50258
+ var SCALAR_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
50259
+ function packedScalarSchema(packed) {
50260
+ if (packed.unsupported || packed.schema === void 0) return void 0;
50261
+ const record = asRecord4(packed.schema);
50262
+ if (!record) return void 0;
50263
+ const types2 = Array.isArray(record.type) ? record.type : [record.type];
50264
+ if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
50265
+ return packed.schema;
50266
+ }
50267
+ function packedArrayItemsSchema(packed) {
50268
+ if (packed.unsupported || packed.schema === void 0) return void 0;
50269
+ const record = asRecord4(packed.schema);
50270
+ if (!record) return void 0;
50271
+ const types2 = Array.isArray(record.type) ? record.type : [record.type];
50272
+ if (types2.length !== 1 || types2[0] !== "array") return void 0;
50273
+ if (record.prefixItems !== void 0 || Array.isArray(record.items)) return void 0;
50274
+ if (record.items === void 0) return {};
50275
+ const items = asRecord4(record.items);
50276
+ if (!items || typeof items.$ref === "string") return void 0;
50277
+ const itemTypes = Array.isArray(items.type) ? items.type : [items.type];
50278
+ if (!itemTypes.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
50279
+ return items;
50280
+ }
50281
+ var QUERY_ARRAY_DECODES = {
50282
+ "form:true": "multi",
50283
+ "form:false": "csv",
50284
+ "spaceDelimited:false": "ssv",
50285
+ "pipeDelimited:false": "pipes"
50286
+ };
50287
+ function collectParameterChecks(root, pathItem, operation, version, operationId, pathTemplate, warnings) {
50288
+ const securityKeys = collectSecurityApiKeys(root, operation);
50289
+ const checks = [];
50290
+ const seen = /* @__PURE__ */ new Set();
50291
+ const orderedParams = [...asArray2(operation.parameters), ...asArray2(pathItem.parameters)].map((rawParam) => {
50292
+ try {
50293
+ return resolveInternalRef(root, rawParam);
50294
+ } catch {
50295
+ return null;
50296
+ }
50297
+ }).filter((param) => Boolean(param));
50298
+ for (const param of orderedParams) {
50299
+ const location2 = String(param.in || "").toLowerCase();
50300
+ if (location2 !== "query" && location2 !== "header" && location2 !== "path" && location2 !== "cookie") continue;
50301
+ const name = String(param.name || "");
50302
+ if (!name || isIgnoredParameter(location2, name)) continue;
50303
+ const key = `${location2}:${name.toLowerCase()}`;
50304
+ if (seen.has(key)) continue;
50305
+ seen.add(key);
50306
+ if (securityKeys.has(key)) continue;
50307
+ const contentMedia = jsonContentParameterMedia(param);
50308
+ if (contentMedia !== void 0 && (location2 === "query" || location2 === "header")) {
50309
+ const packed2 = packSchema(root, contentMedia, version, "request");
50310
+ warnings.push(...packNoteWarnings(packed2, `parameter ${location2}:${name} of ${operationId}`));
50311
+ if (packed2.unsupported) {
50312
+ warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed2.unsupported})`);
50313
+ } else if (packed2.schema !== void 0) {
50314
+ const check2 = { in: location2, name, required: param.required === true, content: true, schema: packed2.schema };
50315
+ if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
50316
+ checks.push(check2);
50317
+ }
50318
+ continue;
50319
+ }
50320
+ if (param.content !== void 0 || param.schema === void 0) continue;
50321
+ const defaultStyle = DEFAULT_PARAM_STYLES[location2];
50322
+ const style = typeof param.style === "string" ? param.style : defaultStyle;
50323
+ const defaultExplode = style === "form";
50324
+ const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
50325
+ const defaultSerialization = style === defaultStyle && explode === defaultExplode;
50326
+ const packed = packSchema(root, param.schema, version);
50327
+ const noteWarnings = packNoteWarnings(packed, `parameter ${location2}:${name} of ${operationId}`);
50328
+ if (defaultSerialization) warnings.push(...noteWarnings);
50329
+ if (packed.unsupported) {
50330
+ if (defaultSerialization) warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
50331
+ continue;
50332
+ }
50333
+ if (location2 === "path" && !pathTemplate.split("/").includes(`{${name}}`)) continue;
50334
+ const scalarSchema = packedScalarSchema(packed);
50335
+ if (scalarSchema !== void 0) {
50336
+ if (!defaultSerialization) continue;
50337
+ const check2 = { in: location2, name, required: param.required === true, schema: scalarSchema };
50338
+ if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
50339
+ checks.push(check2);
50340
+ continue;
50341
+ }
50342
+ if (location2 !== "query" && location2 !== "header") continue;
50343
+ const items = packedArrayItemsSchema(packed);
50344
+ if (items === void 0) continue;
50345
+ const decode = location2 === "query" ? QUERY_ARRAY_DECODES[`${style}:${explode}`] : style === "simple" && !explode ? "csv" : void 0;
50346
+ if (!decode) continue;
50347
+ if (!defaultSerialization) warnings.push(...noteWarnings);
50348
+ const check = { in: location2, name, required: param.required === true, schema: packed.schema, decode, items };
50349
+ if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
50350
+ checks.push(check);
50351
+ }
50352
+ return checks.length > 0 ? checks : void 0;
50353
+ }
50354
+ function packNoteWarnings(packed, context) {
50355
+ return (packed.notes ?? []).map(
50356
+ (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`
50357
+ );
50358
+ }
50359
+ function collectDeclaredQueryParameters(root, pathItem, operation) {
50360
+ const names = /* @__PURE__ */ new Set();
50361
+ for (const param of resolvedParameters(root, pathItem, operation)) {
50362
+ if (String(param.in || "").toLowerCase() !== "query") continue;
50363
+ const name = String(param.name || "");
50364
+ if (name) names.add(name.toLowerCase());
50365
+ }
50366
+ for (const key of collectSecurityApiKeys(root, operation)) {
50367
+ if (key.startsWith("query:")) names.add(key.slice("query:".length));
50368
+ }
50369
+ return [...names];
50370
+ }
49939
50371
  function collectParameters(root, pathItem, operation) {
49940
50372
  const securityKeys = collectSecurityApiKeys(root, operation);
49941
50373
  const requirements = [];
@@ -49945,9 +50377,9 @@ function collectParameters(root, pathItem, operation) {
49945
50377
  const param = resolveInternalRef(root, rawParam);
49946
50378
  if (!param) continue;
49947
50379
  const location2 = String(param.in || "").toLowerCase();
49948
- if (!["path", "query", "header"].includes(location2)) continue;
50380
+ if (!["path", "query", "header", "cookie"].includes(location2)) continue;
49949
50381
  const name = String(param.name || "");
49950
- if (!name || param.required !== true) continue;
50382
+ if (!name || param.required !== true || isIgnoredParameter(location2, name)) continue;
49951
50383
  const key = `${location2}:${name.toLowerCase()}`;
49952
50384
  if (seen.has(key)) continue;
49953
50385
  seen.add(key);
@@ -49959,36 +50391,263 @@ function collectParameters(root, pathItem, operation) {
49959
50391
  }
49960
50392
  return requirements;
49961
50393
  }
49962
- function collectRequestBody(root, operation) {
50394
+ var BODY_FIELD_RULE_TYPES = /* @__PURE__ */ new Set(["application/x-www-form-urlencoded", "multipart/form-data"]);
50395
+ function isJsonBaseType(base) {
50396
+ return base === "application/json" || /\+json$/.test(base);
50397
+ }
50398
+ function mergeObjectSchema(root, rawSchema, depth) {
50399
+ if (depth > 10) return null;
50400
+ let schema2;
50401
+ try {
50402
+ schema2 = resolveInternalRef(root, rawSchema);
50403
+ } catch {
50404
+ return null;
50405
+ }
50406
+ if (!schema2) return null;
50407
+ const merged = {
50408
+ required: asArray2(schema2.required).map((entry) => String(entry)).filter(Boolean),
50409
+ properties: { ...asRecord4(schema2.properties) ?? {} }
50410
+ };
50411
+ for (const member of asArray2(schema2.allOf)) {
50412
+ const child2 = mergeObjectSchema(root, member, depth + 1);
50413
+ if (!child2) continue;
50414
+ merged.required.push(...child2.required);
50415
+ merged.properties = { ...merged.properties, ...child2.properties };
50416
+ }
50417
+ merged.required = [...new Set(merged.required)];
50418
+ return merged;
50419
+ }
50420
+ function propertyIsReadOnly(root, properties, name) {
50421
+ try {
50422
+ return resolveInternalRef(root, properties[name])?.readOnly === true;
50423
+ } catch {
50424
+ return false;
50425
+ }
50426
+ }
50427
+ function propertyIsBinary(root, properties, name) {
50428
+ let schema2;
50429
+ try {
50430
+ schema2 = resolveInternalRef(root, properties[name]);
50431
+ } catch {
50432
+ return false;
50433
+ }
50434
+ if (!schema2) return false;
50435
+ if (schema2.format === "binary") return true;
50436
+ if (typeof schema2.contentMediaType !== "string" || typeof schema2.contentEncoding === "string") return false;
50437
+ const media = schema2.contentMediaType.toLowerCase().split(";")[0]?.trim() ?? "";
50438
+ if (!media) return false;
50439
+ return media !== "application/json" && !media.endsWith("+json") && !media.startsWith("text/");
50440
+ }
50441
+ function fieldEncodings(root, base, mediaObject, properties) {
50442
+ const declared = asRecord4(mediaObject?.encoding);
50443
+ const encodings = {};
50444
+ for (const name of Object.keys(properties)) {
50445
+ if (base === "multipart/form-data" && propertyIsBinary(root, properties, name)) {
50446
+ encodings[name] = { ...encodings[name], binary: true };
50447
+ }
50448
+ }
50449
+ if (declared) {
50450
+ for (const [name, rawEncoding] of Object.entries(declared)) {
50451
+ const encoding = asRecord4(rawEncoding);
50452
+ if (!encoding) continue;
50453
+ const entry = { ...encodings[name] };
50454
+ if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
50455
+ entry.contentType = encoding.contentType.toLowerCase();
50456
+ }
50457
+ if (base === "multipart/form-data" && asRecord4(encoding.headers)) {
50458
+ entry.hasHeaders = true;
50459
+ }
50460
+ if (base === "application/x-www-form-urlencoded") {
50461
+ const style = typeof encoding.style === "string" ? encoding.style : "form";
50462
+ const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
50463
+ if (style !== "form" || explode !== (style === "form") || encoding.allowReserved === true) {
50464
+ entry.nonDefaultSerialization = true;
50465
+ }
50466
+ }
50467
+ if (Object.keys(entry).length > 0) encodings[name] = entry;
50468
+ }
50469
+ }
50470
+ return Object.keys(encodings).length > 0 ? encodings : void 0;
50471
+ }
50472
+ function formFieldSchemas(root, version, properties, context, warnings) {
50473
+ const schemas = {};
50474
+ for (const name of Object.keys(properties)) {
50475
+ const packed = packSchema(root, properties[name], version, "request");
50476
+ warnings.push(...packNoteWarnings(packed, `field ${name} of ${context}`));
50477
+ if (packed.unsupported) {
50478
+ warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: field ${name} of ${context} skipped (${packed.unsupported})`);
50479
+ continue;
50480
+ }
50481
+ if (packed.schema === void 0) continue;
50482
+ const schema2 = packedScalarSchema(packed);
50483
+ if (schema2 !== void 0) schemas[name] = schema2;
50484
+ }
50485
+ return Object.keys(schemas).length > 0 ? schemas : void 0;
50486
+ }
50487
+ function requestBodyFieldRules(root, content, version, operationId, warnings) {
50488
+ const rules = {};
50489
+ for (const [contentType, mediaObject] of Object.entries(content)) {
50490
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
50491
+ if (!isJsonBaseType(base) && !BODY_FIELD_RULE_TYPES.has(base)) continue;
50492
+ const mediaRecord = asRecord4(mediaObject);
50493
+ const merged = mergeObjectSchema(root, mediaRecord?.schema, 0);
50494
+ if (!merged) continue;
50495
+ const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
50496
+ const required = merged.required.filter((name) => !propertyIsReadOnly(root, merged.properties, name));
50497
+ const formRules = BODY_FIELD_RULE_TYPES.has(base);
50498
+ const encodings = formRules ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
50499
+ const fieldSchemas = formRules ? formFieldSchemas(root, version, merged.properties, `request body ${contentType} of ${operationId}`, warnings) : void 0;
50500
+ if (required.length > 0 || readOnly.length > 0 || encodings || fieldSchemas) {
50501
+ const rule = { required, readOnly };
50502
+ if (encodings) rule.encodings = encodings;
50503
+ if (fieldSchemas) rule.fieldSchemas = fieldSchemas;
50504
+ rules[base] = rule;
50505
+ }
50506
+ }
50507
+ return Object.keys(rules).length > 0 ? rules : void 0;
50508
+ }
50509
+ function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
50510
+ const schemas = {};
50511
+ const exampleWarnings = /* @__PURE__ */ new Set();
50512
+ for (const [contentType, mediaObject] of Object.entries(content)) {
50513
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
50514
+ const mediaRecord = asRecord4(mediaObject);
50515
+ const schema2 = mediaRecord?.schema;
50516
+ if (!isJsonBaseType(base)) {
50517
+ if (schema2 !== void 0 && !BODY_FIELD_RULE_TYPES.has(base)) {
50518
+ warnings.push(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated at runtime`);
50519
+ }
50520
+ continue;
50521
+ }
50522
+ if (schema2 === void 0) continue;
50523
+ const packed = packSchema(root, schema2, version, "request");
50524
+ warnings.push(...packNoteWarnings(packed, `request body ${contentType} of ${operationId}`));
50525
+ if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
50526
+ if (packed.unsupported) {
50527
+ warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
50528
+ continue;
50529
+ }
50530
+ if (packed.schema !== void 0) schemas[base] = packed.schema;
50531
+ }
50532
+ warnings.push(...exampleWarnings);
50533
+ return Object.keys(schemas).length > 0 ? schemas : void 0;
50534
+ }
50535
+ function collectRequestBody(root, operation, version, operationId, warnings) {
49963
50536
  const body = resolveInternalRef(root, operation.requestBody);
49964
- if (!body || body.required !== true) return void 0;
50537
+ if (!body) return void 0;
49965
50538
  const content = asRecord4(body.content);
50539
+ const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
50540
+ if (fieldRules) {
50541
+ for (const [base, rule] of Object.entries(fieldRules)) {
50542
+ for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
50543
+ if (encoding.nonDefaultSerialization) {
50544
+ warnings.push(
50545
+ `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`
50546
+ );
50547
+ }
50548
+ if (encoding.hasHeaders) {
50549
+ warnings.push(
50550
+ `CONTRACT_ENCODING_HEADERS_NOT_VALIDATED: ${base} request body field ${field} on ${operationId} declares per-part headers that generated formdata entries cannot carry`
50551
+ );
50552
+ }
50553
+ }
50554
+ }
50555
+ }
49966
50556
  return {
49967
- required: true,
49968
- contentTypes: content ? Object.keys(content) : []
50557
+ required: body.required === true,
50558
+ contentTypes: content ? Object.keys(content) : [],
50559
+ fieldRules,
50560
+ jsonSchemas: content ? requestBodyJsonSchemas(root, content, version, operationId, warnings) : void 0
49969
50561
  };
49970
50562
  }
49971
- function responseContent(root, version, response) {
50563
+ function exampleCandidates(root, mediaObject) {
50564
+ const candidates = [];
50565
+ if ("example" in mediaObject) candidates.push({ label: "example", value: mediaObject.example });
50566
+ const examples = asRecord4(mediaObject.examples);
50567
+ if (examples) {
50568
+ for (const [name, rawExample] of Object.entries(examples)) {
50569
+ let example;
50570
+ try {
50571
+ example = resolveInternalRef(root, rawExample);
50572
+ } catch {
50573
+ continue;
50574
+ }
50575
+ if (example && "value" in example) candidates.push({ label: `examples.${name}`, value: example.value });
50576
+ }
50577
+ }
50578
+ return candidates;
50579
+ }
50580
+ function validateExamples(root, mediaObject, packed, contentType, context, warnings) {
50581
+ if (packed.schema === void 0 || packed.unsupported) return;
50582
+ const candidates = exampleCandidates(root, mediaObject);
50583
+ if (candidates.length === 0) return;
50584
+ const validate2 = compileSchemaValidator(packed.schema);
50585
+ if (!validate2) return;
50586
+ for (const candidate of candidates) {
50587
+ if (!validate2(candidate.value)) {
50588
+ warnings.add(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for ${contentType} on ${context} does not match its schema`);
50589
+ }
50590
+ }
50591
+ }
50592
+ function responseContent(root, version, response, context, warnings) {
49972
50593
  const content = asRecord4(response.content);
49973
50594
  if (!content) return {};
49974
50595
  const media = {};
49975
50596
  for (const [contentType, mediaObject] of Object.entries(content)) {
49976
- const schema2 = asRecord4(mediaObject)?.schema;
49977
- media[contentType] = schema2 === void 0 ? {} : packSchema(root, schema2, version);
50597
+ const mediaRecord = asRecord4(mediaObject);
50598
+ const schema2 = mediaRecord?.schema;
50599
+ let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
50600
+ for (const warning2 of packNoteWarnings(packed, `response ${contentType} of ${context}`)) warnings.add(warning2);
50601
+ if (isSchemaGraphOverflow(packed)) {
50602
+ warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
50603
+ packed = {};
50604
+ }
50605
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
50606
+ if (mediaRecord && isJsonBaseType(base)) validateExamples(root, mediaRecord, packed, contentType, context, warnings);
50607
+ media[contentType] = packed;
49978
50608
  }
49979
50609
  return media;
49980
50610
  }
49981
- function responseHeaders(root, version, response) {
50611
+ function responseHeaders(root, version, response, context, warnings) {
49982
50612
  const headers = asRecord4(response.headers);
49983
50613
  if (!headers) return [];
49984
- return Object.entries(headers).map(([name, rawHeader]) => {
50614
+ const entries = [];
50615
+ for (const [name, rawHeader] of Object.entries(headers)) {
50616
+ if (name.toLowerCase() === "content-type") continue;
49985
50617
  const header = resolveInternalRef(root, rawHeader);
49986
- if (!header) return { name, required: true, unsupported: "Unresolved response header" };
50618
+ if (!header) {
50619
+ entries.push({ name, required: true, unsupported: "Unresolved response header" });
50620
+ continue;
50621
+ }
49987
50622
  const required = header.required === true;
49988
- if (header.content) return { name, required, unsupported: "OpenAPI response header content is unsupported" };
49989
- if (!header.schema) return { name, required };
49990
- return { name, required, ...packSchema(root, header.schema, version) };
49991
- });
50623
+ if (header.content) {
50624
+ entries.push({ name, required, unsupported: "OpenAPI response header content is unsupported" });
50625
+ continue;
50626
+ }
50627
+ if (!header.schema) {
50628
+ entries.push({ name, required });
50629
+ continue;
50630
+ }
50631
+ const packed = packSchema(root, header.schema, version);
50632
+ for (const warning2 of packNoteWarnings(packed, `response header ${name} of ${context}`)) warnings.add(warning2);
50633
+ if (isSchemaGraphOverflow(packed)) {
50634
+ warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
50635
+ entries.push({ name, required });
50636
+ continue;
50637
+ }
50638
+ if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
50639
+ const items = packedArrayItemsSchema(packed);
50640
+ if (items !== void 0) {
50641
+ entries.push({ name, required, schema: packed.schema, items });
50642
+ continue;
50643
+ }
50644
+ warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
50645
+ entries.push({ name, required });
50646
+ continue;
50647
+ }
50648
+ entries.push({ name, required, ...packed });
50649
+ }
50650
+ return entries;
49992
50651
  }
49993
50652
  function buildContractIndex(root) {
49994
50653
  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 +50672,23 @@ function buildContractIndex(root) {
50013
50672
  throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path8} must define at least one response`);
50014
50673
  }
50015
50674
  const contractResponses = {};
50675
+ const responseWarnings = /* @__PURE__ */ new Set();
50016
50676
  for (const [status, rawResponse] of Object.entries(responses)) {
50017
50677
  const response = resolveInternalRef(root, rawResponse);
50018
50678
  if (!response) continue;
50019
- const content = responseContent(root, version, response);
50020
- const headers = responseHeaders(root, version, response);
50679
+ if (asRecord4(response.links)) {
50680
+ responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path8}`);
50681
+ }
50682
+ const responseContext = `${lowerMethod.toUpperCase()} ${path8} status ${status}`;
50683
+ const content = responseContent(root, version, response, responseContext, responseWarnings);
50684
+ for (const [contentType, media] of Object.entries(content)) {
50685
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
50686
+ const schemaType = asRecord4(media.schema)?.type;
50687
+ if (!isJsonBaseType(base) && media.schema !== void 0 && !media.unsupported && schemaType !== "string") {
50688
+ responseWarnings.add(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: response schema for ${contentType} on ${responseContext} is not validated at runtime`);
50689
+ }
50690
+ }
50691
+ const headers = responseHeaders(root, version, response, responseContext, responseWarnings);
50021
50692
  contractResponses[normalizeResponseKey(status)] = {
50022
50693
  content,
50023
50694
  hasBody: Object.keys(content).length > 0,
@@ -50028,21 +50699,45 @@ function buildContractIndex(root) {
50028
50699
  path8,
50029
50700
  ...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path8))
50030
50701
  ].map(normalizePath))];
50702
+ const operationId = `${lowerMethod.toUpperCase()} ${path8}`;
50031
50703
  const opWarnings = [];
50704
+ opWarnings.push(...responseWarnings);
50032
50705
  opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
50706
+ const parameterChecks = collectParameterChecks(root, pathItem, operation, version, operationId, path8, opWarnings);
50707
+ const checkedKeys = new Set((parameterChecks ?? []).map((check) => `${check.in}:${check.name.toLowerCase()}`));
50708
+ const decodedKeys = new Set((parameterChecks ?? []).filter((check) => check.decode).map((check) => `${check.in}:${check.name.toLowerCase()}`));
50709
+ opWarnings.push(...collectSerializationWarnings(root, pathItem, operation, decodedKeys));
50710
+ if (operation.deprecated === true) {
50711
+ opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path8} is marked deprecated in the OpenAPI document`);
50712
+ }
50033
50713
  const requiredParameters = collectParameters(root, pathItem, operation);
50034
50714
  for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
50035
50715
  opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
50036
50716
  }
50717
+ for (const parameter of requiredParameters.filter((entry) => entry.in === "cookie" && !entry.securityDerived)) {
50718
+ 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`);
50719
+ }
50720
+ const pathParamWarnings = /* @__PURE__ */ new Set();
50721
+ for (const param of resolvedParameters(root, pathItem, operation)) {
50722
+ if (String(param.in || "").toLowerCase() !== "path") continue;
50723
+ const name = String(param.name || "");
50724
+ if (name && !checkedKeys.has(`path:${name.toLowerCase()}`)) {
50725
+ pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
50726
+ }
50727
+ }
50728
+ opWarnings.push(...pathParamWarnings);
50037
50729
  operations.push({
50038
- id: `${lowerMethod.toUpperCase()} ${path8}`,
50730
+ id: operationId,
50039
50731
  method: lowerMethod.toUpperCase(),
50040
50732
  path: path8,
50041
50733
  pointer: `/paths/${path8.replace(/~/g, "~0").replace(/\//g, "~1")}/${lowerMethod}`,
50042
50734
  candidates,
50043
50735
  responses: contractResponses,
50044
50736
  requiredParameters,
50045
- requestBody: collectRequestBody(root, operation),
50737
+ declaredQueryParameters: collectDeclaredQueryParameters(root, pathItem, operation),
50738
+ parameterChecks,
50739
+ requestBody: collectRequestBody(root, operation, version, operationId, opWarnings),
50740
+ security: collectSecurityRuntimeChecks(root, operation),
50046
50741
  warnings: opWarnings
50047
50742
  });
50048
50743
  }
@@ -50063,35 +50758,6 @@ function buildContractIndex(root) {
50063
50758
  return { operations, version, warnings };
50064
50759
  }
50065
50760
 
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
50761
  // src/lib/spec/collection-contracts.ts
50096
50762
  var CONTRACT_SIZE_LIMITS = {
50097
50763
  warnTestScriptBytes: 256e3,
@@ -50180,29 +50846,51 @@ function matchOperation(index, request) {
50180
50846
  function assignValidator(lines, target, source) {
50181
50847
  lines.push(`${target} = ${source};`);
50182
50848
  }
50183
- function buildValidatorAssignments(operation) {
50849
+ function tryCompile(target, schema2, lines, warnings, context) {
50850
+ try {
50851
+ assignValidator(lines, target, compileSchemaValidatorCode(schema2));
50852
+ } catch (error2) {
50853
+ lines.push(`${target} = { skip: true };`);
50854
+ 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)})`);
50855
+ }
50856
+ }
50857
+ function buildValidatorAssignments(operation, warnings) {
50184
50858
  const lines = ["var validators = {};"];
50859
+ const parameterChecks = operation.parameterChecks ?? [];
50860
+ if (parameterChecks.length > 0) {
50861
+ lines.push("var paramValidators = {};");
50862
+ for (const check of parameterChecks) {
50863
+ tryCompile(`paramValidators[${JSON.stringify(`${check.in}:${check.name.toLowerCase()}`)}]`, check.schema, lines, warnings, `parameter ${check.in}:${check.name} schema on ${operation.id}`);
50864
+ }
50865
+ }
50866
+ const bodySchemas = Object.entries(operation.requestBody?.jsonSchemas ?? {});
50867
+ if (bodySchemas.length > 0) {
50868
+ lines.push("var requestBodyValidators = {};");
50869
+ for (const [base, schema2] of bodySchemas) {
50870
+ tryCompile(`requestBodyValidators[${JSON.stringify(base)}]`, schema2, lines, warnings, `request body schema for ${base} on ${operation.id}`);
50871
+ }
50872
+ }
50185
50873
  for (const [status, response] of Object.entries(operation.responses)) {
50186
50874
  lines.push(`validators[${JSON.stringify(status)}] = validators[${JSON.stringify(status)}] || {};`);
50187
50875
  for (const [mediaType, media] of Object.entries(response.content)) {
50188
50876
  if (media.schema !== void 0 && !media.unsupported) {
50189
- assignValidator(lines, `validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, compileSchemaValidatorCode(media.schema));
50877
+ tryCompile(`validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, media.schema, lines, warnings, `response schema for ${mediaType} on ${operation.id} status ${status}`);
50190
50878
  }
50191
50879
  }
50192
50880
  for (const header of response.headers) {
50193
50881
  if (header.schema !== void 0 && !header.unsupported) {
50194
50882
  lines.push(`validators[${JSON.stringify(status)}].__headers = validators[${JSON.stringify(status)}].__headers || {};`);
50195
- assignValidator(lines, `validators[${JSON.stringify(status)}].__headers[${JSON.stringify(header.name.toLowerCase())}]`, compileSchemaValidatorCode(header.schema));
50883
+ 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
50884
  }
50197
50885
  }
50198
50886
  }
50199
50887
  return lines;
50200
50888
  }
50201
- function createContractScript(operation) {
50202
- const contract = { method: operation.method, path: operation.path, responses: operation.responses };
50889
+ function createContractScript(operation, warnings = []) {
50890
+ const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks };
50203
50891
  return [
50204
50892
  `var contract = JSON.parse(${JSON.stringify(JSON.stringify(contract))});`,
50205
- ...buildValidatorAssignments(operation),
50893
+ ...buildValidatorAssignments(operation, warnings),
50206
50894
  "function selectedResponseContract() {",
50207
50895
  " var status = String(pm.response.code);",
50208
50896
  " if (contract.responses[status]) return { key: status, value: contract.responses[status] };",
@@ -50216,6 +50904,15 @@ function createContractScript(operation) {
50216
50904
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
50217
50905
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
50218
50906
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
50907
+ "function coerceBySchema(value, schema) {",
50908
+ " var type = schema && schema.type;",
50909
+ " var types = Array.isArray(type) ? type : [type];",
50910
+ ' if ((types.indexOf("integer") !== -1 || types.indexOf("number") !== -1) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(String(value).trim())) return Number(value);',
50911
+ ' if (types.indexOf("boolean") !== -1 && (value === "true" || value === "false")) return value === "true";',
50912
+ " return value;",
50913
+ "}",
50914
+ '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; }',
50915
+ "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
50916
  "function mediaScore(expected, actual) {",
50220
50917
  " var e = mediaParts(expected); var a = mediaParts(actual);",
50221
50918
  " if (!e.raw || !a.raw) return 0;",
@@ -50246,7 +50943,11 @@ function createContractScript(operation) {
50246
50943
  " if (!actual) return;",
50247
50944
  ' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
50248
50945
  " var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
50249
- ' if (headerValidator && !headerValidator(actual)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
50946
+ " if (!headerValidator || headerValidator.skip) return;",
50947
+ " var expected;",
50948
+ ' if (header.items) { var joined = String(actual).trim(); expected = joined === "" ? [] : joined.split(",").map(function (entry) { return coerceBySchema(entry.trim(), header.items); }); }',
50949
+ " else expected = coerceBySchema(actual, header.schema);",
50950
+ ' if (!headerValidator(expected)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
50250
50951
  " });",
50251
50952
  "});",
50252
50953
  "pm.test('Response body matches OpenAPI body contract', function () {",
@@ -50274,11 +50975,122 @@ function createContractScript(operation) {
50274
50975
  ' if (media.media.unsupported) pm.expect.fail("OpenAPI schema unsupported for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + media.media.unsupported);',
50275
50976
  " if (!media.media.schema) { return; }",
50276
50977
  " var validate = validators[selected.key] && validators[selected.key][media.expected];",
50978
+ " if (validate && validate.skip) return;",
50277
50979
  ' if (!validate) pm.expect.fail("OpenAPI schema validator was not generated for " + media.expected);',
50278
50980
  ' var actual = mediaParts(pm.response.headers.get("Content-Type") || "");',
50279
50981
  " var value = isJsonSubtype(actual.subtype) ? pm.response.json() : responseText();",
50280
- ' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") pm.expect.fail("Non-JSON response schema validation unsupported for " + contract.method + " " + contract.path);',
50982
+ // Non-JSON object-schema bodies skip schema validation instead of
50983
+ // failing; the index emits CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED so the
50984
+ // skip is visible at instrumentation time.
50985
+ ' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") { return; }',
50281
50986
  ' if (!validate(value)) pm.expect.fail("OpenAPI schema validation failed for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + JSON.stringify(validate.errors || []));',
50987
+ "});",
50988
+ ...operation.security ? [
50989
+ "pm.test('Request carries credentials required by OpenAPI security', function () {",
50990
+ " function satisfied(check) {",
50991
+ " if (!check.checkable) return true;",
50992
+ ' if (check.in === "query") return hasQueryParam(check.name);',
50993
+ ' 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); }); }',
50994
+ ' if (check.prefix) { return requestHeader("Authorization").toLowerCase().indexOf(check.prefix.toLowerCase()) === 0; }',
50995
+ ' if (check.kind === "oauth2" || check.kind === "openIdConnect") { return Boolean(requestHeader("Authorization")) || hasQueryParam("access_token"); }',
50996
+ " return Boolean(requestHeader(check.name));",
50997
+ " }",
50998
+ " var alternatives = contract.security || [];",
50999
+ " var ok = alternatives.some(function (alternative) { return alternative.every(function (check) { return satisfied(check); }); });",
51000
+ ' 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(" | "));',
51001
+ "});"
51002
+ ] : [],
51003
+ ...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
51004
+ "pm.test('Request parameters match OpenAPI schemas', function () {",
51005
+ ' 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; }',
51006
+ ' 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; }',
51007
+ ' 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; }',
51008
+ ' 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; }',
51009
+ ' 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; }); }',
51010
+ // Server path prefixes sit ahead of the template segments, so the
51011
+ // template aligns against the trailing request segments.
51012
+ ' 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; }',
51013
+ ' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
51014
+ ' function splitDelimited(value, decode) { if (decode === "csv") return value.split(","); if (decode === "ssv") return value.split(/%20| /); return value.split(/%7C|\\|/i); }',
51015
+ " function decodeComponent(value) { try { return decodeURIComponent(value); } catch (ignored) { return value; } }",
51016
+ " (contract.parameters || []).forEach(function (param) {",
51017
+ ' var key = param.in + ":" + String(param.name).toLowerCase();',
51018
+ " var validate = paramValidators[key];",
51019
+ " if (!validate || validate.skip) return;",
51020
+ " var value;",
51021
+ ' if (param.decode === "multi") {',
51022
+ " var entries = queryValues(String(param.name).toLowerCase());",
51023
+ ' 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; }',
51024
+ ' if (param.allowEmptyValue && entries.length === 1 && entries[0] === "") return;',
51025
+ " if (entries.some(isPlaceholder)) return;",
51026
+ " value = entries.map(function (entry) { return coerceBySchema(decodeComponent(entry), param.items); });",
51027
+ " } else if (param.decode) {",
51028
+ ' var joined = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
51029
+ ' if (joined === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
51030
+ ' if (joined === "" && param.allowEmptyValue) return;',
51031
+ " if (isPlaceholder(joined)) return;",
51032
+ ' var parts = joined === "" ? [] : splitDelimited(String(joined), param.decode);',
51033
+ // HTTP header lists allow optional whitespace after the comma, so
51034
+ // header-sourced items are trimmed; query values stay literal.
51035
+ ' if (param.in === "header") parts = parts.map(function (entry) { return entry.trim(); });',
51036
+ " if (parts.some(isPlaceholder)) return;",
51037
+ // Query values arrive percent-encoded while the server validates the
51038
+ // decoded form; header values are never percent-encoded.
51039
+ ' value = parts.map(function (entry) { return coerceBySchema(param.in === "query" ? decodeComponent(entry) : entry, param.items); });',
51040
+ ' } else if (param.in === "path") {',
51041
+ " value = pathParamValue(param.name);",
51042
+ " if (value === undefined) return;",
51043
+ ' if (isPlaceholder(value) || value.charAt(0) === ":" || value.charAt(0) === "{") return;',
51044
+ " value = coerceBySchema(value, param.schema);",
51045
+ ' } else if (param.in === "cookie") {',
51046
+ " value = cookieValue(param.name);",
51047
+ ' if (value === undefined) { if (param.required) pm.expect.fail("Required cookie parameter " + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
51048
+ " if (isPlaceholder(value)) return;",
51049
+ " value = coerceBySchema(value, param.schema);",
51050
+ " } else {",
51051
+ ' value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
51052
+ ' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
51053
+ ' if (value === "" && param.allowEmptyValue) return;',
51054
+ " if (isPlaceholder(value)) return;",
51055
+ " if (param.content) {",
51056
+ " var parsed;",
51057
+ ' 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; }',
51058
+ ' if (!validate(parsed)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
51059
+ " return;",
51060
+ " }",
51061
+ " value = coerceBySchema(value, param.schema);",
51062
+ " }",
51063
+ ' if (!validate(value)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
51064
+ " });",
51065
+ "});"
51066
+ ] : [],
51067
+ ...operation.requestBody?.jsonSchemas && Object.keys(operation.requestBody.jsonSchemas).length > 0 ? [
51068
+ "pm.test('Request body matches OpenAPI request schema', function () {",
51069
+ " var body = pm.request.body;",
51070
+ ' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
51071
+ " if (!raw.trim()) return;",
51072
+ ' if (/"<[^"<>]*>"/.test(raw) || raw.indexOf("{{") !== -1) return;',
51073
+ ' var validate = requestBodyValidators[mediaBase(requestHeader("Content-Type"))];',
51074
+ " if (!validate || validate.skip) return;",
51075
+ " var parsed;",
51076
+ // Unquoted generator placeholders such as {"count": <long>} break
51077
+ // JSON.parse; a parse failure alongside an angle-bracket token is
51078
+ // treated as a placeholder body rather than drift.
51079
+ ' 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; }',
51080
+ ' if (!validate(parsed)) pm.expect.fail("Request body failed OpenAPI request schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
51081
+ "});"
51082
+ ] : [],
51083
+ "pm.test('Content-Length is consistent with OpenAPI body expectations', function () {",
51084
+ ' var raw = pm.response.headers.get("Content-Length");',
51085
+ " if (raw === null || raw === undefined) return;",
51086
+ // Combined duplicate Content-Length values ("5, 5") fail the integer
51087
+ // syntax check on purpose: RFC 9110 tolerates identical repeats, but a
51088
+ // contract test surfacing them is strict by design.
51089
+ ' if (!/^[0-9]+$/.test(String(raw).trim())) pm.expect.fail("Content-Length header is not a non-negative integer: " + raw);',
51090
+ ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
51091
+ ' if (pm.response.headers.get("Content-Encoding")) return;',
51092
+ " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
51093
+ ' 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
51094
  "});"
50283
51095
  ];
50284
51096
  }
@@ -50371,14 +51183,160 @@ function assertStaticRequestShape(operation, request) {
50371
51183
  if (!contentType) {
50372
51184
  throw new Error(`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} missing required request Content-Type`);
50373
51185
  }
50374
- const actual = contentType.toLowerCase().split(";")[0]?.trim();
50375
- const matches = operation.requestBody.contentTypes.some((expected) => expected.toLowerCase() === actual);
51186
+ const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
51187
+ const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
50376
51188
  if (!matches) {
50377
51189
  throw new Error(
50378
51190
  `CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} request Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
50379
51191
  );
50380
51192
  }
50381
51193
  }
51194
+ const warnings = collectStaticBodyWarnings(operation, request, contentType);
51195
+ if (operation.requestBody && !operation.requestBody.required && operation.requestBody.contentTypes.length > 0 && hasRequestBody(request) && contentType) {
51196
+ const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
51197
+ const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
51198
+ if (!matches) {
51199
+ warnings.push(
51200
+ `CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} optional request body Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
51201
+ );
51202
+ }
51203
+ }
51204
+ return warnings;
51205
+ }
51206
+ function requestBodyFieldNames(request, base) {
51207
+ const body = asRecord5(request.body);
51208
+ if (!body) return void 0;
51209
+ if (base === "application/json" || /\+json$/.test(base)) {
51210
+ if (body.mode !== "raw" || typeof body.raw !== "string") return void 0;
51211
+ let parsed;
51212
+ try {
51213
+ parsed = JSON.parse(body.raw);
51214
+ } catch {
51215
+ return void 0;
51216
+ }
51217
+ const record = asRecord5(parsed);
51218
+ return record ? Object.keys(record) : void 0;
51219
+ }
51220
+ const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
51221
+ if (!mode || !Array.isArray(body[mode])) return void 0;
51222
+ return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true).map((entry) => String(entry.key || "")).filter(Boolean);
51223
+ }
51224
+ function requestBodyEntries(request, base) {
51225
+ const body = asRecord5(request.body);
51226
+ const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
51227
+ if (!body || !mode || !Array.isArray(body[mode])) return void 0;
51228
+ return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true);
51229
+ }
51230
+ function mediaTypeMatchesPattern(pattern, actual) {
51231
+ return pattern.split(",").some((candidate) => {
51232
+ const entry = (candidate.split(";")[0] ?? "").trim();
51233
+ if (!entry) return false;
51234
+ if (entry === "*/*" || entry === actual) return true;
51235
+ if (entry.endsWith("/*")) return actual.startsWith(entry.slice(0, -1));
51236
+ return false;
51237
+ });
51238
+ }
51239
+ function isJsonEncodingContentType(declared) {
51240
+ return declared.split(",").some((candidate) => {
51241
+ const entry = (candidate.split(";")[0] ?? "").trim();
51242
+ return entry === "application/json" || entry.endsWith("+json");
51243
+ });
51244
+ }
51245
+ function isPlaceholderValue(value) {
51246
+ return /^<[^>]*>$/.test(value.trim()) || value.includes("{{");
51247
+ }
51248
+ function collectStaticEncodingWarnings(operation, request, base, rule) {
51249
+ const encodings = rule.encodings;
51250
+ if (!encodings) return [];
51251
+ const multipart = base === "multipart/form-data";
51252
+ const entries = requestBodyEntries(request, base);
51253
+ if (!entries) return [];
51254
+ const warnings = [];
51255
+ const part = multipart ? "multipart" : "urlencoded";
51256
+ for (const [field, encoding] of Object.entries(encodings)) {
51257
+ for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
51258
+ if (multipart && encoding.binary && String(entry.type || "") !== "file") {
51259
+ warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
51260
+ }
51261
+ if (multipart && encoding.contentType) {
51262
+ const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
51263
+ if (!actual) {
51264
+ warnings.push(
51265
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
51266
+ );
51267
+ } else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
51268
+ warnings.push(
51269
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
51270
+ );
51271
+ }
51272
+ }
51273
+ if (encoding.contentType && isJsonEncodingContentType(encoding.contentType) && String(entry.type || "text") !== "file") {
51274
+ const value = typeof entry.value === "string" ? entry.value : "";
51275
+ if (value.trim() && !isPlaceholderValue(value)) {
51276
+ try {
51277
+ JSON.parse(value);
51278
+ } catch {
51279
+ warnings.push(
51280
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated ${part} field ${field} declares JSON encoding ${encoding.contentType} but its value is not parseable JSON`
51281
+ );
51282
+ }
51283
+ }
51284
+ }
51285
+ }
51286
+ }
51287
+ return warnings;
51288
+ }
51289
+ function coerceFormValue(value, schema2) {
51290
+ const record = asRecord5(schema2);
51291
+ const type2 = record?.type;
51292
+ const types2 = Array.isArray(type2) ? type2 : [type2];
51293
+ if ((types2.includes("integer") || types2.includes("number")) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(value.trim())) return Number(value);
51294
+ if (types2.includes("boolean") && (value === "true" || value === "false")) return value === "true";
51295
+ return value;
51296
+ }
51297
+ function collectStaticFieldSchemaWarnings(operation, request, base, rule) {
51298
+ const fieldSchemas = rule.fieldSchemas;
51299
+ if (!fieldSchemas) return [];
51300
+ const entries = requestBodyEntries(request, base);
51301
+ if (!entries) return [];
51302
+ const part = base === "multipart/form-data" ? "multipart" : "urlencoded";
51303
+ const warnings = [];
51304
+ for (const [field, schema2] of Object.entries(fieldSchemas)) {
51305
+ const validate2 = compileSchemaValidator(schema2);
51306
+ if (!validate2) continue;
51307
+ for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
51308
+ if (String(entry.type || "text") === "file") continue;
51309
+ const value = typeof entry.value === "string" ? entry.value : "";
51310
+ if (!value.trim() || isPlaceholderValue(value)) continue;
51311
+ if (!validate2(coerceFormValue(value, schema2))) {
51312
+ warnings.push(`CONTRACT_FORM_FIELD_SCHEMA_MISMATCH: ${operation.id} generated ${part} field ${field} value does not match its schema`);
51313
+ }
51314
+ }
51315
+ }
51316
+ return warnings;
51317
+ }
51318
+ function collectStaticBodyWarnings(operation, request, contentType) {
51319
+ const rules = operation.requestBody?.fieldRules;
51320
+ if (!rules) return [];
51321
+ const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
51322
+ const rule = rules[base];
51323
+ if (!rule) return [];
51324
+ const warnings = [
51325
+ ...collectStaticEncodingWarnings(operation, request, base, rule),
51326
+ ...collectStaticFieldSchemaWarnings(operation, request, base, rule)
51327
+ ];
51328
+ const names = requestBodyFieldNames(request, base);
51329
+ if (!names) return warnings;
51330
+ const present = new Set(names);
51331
+ const missing = rule.required.filter((name) => !present.has(name));
51332
+ if (missing.length > 0) {
51333
+ warnings.push(`CONTRACT_REQUEST_BODY_INCOMPLETE: ${operation.id} generated request body is missing required properties: ${missing.join(", ")}`);
51334
+ }
51335
+ const readOnlySent = rule.readOnly.filter((name) => present.has(name));
51336
+ if (readOnlySent.length > 0) {
51337
+ warnings.push(`CONTRACT_READONLY_PROPERTY_IN_REQUEST: ${operation.id} generated request body includes readOnly properties: ${readOnlySent.join(", ")}`);
51338
+ }
51339
+ return warnings;
50382
51340
  }
50383
51341
  function validateScript(script) {
50384
51342
  const source = script.join("\n");
@@ -50427,9 +51385,12 @@ function instrumentContractCollection(collection, index) {
50427
51385
  if (result.operation) {
50428
51386
  const previous = covered.get(result.operation.id);
50429
51387
  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);
51388
+ warnings.push(...assertStaticRequestShape(result.operation, request));
51389
+ for (const name of [...requestQueryNames(request)].filter((entry) => !result.operation.declaredQueryParameters.includes(entry))) {
51390
+ warnings.push(`CONTRACT_UNDOCUMENTED_QUERY_PARAM: ${result.operation.id} generated request sends query parameter ${name} that the OpenAPI operation does not declare`);
51391
+ }
50431
51392
  covered.set(result.operation.id, String(item.name || "<unnamed>"));
50432
- script = createContractScript(result.operation);
51393
+ script = createContractScript(result.operation, warnings);
50433
51394
  } else if (result.ambiguous && result.ambiguous.length > 0) {
50434
51395
  script = createMappingFailureScript(`Ambiguous OpenAPI operation match for request ${result.method} ${result.path}: ${result.ambiguous.map((entry) => entry.id).join(", ")}`);
50435
51396
  } else {