@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/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"
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 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,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 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 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
- if (asRecord3(propertySchema)?.writeOnly === true) writeOnlyProperties.add(propertyName);
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 (key === "items" && Array.isArray(value) && options.version === "3.0") {
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 (writeOnlyProperties.has(propertyName)) continue;
49789
- const child3 = normalizeSchema(root, propertySchema, { ...options, depth: depth + 1, rootSchema: false });
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 = mergeRequiredWithoutWriteOnly(value, writeOnlyProperties);
49898
+ const required = mergeRequiredWithoutStripped(value, strippedProperties);
49799
49899
  if (required) normalized.required = required;
49800
49900
  continue;
49801
49901
  }
49802
- const child2 = normalizeSchema(root, value, { ...options, depth: depth + 1, rootSchema: false });
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 (options.rootSchema) normalized.$schema = options.dialect ?? rootDialect(root, options.version, record);
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 = options.dialect ?? rootDialect(root, options.version, record);
49949
+ if (options.rootSchema) wrapper.$schema = ctx.dialect;
49820
49950
  return wrapper;
49821
49951
  }
49822
49952
  }
49823
49953
  return normalized;
49824
49954
  }
49825
- function packSchema(root, schema2, version) {
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 normalized = normalizeSchema(root, schema2, { version, rootSchema: true, dialect });
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
- return message ? unsupported(message) : { schema: normalized };
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
- function collectRequestBody(root, operation) {
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 || body.required !== true) return void 0;
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 responseContent(root, version, response) {
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 schema2 = asRecord4(mediaObject)?.schema;
49977
- media[contentType] = schema2 === void 0 ? {} : packSchema(root, schema2, version);
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
- return Object.entries(headers).map(([name, rawHeader]) => {
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) return { name, required: true, unsupported: "Unresolved response 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) 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
- });
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
- const content = responseContent(root, version, response);
50020
- const headers = responseHeaders(root, version, response);
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
- requestBody: collectRequestBody(root, operation),
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 buildValidatorAssignments(operation) {
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
- assignValidator(lines, `validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, compileSchemaValidatorCode(media.schema));
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
- assignValidator(lines, `validators[${JSON.stringify(status)}].__headers[${JSON.stringify(header.name.toLowerCase())}]`, compileSchemaValidatorCode(header.schema));
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
- ' if (headerValidator && !headerValidator(actual)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
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
- ' 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);',
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 {