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