@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/cli.cjs CHANGED
@@ -47930,8 +47930,6 @@ var COMPATIBLE_SCHEMA_URIS = /* @__PURE__ */ new Set([
47930
47930
  `${DRAFT_2020_12_SCHEMA_URI}#`
47931
47931
  ]);
47932
47932
  var ASSERTION_KEYS = /* @__PURE__ */ new Set([
47933
- "$defs",
47934
- "$id",
47935
47933
  "$ref",
47936
47934
  "$schema",
47937
47935
  "additionalItems",
@@ -47976,6 +47974,10 @@ var ASSERTION_KEYS = /* @__PURE__ */ new Set([
47976
47974
  ]);
47977
47975
  var STRIP_KEYS = /* @__PURE__ */ new Set([
47978
47976
  "title",
47977
+ "$comment",
47978
+ "$defs",
47979
+ "definitions",
47980
+ "$id",
47979
47981
  "description",
47980
47982
  "default",
47981
47983
  "example",
@@ -47986,8 +47988,32 @@ var STRIP_KEYS = /* @__PURE__ */ new Set([
47986
47988
  "xml",
47987
47989
  "externalDocs",
47988
47990
  "discriminator",
47989
- "format"
47991
+ "contentEncoding",
47992
+ "contentMediaType",
47993
+ "contentSchema"
47994
+ ]);
47995
+ var ASSERTED_FORMATS = /* @__PURE__ */ new Set([
47996
+ "date-time",
47997
+ "date",
47998
+ "time",
47999
+ "duration",
48000
+ "email",
48001
+ "hostname",
48002
+ "ipv4",
48003
+ "ipv6",
48004
+ "uri",
48005
+ "uri-reference",
48006
+ "uri-template",
48007
+ "uuid",
48008
+ "json-pointer",
48009
+ "relative-json-pointer",
48010
+ "regex"
47990
48011
  ]);
48012
+ var INT32_MIN = -2147483648;
48013
+ var INT32_MAX = 2147483647;
48014
+ var MAX_REFERENCED_SCHEMAS = 400;
48015
+ var DRAFT_2020_12_ONLY_KEYS = /* @__PURE__ */ new Set(["prefixItems", "dependentRequired", "dependentSchemas", "minContains", "maxContains", "unevaluatedItems", "unevaluatedProperties"]);
48016
+ var DRAFT_07_ONLY_KEYS = /* @__PURE__ */ new Set(["dependencies", "additionalItems"]);
47991
48017
  function asRecord3(value) {
47992
48018
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
47993
48019
  return value;
@@ -48005,8 +48031,8 @@ function resolvePointer(root, ref) {
48005
48031
  function unsupported(message) {
48006
48032
  return { unsupported: message };
48007
48033
  }
48008
- function mergeRequiredWithoutWriteOnly(required, writeOnlyProperties) {
48009
- const values = asArray(required).map((entry) => String(entry)).filter((entry) => !writeOnlyProperties.has(entry));
48034
+ function mergeRequiredWithoutStripped(required, strippedProperties) {
48035
+ const values = asArray(required).map((entry) => String(entry)).filter((entry) => !strippedProperties.has(entry));
48010
48036
  return values.length > 0 ? values : void 0;
48011
48037
  }
48012
48038
  function hasUnsupported(child) {
@@ -48022,11 +48048,31 @@ function rootDialect(root, version, schemaRecord) {
48022
48048
  if (declared.includes("draft-07")) return DRAFT_07_SCHEMA_URI;
48023
48049
  return DRAFT_2020_12_SCHEMA_URI;
48024
48050
  }
48025
- function normalizeSchema(root, schema2, options) {
48026
- const depth = options.depth ?? 0;
48027
- if (depth > 50) return unsupported("OpenAPI schema reference depth exceeded 50");
48051
+ function registerDef(ctx, ref) {
48052
+ const existing = ctx.defs.get(ref);
48053
+ if (existing) return existing;
48054
+ if (ctx.defs.size >= MAX_REFERENCED_SCHEMAS) {
48055
+ const entry2 = { name: "overflow", unsupported: `OpenAPI schema reference graph exceeded ${MAX_REFERENCED_SCHEMAS} referenced schemas` };
48056
+ return entry2;
48057
+ }
48058
+ const entry = { name: `d${ctx.defs.size}` };
48059
+ ctx.defs.set(ref, entry);
48060
+ const resolved = resolvePointer(ctx.root, ref);
48061
+ if (resolved === void 0) {
48062
+ entry.unsupported = `Unresolved OpenAPI $ref: ${ref}`;
48063
+ return entry;
48064
+ }
48065
+ const normalized = normalizeSchema(ctx, resolved, { depth: 0, rootSchema: false });
48066
+ const bad = hasUnsupported(normalized);
48067
+ if (bad) entry.unsupported = bad;
48068
+ else entry.schema = normalized;
48069
+ return entry;
48070
+ }
48071
+ function normalizeSchema(ctx, schema2, options) {
48072
+ const depth = options.depth;
48073
+ if (depth > 50) return unsupported("OpenAPI schema nesting depth exceeded 50");
48028
48074
  if (Array.isArray(schema2)) {
48029
- const normalized2 = schema2.map((entry) => normalizeSchema(root, entry, { ...options, depth: depth + 1, rootSchema: false }));
48075
+ const normalized2 = schema2.map((entry) => normalizeSchema(ctx, entry, { depth: depth + 1, rootSchema: false }));
48030
48076
  const bad = normalized2.map(hasUnsupported).find(Boolean);
48031
48077
  return bad ? unsupported(bad) : normalized2;
48032
48078
  }
@@ -48035,40 +48081,42 @@ function normalizeSchema(root, schema2, options) {
48035
48081
  const ref = typeof record.$ref === "string" ? record.$ref : "";
48036
48082
  if (ref) {
48037
48083
  if (!ref.startsWith("#/")) return unsupported(`External OpenAPI $ref remained unresolved: ${ref}`);
48038
- const stack = options.stack ?? [];
48039
- if (stack.includes(ref)) return unsupported(`Recursive OpenAPI $ref is unsupported: ${ref}`);
48040
- const resolved = resolvePointer(root, ref);
48041
- if (resolved === void 0) return unsupported(`Unresolved OpenAPI $ref: ${ref}`);
48042
- const siblingEntries = Object.entries(record).filter(([key]) => key !== "$ref");
48043
- const resolvedSchema = normalizeSchema(root, resolved, {
48044
- ...options,
48045
- depth: depth + 1,
48046
- rootSchema: false,
48047
- stack: [...stack, ref]
48048
- });
48049
- const resolvedUnsupported = hasUnsupported(resolvedSchema);
48050
- if (resolvedUnsupported) return unsupported(resolvedUnsupported);
48051
- if (options.version === "3.0" || siblingEntries.length === 0) return resolvedSchema;
48052
- const siblingSchema = normalizeSchema(root, Object.fromEntries(siblingEntries), {
48053
- ...options,
48054
- depth: depth + 1,
48055
- rootSchema: false,
48056
- stack: [...stack, ref]
48057
- });
48084
+ const siblingEntries = ctx.version === "3.1" ? Object.entries(record).filter(([key]) => key !== "$ref") : [];
48085
+ const inlineStack = options.inlineStack ?? [];
48086
+ if (options.rootSchema && siblingEntries.length === 0 && !inlineStack.includes(ref)) {
48087
+ const resolved = resolvePointer(ctx.root, ref);
48088
+ if (resolved === void 0) return unsupported(`Unresolved OpenAPI $ref: ${ref}`);
48089
+ const inlined = normalizeSchema(ctx, resolved, { depth, rootSchema: true, inlineStack: [...inlineStack, ref] });
48090
+ const bad = hasUnsupported(inlined);
48091
+ return bad ? unsupported(bad) : inlined;
48092
+ }
48093
+ const entry = registerDef(ctx, ref);
48094
+ if (entry.unsupported) return unsupported(entry.unsupported);
48095
+ const refNode = { $ref: `#/$defs/${entry.name}` };
48096
+ if (siblingEntries.length === 0) {
48097
+ if (options.rootSchema) refNode.$schema = ctx.dialect;
48098
+ return refNode;
48099
+ }
48100
+ const siblingSchema = normalizeSchema(ctx, Object.fromEntries(siblingEntries), { depth: depth + 1, rootSchema: false });
48058
48101
  const siblingUnsupported = hasUnsupported(siblingSchema);
48059
48102
  if (siblingUnsupported) return unsupported(siblingUnsupported);
48060
- const wrapper = { allOf: [resolvedSchema, siblingSchema] };
48061
- if (options.rootSchema) wrapper.$schema = options.dialect ?? rootDialect(root, options.version, record);
48103
+ const wrapper = { allOf: [refNode, siblingSchema] };
48104
+ if (options.rootSchema) wrapper.$schema = ctx.dialect;
48062
48105
  return wrapper;
48063
48106
  }
48064
48107
  const sourceSchema = { ...record };
48065
- const nullable = sourceSchema.nullable === true && options.version === "3.0";
48108
+ const nullable = sourceSchema.nullable === true && ctx.version === "3.0";
48066
48109
  delete sourceSchema.nullable;
48067
- const writeOnlyProperties = /* @__PURE__ */ new Set();
48110
+ const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
48111
+ const strippedProperties = /* @__PURE__ */ new Set();
48068
48112
  const rawProperties = asRecord3(sourceSchema.properties);
48069
48113
  if (rawProperties) {
48070
48114
  for (const [propertyName, propertySchema] of Object.entries(rawProperties)) {
48071
- if (asRecord3(propertySchema)?.writeOnly === true) writeOnlyProperties.add(propertyName);
48115
+ let flagSource = asRecord3(propertySchema);
48116
+ if (typeof flagSource?.$ref === "string" && flagSource.$ref.startsWith("#/")) {
48117
+ flagSource = asRecord3(resolvePointer(ctx.root, flagSource.$ref)) ?? flagSource;
48118
+ }
48119
+ if (flagSource?.[directionStrippedFlag] === true) strippedProperties.add(propertyName);
48072
48120
  }
48073
48121
  }
48074
48122
  const normalized = {};
@@ -48095,17 +48143,69 @@ function normalizeSchema(root, schema2, options) {
48095
48143
  delete normalized.maximum;
48096
48144
  continue;
48097
48145
  }
48146
+ if (key === "format") {
48147
+ if (typeof value === "string" && ASSERTED_FORMATS.has(value)) normalized.format = value;
48148
+ continue;
48149
+ }
48098
48150
  if (!ASSERTION_KEYS.has(key)) return unsupported(`Unsupported OpenAPI schema keyword: ${key}`);
48099
- if (key === "items" && Array.isArray(value) && options.version === "3.0") {
48151
+ if (ctx.dialect === DRAFT_07_SCHEMA_URI && DRAFT_2020_12_ONLY_KEYS.has(key)) {
48152
+ return unsupported(`${key} requires the JSON Schema 2020-12 dialect`);
48153
+ }
48154
+ if (ctx.dialect === DRAFT_2020_12_SCHEMA_URI && DRAFT_07_ONLY_KEYS.has(key)) {
48155
+ return unsupported(`${key} is a draft-07 keyword and is unsupported under JSON Schema 2020-12`);
48156
+ }
48157
+ if (key === "items" && Array.isArray(value) && ctx.version === "3.0") {
48100
48158
  return unsupported("Tuple array items are unsupported in OpenAPI 3.0");
48101
48159
  }
48160
+ if (key === "patternProperties" || key === "dependentSchemas") {
48161
+ const map2 = asRecord3(value);
48162
+ if (!map2) continue;
48163
+ const next = {};
48164
+ for (const [mapKey, mapSchema] of Object.entries(map2)) {
48165
+ const child2 = normalizeSchema(ctx, mapSchema, { depth: depth + 1, rootSchema: false });
48166
+ const bad2 = hasUnsupported(child2);
48167
+ if (bad2) return unsupported(bad2);
48168
+ next[mapKey] = child2;
48169
+ }
48170
+ normalized[key] = next;
48171
+ continue;
48172
+ }
48173
+ if (key === "dependentRequired") {
48174
+ const map2 = asRecord3(value);
48175
+ if (!map2) continue;
48176
+ for (const names of Object.values(map2)) {
48177
+ if (!Array.isArray(names) || names.some((name) => typeof name !== "string")) {
48178
+ return unsupported("dependentRequired requires string array values");
48179
+ }
48180
+ }
48181
+ normalized.dependentRequired = value;
48182
+ continue;
48183
+ }
48184
+ if (key === "dependencies") {
48185
+ const map2 = asRecord3(value);
48186
+ if (!map2) continue;
48187
+ const next = {};
48188
+ for (const [mapKey, dependency] of Object.entries(map2)) {
48189
+ if (Array.isArray(dependency)) {
48190
+ if (dependency.some((name) => typeof name !== "string")) return unsupported("dependencies requires string array or schema values");
48191
+ next[mapKey] = dependency;
48192
+ continue;
48193
+ }
48194
+ const child2 = normalizeSchema(ctx, dependency, { depth: depth + 1, rootSchema: false });
48195
+ const bad2 = hasUnsupported(child2);
48196
+ if (bad2) return unsupported(bad2);
48197
+ next[mapKey] = child2;
48198
+ }
48199
+ normalized.dependencies = next;
48200
+ continue;
48201
+ }
48102
48202
  if (key === "properties") {
48103
48203
  const properties = asRecord3(value);
48104
48204
  if (!properties) continue;
48105
48205
  const nextProperties = {};
48106
48206
  for (const [propertyName, propertySchema] of Object.entries(properties)) {
48107
- if (writeOnlyProperties.has(propertyName)) continue;
48108
- const child2 = normalizeSchema(root, propertySchema, { ...options, depth: depth + 1, rootSchema: false });
48207
+ if (strippedProperties.has(propertyName)) continue;
48208
+ const child2 = normalizeSchema(ctx, propertySchema, { depth: depth + 1, rootSchema: false });
48109
48209
  const bad2 = hasUnsupported(child2);
48110
48210
  if (bad2) return unsupported(bad2);
48111
48211
  nextProperties[propertyName] = child2;
@@ -48114,16 +48214,46 @@ function normalizeSchema(root, schema2, options) {
48114
48214
  continue;
48115
48215
  }
48116
48216
  if (key === "required") {
48117
- const required = mergeRequiredWithoutWriteOnly(value, writeOnlyProperties);
48217
+ const required = mergeRequiredWithoutStripped(value, strippedProperties);
48118
48218
  if (required) normalized.required = required;
48119
48219
  continue;
48120
48220
  }
48121
- const child = normalizeSchema(root, value, { ...options, depth: depth + 1, rootSchema: false });
48221
+ const child = normalizeSchema(ctx, value, { depth: depth + 1, rootSchema: false });
48122
48222
  const bad = hasUnsupported(child);
48123
48223
  if (bad) return unsupported(bad);
48124
48224
  normalized[key] = child;
48125
48225
  }
48126
- if (options.rootSchema) normalized.$schema = options.dialect ?? rootDialect(root, options.version, record);
48226
+ if (typeof normalized.minimum === "number" && typeof normalized.exclusiveMinimum === "number") {
48227
+ if (normalized.exclusiveMinimum >= normalized.minimum) delete normalized.minimum;
48228
+ else delete normalized.exclusiveMinimum;
48229
+ }
48230
+ if (typeof normalized.maximum === "number" && typeof normalized.exclusiveMaximum === "number") {
48231
+ if (normalized.exclusiveMaximum <= normalized.maximum) delete normalized.maximum;
48232
+ else delete normalized.exclusiveMaximum;
48233
+ }
48234
+ if (sourceSchema.format === "int32") {
48235
+ const type2 = normalized.type;
48236
+ const isInteger2 = type2 === "integer" || Array.isArray(type2) && type2.includes("integer");
48237
+ if (isInteger2) {
48238
+ if (typeof normalized.exclusiveMinimum === "number") {
48239
+ if (normalized.exclusiveMinimum < INT32_MIN) {
48240
+ delete normalized.exclusiveMinimum;
48241
+ normalized.minimum = INT32_MIN;
48242
+ }
48243
+ } else if (typeof normalized.minimum !== "number" || normalized.minimum < INT32_MIN) {
48244
+ normalized.minimum = INT32_MIN;
48245
+ }
48246
+ if (typeof normalized.exclusiveMaximum === "number") {
48247
+ if (normalized.exclusiveMaximum > INT32_MAX) {
48248
+ delete normalized.exclusiveMaximum;
48249
+ normalized.maximum = INT32_MAX;
48250
+ }
48251
+ } else if (typeof normalized.maximum !== "number" || normalized.maximum > INT32_MAX) {
48252
+ normalized.maximum = INT32_MAX;
48253
+ }
48254
+ }
48255
+ }
48256
+ if (options.rootSchema) normalized.$schema = ctx.dialect;
48127
48257
  if (nullable) {
48128
48258
  if (typeof normalized.type === "string") {
48129
48259
  normalized.type = [normalized.type, "null"];
@@ -48135,23 +48265,106 @@ function normalizeSchema(root, schema2, options) {
48135
48265
  const wrappedSchema = { ...normalized };
48136
48266
  delete wrappedSchema.$schema;
48137
48267
  const wrapper = { anyOf: [{ type: "null" }, wrappedSchema] };
48138
- if (options.rootSchema) wrapper.$schema = options.dialect ?? rootDialect(root, options.version, record);
48268
+ if (options.rootSchema) wrapper.$schema = ctx.dialect;
48139
48269
  return wrapper;
48140
48270
  }
48141
48271
  }
48142
48272
  return normalized;
48143
48273
  }
48144
- function packSchema(root, schema2, version) {
48274
+ function isSchemaGraphOverflow(packed) {
48275
+ return typeof packed.unsupported === "string" && packed.unsupported.startsWith("OpenAPI schema reference graph exceeded");
48276
+ }
48277
+ function packSchema(root, schema2, version, direction = "response") {
48145
48278
  try {
48279
+ if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
48280
+ if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
48146
48281
  const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
48147
- const normalized = normalizeSchema(root, schema2, { version, rootSchema: true, dialect });
48282
+ const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map() };
48283
+ const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
48148
48284
  const message = hasUnsupported(normalized);
48149
- return message ? unsupported(message) : { schema: normalized };
48285
+ if (message) return unsupported(message);
48286
+ const aliasTargets = /* @__PURE__ */ new Map();
48287
+ for (const entry of ctx.defs.values()) {
48288
+ if (entry.unsupported) return unsupported(entry.unsupported);
48289
+ const entrySchema = asRecord3(entry.schema);
48290
+ const ref = entrySchema && Object.keys(entrySchema).length === 1 && typeof entrySchema.$ref === "string" ? entrySchema.$ref : "";
48291
+ if (ref.startsWith("#/$defs/")) aliasTargets.set(entry.name, ref.slice("#/$defs/".length));
48292
+ }
48293
+ for (const start of aliasTargets.keys()) {
48294
+ const seen = /* @__PURE__ */ new Set();
48295
+ let current = start;
48296
+ while (current !== void 0 && aliasTargets.has(current)) {
48297
+ if (seen.has(current)) return unsupported("Self-referential alias schema is unsupported");
48298
+ seen.add(current);
48299
+ current = aliasTargets.get(current);
48300
+ }
48301
+ }
48302
+ if (ctx.defs.size > 0) {
48303
+ const normalizedRecord = asRecord3(normalized);
48304
+ if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
48305
+ normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
48306
+ }
48307
+ return { schema: normalized };
48150
48308
  } catch (error) {
48151
48309
  return unsupported(error instanceof Error ? error.message : String(error));
48152
48310
  }
48153
48311
  }
48154
48312
 
48313
+ // src/lib/spec/schema-validator-code.ts
48314
+ var import_schemasafe = __toESM(require_src(), 1);
48315
+ function compileSchemaValidator(schema2) {
48316
+ try {
48317
+ const validate2 = (0, import_schemasafe.validator)(schema2, {
48318
+ includeErrors: false,
48319
+ // Real-world specs legally mix enum/const with other keywords; without
48320
+ // this flag schemasafe refuses to compile them (observed in Stripe,
48321
+ // Plaid, and DigitalOcean public specs).
48322
+ allowUnusedKeywords: true,
48323
+ contentValidation: false,
48324
+ formatAssertion: true,
48325
+ isJSON: true,
48326
+ mode: "default",
48327
+ removeAdditional: false,
48328
+ requireSchema: true,
48329
+ requireStringValidation: false,
48330
+ useDefaults: false
48331
+ });
48332
+ return (value) => validate2(value);
48333
+ } catch {
48334
+ return null;
48335
+ }
48336
+ }
48337
+ function compileSchemaValidatorCode(schema2) {
48338
+ try {
48339
+ const validate2 = (0, import_schemasafe.validator)(schema2, {
48340
+ includeErrors: true,
48341
+ allErrors: true,
48342
+ // Real-world specs legally mix enum/const with other keywords; without
48343
+ // this flag schemasafe refuses to compile them (observed in Stripe,
48344
+ // Plaid, and DigitalOcean public specs).
48345
+ allowUnusedKeywords: true,
48346
+ contentValidation: false,
48347
+ formatAssertion: true,
48348
+ isJSON: true,
48349
+ mode: "default",
48350
+ removeAdditional: false,
48351
+ requireSchema: true,
48352
+ requireStringValidation: false,
48353
+ useDefaults: false
48354
+ });
48355
+ const source = validate2.toModule();
48356
+ if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
48357
+ throw new Error("schemasafe generated forbidden dynamic code");
48358
+ }
48359
+ return source;
48360
+ } catch (error) {
48361
+ throw new Error(
48362
+ `CONTRACT_SCHEMA_COMPILE_FAILED: ${error instanceof Error ? error.message : String(error)}`,
48363
+ { cause: error }
48364
+ );
48365
+ }
48366
+ }
48367
+
48155
48368
  // src/lib/spec/contract-index.ts
48156
48369
  var HTTP_METHODS = /* @__PURE__ */ new Set(["get", "put", "post", "delete", "options", "head", "patch", "trace"]);
48157
48370
  function asRecord4(value) {
@@ -48227,7 +48440,7 @@ function collectSecurityApiKeys(root, operation) {
48227
48440
  for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
48228
48441
  for (const schemeName of Object.keys(requirement)) {
48229
48442
  const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
48230
- if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header"].includes(String(scheme.in))) {
48443
+ if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["query", "header", "cookie"].includes(String(scheme.in))) {
48231
48444
  names.add(`${String(scheme.in)}:${scheme.name.toLowerCase()}`);
48232
48445
  }
48233
48446
  }
@@ -48249,12 +48462,130 @@ function collectSecuritySchemeWarnings(root, operation) {
48249
48462
  for (const schemeName of Object.keys(requirement)) {
48250
48463
  const scheme = resolveInternalRef(root, securitySchemes?.[schemeName]);
48251
48464
  warnings.add(
48252
- `CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven by dynamic contract tests`
48465
+ `CONTRACT_SECURITY_NOT_VALIDATED: security scheme ${schemeName} (${securitySchemeKind(scheme)}) is not runtime-proven beyond credential presence by dynamic contract tests`
48253
48466
  );
48254
48467
  }
48255
48468
  }
48256
48469
  return [...warnings];
48257
48470
  }
48471
+ function securityCheckFor(schemeName, scheme) {
48472
+ const kind = securitySchemeKind(scheme);
48473
+ if (scheme?.type === "apiKey" && typeof scheme.name === "string" && ["header", "query", "cookie"].includes(String(scheme.in))) {
48474
+ return { scheme: schemeName, kind, checkable: true, in: String(scheme.in), name: scheme.name };
48475
+ }
48476
+ if (scheme?.type === "http") {
48477
+ const httpScheme = String(scheme.scheme || "").toLowerCase();
48478
+ if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
48479
+ if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
48480
+ return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
48481
+ }
48482
+ if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
48483
+ return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
48484
+ }
48485
+ return { scheme: schemeName, kind, checkable: false };
48486
+ }
48487
+ function collectSecurityRuntimeChecks(root, operation) {
48488
+ const securitySchemes = asRecord4(asRecord4(root.components)?.securitySchemes);
48489
+ const requirements = operation.security === void 0 ? asArray2(root.security) : asArray2(operation.security);
48490
+ const alternatives = [];
48491
+ for (const requirement of requirements.map((entry) => asRecord4(entry)).filter(Boolean)) {
48492
+ const schemeNames = Object.keys(requirement);
48493
+ if (schemeNames.length === 0) return void 0;
48494
+ alternatives.push(schemeNames.map((schemeName) => securityCheckFor(schemeName, resolveInternalRef(root, securitySchemes?.[schemeName]))));
48495
+ }
48496
+ if (alternatives.some((alternative) => alternative.length > 0 && alternative.every((check) => !check.checkable))) return void 0;
48497
+ return alternatives.length > 0 ? alternatives : void 0;
48498
+ }
48499
+ function resolvedParameters(root, pathItem, operation) {
48500
+ return [...asArray2(pathItem.parameters), ...asArray2(operation.parameters)].map((rawParam) => {
48501
+ try {
48502
+ return resolveInternalRef(root, rawParam);
48503
+ } catch {
48504
+ return null;
48505
+ }
48506
+ }).filter((param) => Boolean(param));
48507
+ }
48508
+ var DEFAULT_PARAM_STYLES = { query: "form", path: "simple", header: "simple", cookie: "form" };
48509
+ var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
48510
+ function isIgnoredParameter(location2, name) {
48511
+ return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
48512
+ }
48513
+ function collectSerializationWarnings(root, pathItem, operation) {
48514
+ const warnings = [];
48515
+ for (const param of resolvedParameters(root, pathItem, operation)) {
48516
+ const location2 = String(param.in || "").toLowerCase();
48517
+ const name = String(param.name || "");
48518
+ const defaultStyle = DEFAULT_PARAM_STYLES[location2];
48519
+ if (!name || !defaultStyle || isIgnoredParameter(location2, name)) continue;
48520
+ const style = typeof param.style === "string" ? param.style : defaultStyle;
48521
+ const defaultExplode = style === "form";
48522
+ const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
48523
+ if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || param.content !== void 0) {
48524
+ warnings.push(`CONTRACT_PARAM_SERIALIZATION_NOT_VALIDATED: parameter ${location2}:${name} declares non-default style, explode, allowReserved, or content and its serialization is not validated`);
48525
+ }
48526
+ }
48527
+ return warnings;
48528
+ }
48529
+ var SCALAR_SCHEMA_TYPES = /* @__PURE__ */ new Set(["string", "number", "integer", "boolean", "null"]);
48530
+ function packedScalarSchema(packed) {
48531
+ if (packed.unsupported || packed.schema === void 0) return void 0;
48532
+ const record = asRecord4(packed.schema);
48533
+ if (!record) return void 0;
48534
+ const types2 = Array.isArray(record.type) ? record.type : [record.type];
48535
+ if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
48536
+ return packed.schema;
48537
+ }
48538
+ function collectParameterChecks(root, pathItem, operation, version, operationId, warnings) {
48539
+ const securityKeys = collectSecurityApiKeys(root, operation);
48540
+ const checks = [];
48541
+ const seen = /* @__PURE__ */ new Set();
48542
+ const orderedParams = [...asArray2(operation.parameters), ...asArray2(pathItem.parameters)].map((rawParam) => {
48543
+ try {
48544
+ return resolveInternalRef(root, rawParam);
48545
+ } catch {
48546
+ return null;
48547
+ }
48548
+ }).filter((param) => Boolean(param));
48549
+ for (const param of orderedParams) {
48550
+ const location2 = String(param.in || "").toLowerCase();
48551
+ if (location2 !== "query" && location2 !== "header") continue;
48552
+ const name = String(param.name || "");
48553
+ if (!name || isIgnoredParameter(location2, name)) continue;
48554
+ const key = `${location2}:${name.toLowerCase()}`;
48555
+ if (seen.has(key)) continue;
48556
+ seen.add(key);
48557
+ if (securityKeys.has(key)) continue;
48558
+ if (param.content !== void 0 || param.schema === void 0) continue;
48559
+ const defaultStyle = DEFAULT_PARAM_STYLES[location2];
48560
+ const style = typeof param.style === "string" ? param.style : defaultStyle;
48561
+ const defaultExplode = style === "form";
48562
+ const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
48563
+ if (style !== defaultStyle || explode !== defaultExplode) continue;
48564
+ const packed = packSchema(root, param.schema, version);
48565
+ if (packed.unsupported) {
48566
+ warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
48567
+ continue;
48568
+ }
48569
+ const schema2 = packedScalarSchema(packed);
48570
+ if (schema2 === void 0) continue;
48571
+ const check = { in: location2, name, required: param.required === true, schema: schema2 };
48572
+ if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
48573
+ checks.push(check);
48574
+ }
48575
+ return checks.length > 0 ? checks : void 0;
48576
+ }
48577
+ function collectDeclaredQueryParameters(root, pathItem, operation) {
48578
+ const names = /* @__PURE__ */ new Set();
48579
+ for (const param of resolvedParameters(root, pathItem, operation)) {
48580
+ if (String(param.in || "").toLowerCase() !== "query") continue;
48581
+ const name = String(param.name || "");
48582
+ if (name) names.add(name.toLowerCase());
48583
+ }
48584
+ for (const key of collectSecurityApiKeys(root, operation)) {
48585
+ if (key.startsWith("query:")) names.add(key.slice("query:".length));
48586
+ }
48587
+ return [...names];
48588
+ }
48258
48589
  function collectParameters(root, pathItem, operation) {
48259
48590
  const securityKeys = collectSecurityApiKeys(root, operation);
48260
48591
  const requirements = [];
@@ -48264,9 +48595,9 @@ function collectParameters(root, pathItem, operation) {
48264
48595
  const param = resolveInternalRef(root, rawParam);
48265
48596
  if (!param) continue;
48266
48597
  const location2 = String(param.in || "").toLowerCase();
48267
- if (!["path", "query", "header"].includes(location2)) continue;
48598
+ if (!["path", "query", "header", "cookie"].includes(location2)) continue;
48268
48599
  const name = String(param.name || "");
48269
- if (!name || param.required !== true) continue;
48600
+ if (!name || param.required !== true || isIgnoredParameter(location2, name)) continue;
48270
48601
  const key = `${location2}:${name.toLowerCase()}`;
48271
48602
  if (seen.has(key)) continue;
48272
48603
  seen.add(key);
@@ -48278,36 +48609,226 @@ function collectParameters(root, pathItem, operation) {
48278
48609
  }
48279
48610
  return requirements;
48280
48611
  }
48281
- function collectRequestBody(root, operation) {
48612
+ var BODY_FIELD_RULE_TYPES = /* @__PURE__ */ new Set(["application/x-www-form-urlencoded", "multipart/form-data"]);
48613
+ function isJsonBaseType(base) {
48614
+ return base === "application/json" || /\+json$/.test(base);
48615
+ }
48616
+ function mergeObjectSchema(root, rawSchema, depth) {
48617
+ if (depth > 10) return null;
48618
+ let schema2;
48619
+ try {
48620
+ schema2 = resolveInternalRef(root, rawSchema);
48621
+ } catch {
48622
+ return null;
48623
+ }
48624
+ if (!schema2) return null;
48625
+ const merged = {
48626
+ required: asArray2(schema2.required).map((entry) => String(entry)).filter(Boolean),
48627
+ properties: { ...asRecord4(schema2.properties) ?? {} }
48628
+ };
48629
+ for (const member of asArray2(schema2.allOf)) {
48630
+ const child = mergeObjectSchema(root, member, depth + 1);
48631
+ if (!child) continue;
48632
+ merged.required.push(...child.required);
48633
+ merged.properties = { ...merged.properties, ...child.properties };
48634
+ }
48635
+ merged.required = [...new Set(merged.required)];
48636
+ return merged;
48637
+ }
48638
+ function propertyIsReadOnly(root, properties, name) {
48639
+ try {
48640
+ return resolveInternalRef(root, properties[name])?.readOnly === true;
48641
+ } catch {
48642
+ return false;
48643
+ }
48644
+ }
48645
+ function propertyIsBinary(root, properties, name) {
48646
+ let schema2;
48647
+ try {
48648
+ schema2 = resolveInternalRef(root, properties[name]);
48649
+ } catch {
48650
+ return false;
48651
+ }
48652
+ if (!schema2) return false;
48653
+ if (schema2.format === "binary") return true;
48654
+ return typeof schema2.contentMediaType === "string" && schema2.contentMediaType.toLowerCase().startsWith("application/octet-stream");
48655
+ }
48656
+ function fieldEncodings(root, base, mediaObject, properties) {
48657
+ const declared = asRecord4(mediaObject?.encoding);
48658
+ const encodings = {};
48659
+ for (const name of Object.keys(properties)) {
48660
+ if (base === "multipart/form-data" && propertyIsBinary(root, properties, name)) {
48661
+ encodings[name] = { ...encodings[name], binary: true };
48662
+ }
48663
+ }
48664
+ if (declared) {
48665
+ for (const [name, rawEncoding] of Object.entries(declared)) {
48666
+ const encoding = asRecord4(rawEncoding);
48667
+ if (!encoding) continue;
48668
+ const entry = { ...encodings[name] };
48669
+ if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
48670
+ entry.contentType = encoding.contentType.toLowerCase();
48671
+ }
48672
+ if (base === "application/x-www-form-urlencoded") {
48673
+ const style = typeof encoding.style === "string" ? encoding.style : "form";
48674
+ const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
48675
+ if (style !== "form" || explode !== (style === "form") || encoding.allowReserved === true) {
48676
+ entry.nonDefaultSerialization = true;
48677
+ }
48678
+ }
48679
+ if (Object.keys(entry).length > 0) encodings[name] = entry;
48680
+ }
48681
+ }
48682
+ return Object.keys(encodings).length > 0 ? encodings : void 0;
48683
+ }
48684
+ function requestBodyFieldRules(root, content) {
48685
+ const rules = {};
48686
+ for (const [contentType, mediaObject] of Object.entries(content)) {
48687
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
48688
+ if (!isJsonBaseType(base) && !BODY_FIELD_RULE_TYPES.has(base)) continue;
48689
+ const mediaRecord = asRecord4(mediaObject);
48690
+ const merged = mergeObjectSchema(root, mediaRecord?.schema, 0);
48691
+ if (!merged) continue;
48692
+ const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
48693
+ const required = merged.required.filter((name) => !propertyIsReadOnly(root, merged.properties, name));
48694
+ const encodings = BODY_FIELD_RULE_TYPES.has(base) ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
48695
+ if (required.length > 0 || readOnly.length > 0 || encodings) {
48696
+ const rule = { required, readOnly };
48697
+ if (encodings) rule.encodings = encodings;
48698
+ rules[base] = rule;
48699
+ }
48700
+ }
48701
+ return Object.keys(rules).length > 0 ? rules : void 0;
48702
+ }
48703
+ function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
48704
+ const schemas = {};
48705
+ const exampleWarnings = /* @__PURE__ */ new Set();
48706
+ for (const [contentType, mediaObject] of Object.entries(content)) {
48707
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
48708
+ const mediaRecord = asRecord4(mediaObject);
48709
+ const schema2 = mediaRecord?.schema;
48710
+ if (!isJsonBaseType(base)) {
48711
+ if (schema2 !== void 0 && !BODY_FIELD_RULE_TYPES.has(base)) {
48712
+ warnings.push(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated at runtime`);
48713
+ }
48714
+ continue;
48715
+ }
48716
+ if (schema2 === void 0) continue;
48717
+ const packed = packSchema(root, schema2, version, "request");
48718
+ if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
48719
+ if (packed.unsupported) {
48720
+ warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
48721
+ continue;
48722
+ }
48723
+ if (packed.schema !== void 0) schemas[base] = packed.schema;
48724
+ }
48725
+ warnings.push(...exampleWarnings);
48726
+ return Object.keys(schemas).length > 0 ? schemas : void 0;
48727
+ }
48728
+ function collectRequestBody(root, operation, version, operationId, warnings) {
48282
48729
  const body = resolveInternalRef(root, operation.requestBody);
48283
- if (!body || body.required !== true) return void 0;
48730
+ if (!body) return void 0;
48284
48731
  const content = asRecord4(body.content);
48732
+ const fieldRules = content ? requestBodyFieldRules(root, content) : void 0;
48733
+ if (fieldRules) {
48734
+ for (const [base, rule] of Object.entries(fieldRules)) {
48735
+ for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
48736
+ if (encoding.nonDefaultSerialization) {
48737
+ warnings.push(
48738
+ `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`
48739
+ );
48740
+ }
48741
+ }
48742
+ }
48743
+ }
48285
48744
  return {
48286
- required: true,
48287
- contentTypes: content ? Object.keys(content) : []
48745
+ required: body.required === true,
48746
+ contentTypes: content ? Object.keys(content) : [],
48747
+ fieldRules,
48748
+ jsonSchemas: content ? requestBodyJsonSchemas(root, content, version, operationId, warnings) : void 0
48288
48749
  };
48289
48750
  }
48290
- function responseContent(root, version, response) {
48751
+ function exampleCandidates(root, mediaObject) {
48752
+ const candidates = [];
48753
+ if ("example" in mediaObject) candidates.push({ label: "example", value: mediaObject.example });
48754
+ const examples = asRecord4(mediaObject.examples);
48755
+ if (examples) {
48756
+ for (const [name, rawExample] of Object.entries(examples)) {
48757
+ let example;
48758
+ try {
48759
+ example = resolveInternalRef(root, rawExample);
48760
+ } catch {
48761
+ continue;
48762
+ }
48763
+ if (example && "value" in example) candidates.push({ label: `examples.${name}`, value: example.value });
48764
+ }
48765
+ }
48766
+ return candidates;
48767
+ }
48768
+ function validateExamples(root, mediaObject, packed, contentType, context, warnings) {
48769
+ if (packed.schema === void 0 || packed.unsupported) return;
48770
+ const candidates = exampleCandidates(root, mediaObject);
48771
+ if (candidates.length === 0) return;
48772
+ const validate2 = compileSchemaValidator(packed.schema);
48773
+ if (!validate2) return;
48774
+ for (const candidate of candidates) {
48775
+ if (!validate2(candidate.value)) {
48776
+ warnings.add(`CONTRACT_EXAMPLE_SCHEMA_MISMATCH: ${candidate.label} for ${contentType} on ${context} does not match its schema`);
48777
+ }
48778
+ }
48779
+ }
48780
+ function responseContent(root, version, response, context, warnings) {
48291
48781
  const content = asRecord4(response.content);
48292
48782
  if (!content) return {};
48293
48783
  const media = {};
48294
48784
  for (const [contentType, mediaObject] of Object.entries(content)) {
48295
- const schema2 = asRecord4(mediaObject)?.schema;
48296
- media[contentType] = schema2 === void 0 ? {} : packSchema(root, schema2, version);
48785
+ const mediaRecord = asRecord4(mediaObject);
48786
+ const schema2 = mediaRecord?.schema;
48787
+ let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
48788
+ if (isSchemaGraphOverflow(packed)) {
48789
+ warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
48790
+ packed = {};
48791
+ }
48792
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
48793
+ if (mediaRecord && isJsonBaseType(base)) validateExamples(root, mediaRecord, packed, contentType, context, warnings);
48794
+ media[contentType] = packed;
48297
48795
  }
48298
48796
  return media;
48299
48797
  }
48300
- function responseHeaders(root, version, response) {
48798
+ function responseHeaders(root, version, response, context, warnings) {
48301
48799
  const headers = asRecord4(response.headers);
48302
48800
  if (!headers) return [];
48303
- return Object.entries(headers).map(([name, rawHeader]) => {
48801
+ const entries = [];
48802
+ for (const [name, rawHeader] of Object.entries(headers)) {
48803
+ if (name.toLowerCase() === "content-type") continue;
48304
48804
  const header = resolveInternalRef(root, rawHeader);
48305
- if (!header) return { name, required: true, unsupported: "Unresolved response header" };
48805
+ if (!header) {
48806
+ entries.push({ name, required: true, unsupported: "Unresolved response header" });
48807
+ continue;
48808
+ }
48306
48809
  const required = header.required === true;
48307
- if (header.content) return { name, required, unsupported: "OpenAPI response header content is unsupported" };
48308
- if (!header.schema) return { name, required };
48309
- return { name, required, ...packSchema(root, header.schema, version) };
48310
- });
48810
+ if (header.content) {
48811
+ entries.push({ name, required, unsupported: "OpenAPI response header content is unsupported" });
48812
+ continue;
48813
+ }
48814
+ if (!header.schema) {
48815
+ entries.push({ name, required });
48816
+ continue;
48817
+ }
48818
+ const packed = packSchema(root, header.schema, version);
48819
+ if (isSchemaGraphOverflow(packed)) {
48820
+ warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
48821
+ entries.push({ name, required });
48822
+ continue;
48823
+ }
48824
+ if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
48825
+ warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
48826
+ entries.push({ name, required });
48827
+ continue;
48828
+ }
48829
+ entries.push({ name, required, ...packed });
48830
+ }
48831
+ return entries;
48311
48832
  }
48312
48833
  function buildContractIndex(root) {
48313
48834
  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)");
@@ -48332,11 +48853,23 @@ function buildContractIndex(root) {
48332
48853
  throw new Error(`CONTRACT_OPERATION_NO_RESPONSES: ${lowerMethod.toUpperCase()} ${path6} must define at least one response`);
48333
48854
  }
48334
48855
  const contractResponses = {};
48856
+ const responseWarnings = /* @__PURE__ */ new Set();
48335
48857
  for (const [status, rawResponse] of Object.entries(responses)) {
48336
48858
  const response = resolveInternalRef(root, rawResponse);
48337
48859
  if (!response) continue;
48338
- const content = responseContent(root, version, response);
48339
- const headers = responseHeaders(root, version, response);
48860
+ if (asRecord4(response.links)) {
48861
+ responseWarnings.add(`CONTRACT_LINKS_NOT_VALIDATED: response links are not validated for ${lowerMethod.toUpperCase()} ${path6}`);
48862
+ }
48863
+ const responseContext = `${lowerMethod.toUpperCase()} ${path6} status ${status}`;
48864
+ const content = responseContent(root, version, response, responseContext, responseWarnings);
48865
+ for (const [contentType, media] of Object.entries(content)) {
48866
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
48867
+ const schemaType = asRecord4(media.schema)?.type;
48868
+ if (!isJsonBaseType(base) && media.schema !== void 0 && !media.unsupported && schemaType !== "string") {
48869
+ responseWarnings.add(`CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED: response schema for ${contentType} on ${responseContext} is not validated at runtime`);
48870
+ }
48871
+ }
48872
+ const headers = responseHeaders(root, version, response, responseContext, responseWarnings);
48340
48873
  contractResponses[normalizeResponseKey(status)] = {
48341
48874
  content,
48342
48875
  hasBody: Object.keys(content).length > 0,
@@ -48348,11 +48881,26 @@ function buildContractIndex(root) {
48348
48881
  ...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path6))
48349
48882
  ].map(normalizePath))];
48350
48883
  const opWarnings = [];
48884
+ opWarnings.push(...responseWarnings);
48351
48885
  opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
48886
+ opWarnings.push(...collectSerializationWarnings(root, pathItem, operation));
48887
+ if (operation.deprecated === true) {
48888
+ opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path6} is marked deprecated in the OpenAPI document`);
48889
+ }
48352
48890
  const requiredParameters = collectParameters(root, pathItem, operation);
48353
48891
  for (const parameter of requiredParameters.filter((entry) => entry.securityDerived)) {
48354
48892
  opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
48355
48893
  }
48894
+ for (const parameter of requiredParameters.filter((entry) => entry.in === "cookie" && !entry.securityDerived)) {
48895
+ opWarnings.push(`CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not statically required in generated requests`);
48896
+ }
48897
+ const pathParamWarnings = /* @__PURE__ */ new Set();
48898
+ for (const param of resolvedParameters(root, pathItem, operation)) {
48899
+ if (String(param.in || "").toLowerCase() !== "path") continue;
48900
+ const name = String(param.name || "");
48901
+ if (name) pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
48902
+ }
48903
+ opWarnings.push(...pathParamWarnings);
48356
48904
  operations.push({
48357
48905
  id: `${lowerMethod.toUpperCase()} ${path6}`,
48358
48906
  method: lowerMethod.toUpperCase(),
@@ -48361,7 +48909,10 @@ function buildContractIndex(root) {
48361
48909
  candidates,
48362
48910
  responses: contractResponses,
48363
48911
  requiredParameters,
48364
- requestBody: collectRequestBody(root, operation),
48912
+ declaredQueryParameters: collectDeclaredQueryParameters(root, pathItem, operation),
48913
+ parameterChecks: collectParameterChecks(root, pathItem, operation, version, `${lowerMethod.toUpperCase()} ${path6}`, opWarnings),
48914
+ requestBody: collectRequestBody(root, operation, version, `${lowerMethod.toUpperCase()} ${path6}`, opWarnings),
48915
+ security: collectSecurityRuntimeChecks(root, operation),
48365
48916
  warnings: opWarnings
48366
48917
  });
48367
48918
  }
@@ -48382,35 +48933,6 @@ function buildContractIndex(root) {
48382
48933
  return { operations, version, warnings };
48383
48934
  }
48384
48935
 
48385
- // src/lib/spec/schema-validator-code.ts
48386
- var import_schemasafe = __toESM(require_src(), 1);
48387
- function compileSchemaValidatorCode(schema2) {
48388
- try {
48389
- const validate2 = (0, import_schemasafe.validator)(schema2, {
48390
- includeErrors: true,
48391
- allErrors: true,
48392
- contentValidation: false,
48393
- formatAssertion: true,
48394
- isJSON: true,
48395
- mode: "default",
48396
- removeAdditional: false,
48397
- requireSchema: true,
48398
- requireStringValidation: false,
48399
- useDefaults: false
48400
- });
48401
- const source = validate2.toModule();
48402
- if (/\beval\s*\(/.test(source) || /new\s+Function\b/.test(source)) {
48403
- throw new Error("schemasafe generated forbidden dynamic code");
48404
- }
48405
- return source;
48406
- } catch (error) {
48407
- throw new Error(
48408
- `CONTRACT_SCHEMA_COMPILE_FAILED: ${error instanceof Error ? error.message : String(error)}`,
48409
- { cause: error }
48410
- );
48411
- }
48412
- }
48413
-
48414
48936
  // src/lib/spec/collection-contracts.ts
48415
48937
  var CONTRACT_SIZE_LIMITS = {
48416
48938
  warnTestScriptBytes: 256e3,
@@ -48499,29 +49021,51 @@ function matchOperation(index, request) {
48499
49021
  function assignValidator(lines, target, source) {
48500
49022
  lines.push(`${target} = ${source};`);
48501
49023
  }
48502
- function buildValidatorAssignments(operation) {
49024
+ function tryCompile(target, schema2, lines, warnings, context) {
49025
+ try {
49026
+ assignValidator(lines, target, compileSchemaValidatorCode(schema2));
49027
+ } catch (error) {
49028
+ lines.push(`${target} = { skip: true };`);
49029
+ warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: ${context} could not be compiled into a runtime validator (${error instanceof Error ? error.message.slice(0, 160) : String(error)})`);
49030
+ }
49031
+ }
49032
+ function buildValidatorAssignments(operation, warnings) {
48503
49033
  const lines = ["var validators = {};"];
49034
+ const parameterChecks = operation.parameterChecks ?? [];
49035
+ if (parameterChecks.length > 0) {
49036
+ lines.push("var paramValidators = {};");
49037
+ for (const check of parameterChecks) {
49038
+ tryCompile(`paramValidators[${JSON.stringify(`${check.in}:${check.name.toLowerCase()}`)}]`, check.schema, lines, warnings, `parameter ${check.in}:${check.name} schema on ${operation.id}`);
49039
+ }
49040
+ }
49041
+ const bodySchemas = Object.entries(operation.requestBody?.jsonSchemas ?? {});
49042
+ if (bodySchemas.length > 0) {
49043
+ lines.push("var requestBodyValidators = {};");
49044
+ for (const [base, schema2] of bodySchemas) {
49045
+ tryCompile(`requestBodyValidators[${JSON.stringify(base)}]`, schema2, lines, warnings, `request body schema for ${base} on ${operation.id}`);
49046
+ }
49047
+ }
48504
49048
  for (const [status, response] of Object.entries(operation.responses)) {
48505
49049
  lines.push(`validators[${JSON.stringify(status)}] = validators[${JSON.stringify(status)}] || {};`);
48506
49050
  for (const [mediaType, media] of Object.entries(response.content)) {
48507
49051
  if (media.schema !== void 0 && !media.unsupported) {
48508
- assignValidator(lines, `validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, compileSchemaValidatorCode(media.schema));
49052
+ tryCompile(`validators[${JSON.stringify(status)}][${JSON.stringify(mediaType)}]`, media.schema, lines, warnings, `response schema for ${mediaType} on ${operation.id} status ${status}`);
48509
49053
  }
48510
49054
  }
48511
49055
  for (const header of response.headers) {
48512
49056
  if (header.schema !== void 0 && !header.unsupported) {
48513
49057
  lines.push(`validators[${JSON.stringify(status)}].__headers = validators[${JSON.stringify(status)}].__headers || {};`);
48514
- assignValidator(lines, `validators[${JSON.stringify(status)}].__headers[${JSON.stringify(header.name.toLowerCase())}]`, compileSchemaValidatorCode(header.schema));
49058
+ 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}`);
48515
49059
  }
48516
49060
  }
48517
49061
  }
48518
49062
  return lines;
48519
49063
  }
48520
- function createContractScript(operation) {
48521
- const contract = { method: operation.method, path: operation.path, responses: operation.responses };
49064
+ function createContractScript(operation, warnings = []) {
49065
+ const contract = { method: operation.method, path: operation.path, responses: operation.responses, security: operation.security, parameters: operation.parameterChecks };
48522
49066
  return [
48523
49067
  `var contract = JSON.parse(${JSON.stringify(JSON.stringify(contract))});`,
48524
- ...buildValidatorAssignments(operation),
49068
+ ...buildValidatorAssignments(operation, warnings),
48525
49069
  "function selectedResponseContract() {",
48526
49070
  " var status = String(pm.response.code);",
48527
49071
  " if (contract.responses[status]) return { key: status, value: contract.responses[status] };",
@@ -48535,6 +49079,15 @@ function createContractScript(operation) {
48535
49079
  'function mediaBase(value) { return String(value || "").toLowerCase().split(";")[0].trim(); }',
48536
49080
  'function mediaParts(value) { var base = mediaBase(value); var parts = base.split("/"); return { raw: base, type: parts[0] || "", subtype: parts[1] || "" }; }',
48537
49081
  'function isJsonSubtype(subtype) { return subtype === "json" || /\\+json$/.test(subtype); }',
49082
+ "function coerceBySchema(value, schema) {",
49083
+ " var type = schema && schema.type;",
49084
+ " var types = Array.isArray(type) ? type : [type];",
49085
+ ' if ((types.indexOf("integer") !== -1 || types.indexOf("number") !== -1) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(String(value).trim())) return Number(value);',
49086
+ ' if (types.indexOf("boolean") !== -1 && (value === "true" || value === "false")) return value === "true";',
49087
+ " return value;",
49088
+ "}",
49089
+ '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; }',
49090
+ "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; }",
48538
49091
  "function mediaScore(expected, actual) {",
48539
49092
  " var e = mediaParts(expected); var a = mediaParts(actual);",
48540
49093
  " if (!e.raw || !a.raw) return 0;",
@@ -48565,7 +49118,8 @@ function createContractScript(operation) {
48565
49118
  " if (!actual) return;",
48566
49119
  ' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
48567
49120
  " var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
48568
- ' if (headerValidator && !headerValidator(actual)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
49121
+ " if (headerValidator && headerValidator.skip) return;",
49122
+ ' if (headerValidator && !headerValidator(coerceBySchema(actual, header.schema))) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
48569
49123
  " });",
48570
49124
  "});",
48571
49125
  "pm.test('Response body matches OpenAPI body contract', function () {",
@@ -48593,11 +49147,75 @@ function createContractScript(operation) {
48593
49147
  ' if (media.media.unsupported) pm.expect.fail("OpenAPI schema unsupported for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + media.media.unsupported);',
48594
49148
  " if (!media.media.schema) { return; }",
48595
49149
  " var validate = validators[selected.key] && validators[selected.key][media.expected];",
49150
+ " if (validate && validate.skip) return;",
48596
49151
  ' if (!validate) pm.expect.fail("OpenAPI schema validator was not generated for " + media.expected);',
48597
49152
  ' var actual = mediaParts(pm.response.headers.get("Content-Type") || "");',
48598
49153
  " var value = isJsonSubtype(actual.subtype) ? pm.response.json() : responseText();",
48599
- ' 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);',
49154
+ // Non-JSON object-schema bodies skip schema validation instead of
49155
+ // failing; the index emits CONTRACT_NONJSON_SCHEMA_NOT_VALIDATED so the
49156
+ // skip is visible at instrumentation time.
49157
+ ' if (!isJsonSubtype(actual.subtype) && media.media.schema && media.media.schema.type !== "string") { return; }',
48600
49158
  ' if (!validate(value)) pm.expect.fail("OpenAPI schema validation failed for " + contract.method + " " + contract.path + " status " + pm.response.code + ": " + JSON.stringify(validate.errors || []));',
49159
+ "});",
49160
+ ...operation.security ? [
49161
+ "pm.test('Request carries credentials required by OpenAPI security', function () {",
49162
+ " function satisfied(check) {",
49163
+ " if (!check.checkable) return true;",
49164
+ ' if (check.in === "query") return hasQueryParam(check.name);',
49165
+ ' 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); }); }',
49166
+ ' if (check.prefix) { return requestHeader("Authorization").toLowerCase().indexOf(check.prefix.toLowerCase()) === 0; }',
49167
+ ' if (check.kind === "oauth2" || check.kind === "openIdConnect") { return Boolean(requestHeader("Authorization")) || hasQueryParam("access_token"); }',
49168
+ " return Boolean(requestHeader(check.name));",
49169
+ " }",
49170
+ " var alternatives = contract.security || [];",
49171
+ " var ok = alternatives.some(function (alternative) { return alternative.every(function (check) { return satisfied(check); }); });",
49172
+ ' 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(" | "));',
49173
+ "});"
49174
+ ] : [],
49175
+ ...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
49176
+ "pm.test('Request parameters match OpenAPI schemas', function () {",
49177
+ ' 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; }',
49178
+ ' 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; }',
49179
+ ' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
49180
+ " (contract.parameters || []).forEach(function (param) {",
49181
+ ' var key = param.in + ":" + String(param.name).toLowerCase();',
49182
+ " var validate = paramValidators[key];",
49183
+ " if (!validate || validate.skip) return;",
49184
+ ' var value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
49185
+ ' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
49186
+ ' if (value === "" && param.allowEmptyValue) return;',
49187
+ " if (isPlaceholder(value)) return;",
49188
+ ' 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 || []));',
49189
+ " });",
49190
+ "});"
49191
+ ] : [],
49192
+ ...operation.requestBody?.jsonSchemas && Object.keys(operation.requestBody.jsonSchemas).length > 0 ? [
49193
+ "pm.test('Request body matches OpenAPI request schema', function () {",
49194
+ " var body = pm.request.body;",
49195
+ ' var raw = body && body.mode === "raw" && typeof body.raw === "string" ? body.raw : "";',
49196
+ " if (!raw.trim()) return;",
49197
+ ' if (/"<[^"<>]*>"/.test(raw) || raw.indexOf("{{") !== -1) return;',
49198
+ ' var validate = requestBodyValidators[mediaBase(requestHeader("Content-Type"))];',
49199
+ " if (!validate || validate.skip) return;",
49200
+ " var parsed;",
49201
+ // Unquoted generator placeholders such as {"count": <long>} break
49202
+ // JSON.parse; a parse failure alongside an angle-bracket token is
49203
+ // treated as a placeholder body rather than drift.
49204
+ ' 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; }',
49205
+ ' if (!validate(parsed)) pm.expect.fail("Request body failed OpenAPI request schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
49206
+ "});"
49207
+ ] : [],
49208
+ "pm.test('Content-Length is consistent with OpenAPI body expectations', function () {",
49209
+ ' var raw = pm.response.headers.get("Content-Length");',
49210
+ " if (raw === null || raw === undefined) return;",
49211
+ // Combined duplicate Content-Length values ("5, 5") fail the integer
49212
+ // syntax check on purpose: RFC 9110 tolerates identical repeats, but a
49213
+ // contract test surfacing them is strict by design.
49214
+ ' if (!/^[0-9]+$/.test(String(raw).trim())) pm.expect.fail("Content-Length header is not a non-negative integer: " + raw);',
49215
+ ' if (contract.method === "HEAD" || pm.response.code === 304) return;',
49216
+ ' if (pm.response.headers.get("Content-Encoding")) return;',
49217
+ " var mustBeEmpty = isBodyless() || (selected && Object.keys(selected.value.content || {}).length === 0);",
49218
+ ' 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);',
48601
49219
  "});"
48602
49220
  ];
48603
49221
  }
@@ -48698,6 +49316,87 @@ function assertStaticRequestShape(operation, request) {
48698
49316
  );
48699
49317
  }
48700
49318
  }
49319
+ return collectStaticBodyWarnings(operation, request, contentType);
49320
+ }
49321
+ function requestBodyFieldNames(request, base) {
49322
+ const body = asRecord5(request.body);
49323
+ if (!body) return void 0;
49324
+ if (base === "application/json" || /\+json$/.test(base)) {
49325
+ if (body.mode !== "raw" || typeof body.raw !== "string") return void 0;
49326
+ let parsed;
49327
+ try {
49328
+ parsed = JSON.parse(body.raw);
49329
+ } catch {
49330
+ return void 0;
49331
+ }
49332
+ const record = asRecord5(parsed);
49333
+ return record ? Object.keys(record) : void 0;
49334
+ }
49335
+ const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
49336
+ if (!mode || !Array.isArray(body[mode])) return void 0;
49337
+ return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true).map((entry) => String(entry.key || "")).filter(Boolean);
49338
+ }
49339
+ function requestBodyEntries(request, base) {
49340
+ const body = asRecord5(request.body);
49341
+ const mode = base === "application/x-www-form-urlencoded" ? "urlencoded" : base === "multipart/form-data" ? "formdata" : "";
49342
+ if (!body || !mode || !Array.isArray(body[mode])) return void 0;
49343
+ return body[mode].map((entry) => asRecord5(entry)).filter((entry) => Boolean(entry)).filter((entry) => entry.disabled !== true);
49344
+ }
49345
+ function mediaTypeMatchesPattern(pattern, actual) {
49346
+ return pattern.split(",").some((candidate) => {
49347
+ const entry = (candidate.split(";")[0] ?? "").trim();
49348
+ if (!entry) return false;
49349
+ if (entry === "*/*" || entry === actual) return true;
49350
+ if (entry.endsWith("/*")) return actual.startsWith(entry.slice(0, -1));
49351
+ return false;
49352
+ });
49353
+ }
49354
+ function collectStaticEncodingWarnings(operation, request, base, rule) {
49355
+ const encodings = rule.encodings;
49356
+ if (!encodings || base !== "multipart/form-data") return [];
49357
+ const entries = requestBodyEntries(request, base);
49358
+ if (!entries) return [];
49359
+ const warnings = [];
49360
+ for (const [field, encoding] of Object.entries(encodings)) {
49361
+ const entry = entries.find((candidate) => String(candidate.key || "") === field);
49362
+ if (!entry) continue;
49363
+ if (encoding.binary && String(entry.type || "") !== "file") {
49364
+ warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
49365
+ }
49366
+ if (encoding.contentType) {
49367
+ const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
49368
+ if (!actual) {
49369
+ warnings.push(
49370
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
49371
+ );
49372
+ } else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
49373
+ warnings.push(
49374
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
49375
+ );
49376
+ }
49377
+ }
49378
+ }
49379
+ return warnings;
49380
+ }
49381
+ function collectStaticBodyWarnings(operation, request, contentType) {
49382
+ const rules = operation.requestBody?.fieldRules;
49383
+ if (!rules) return [];
49384
+ const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
49385
+ const rule = rules[base];
49386
+ if (!rule) return [];
49387
+ const warnings = [...collectStaticEncodingWarnings(operation, request, base, rule)];
49388
+ const names = requestBodyFieldNames(request, base);
49389
+ if (!names) return warnings;
49390
+ const present = new Set(names);
49391
+ const missing = rule.required.filter((name) => !present.has(name));
49392
+ if (missing.length > 0) {
49393
+ warnings.push(`CONTRACT_REQUEST_BODY_INCOMPLETE: ${operation.id} generated request body is missing required properties: ${missing.join(", ")}`);
49394
+ }
49395
+ const readOnlySent = rule.readOnly.filter((name) => present.has(name));
49396
+ if (readOnlySent.length > 0) {
49397
+ warnings.push(`CONTRACT_READONLY_PROPERTY_IN_REQUEST: ${operation.id} generated request body includes readOnly properties: ${readOnlySent.join(", ")}`);
49398
+ }
49399
+ return warnings;
48701
49400
  }
48702
49401
  function validateScript(script) {
48703
49402
  const source = script.join("\n");
@@ -48746,9 +49445,12 @@ function instrumentContractCollection(collection, index) {
48746
49445
  if (result.operation) {
48747
49446
  const previous = covered.get(result.operation.id);
48748
49447
  if (previous) throw new Error(`CONTRACT_DUPLICATE_OPERATION_REQUEST: ${result.operation.id} matched more than one generated request (${previous}, ${String(item.name || "<unnamed>")})`);
48749
- assertStaticRequestShape(result.operation, request);
49448
+ warnings.push(...assertStaticRequestShape(result.operation, request));
49449
+ for (const name of [...requestQueryNames(request)].filter((entry) => !result.operation.declaredQueryParameters.includes(entry))) {
49450
+ warnings.push(`CONTRACT_UNDOCUMENTED_QUERY_PARAM: ${result.operation.id} generated request sends query parameter ${name} that the OpenAPI operation does not declare`);
49451
+ }
48750
49452
  covered.set(result.operation.id, String(item.name || "<unnamed>"));
48751
- script = createContractScript(result.operation);
49453
+ script = createContractScript(result.operation, warnings);
48752
49454
  } else if (result.ambiguous && result.ambiguous.length > 0) {
48753
49455
  script = createMappingFailureScript(`Ambiguous OpenAPI operation match for request ${result.method} ${result.path}: ${result.ambiguous.map((entry) => entry.id).join(", ")}`);
48754
49456
  } else {