@postman-cse/onboarding-bootstrap 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.cjs CHANGED
@@ -48107,6 +48107,38 @@ function normalizeSchema(ctx, schema2, options) {
48107
48107
  const sourceSchema = { ...record };
48108
48108
  const nullable = sourceSchema.nullable === true && ctx.version === "3.0";
48109
48109
  delete sourceSchema.nullable;
48110
+ const discriminator = asRecord3(sourceSchema.discriminator);
48111
+ if (discriminator && typeof discriminator.propertyName === "string" && discriminator.propertyName) {
48112
+ const propertyName = discriminator.propertyName;
48113
+ const branchKey = Array.isArray(sourceSchema.oneOf) ? "oneOf" : Array.isArray(sourceSchema.anyOf) ? "anyOf" : "";
48114
+ const members = branchKey ? sourceSchema[branchKey] : [];
48115
+ const memberRefs = members.map((member) => {
48116
+ const memberRecord = asRecord3(member);
48117
+ const memberRef = memberRecord && Object.keys(memberRecord).length === 1 && typeof memberRecord.$ref === "string" ? memberRecord.$ref : "";
48118
+ return memberRef.startsWith("#/") ? memberRef : "";
48119
+ });
48120
+ if (branchKey && members.length > 0 && memberRefs.every(Boolean)) {
48121
+ const valuesByRef = /* @__PURE__ */ new Map();
48122
+ const refByMappingKey = /* @__PURE__ */ new Map();
48123
+ for (const [value, target] of Object.entries(asRecord3(discriminator.mapping) ?? {})) {
48124
+ if (typeof target !== "string" || !target) continue;
48125
+ const targetRef = target.startsWith("#/") ? target : `#/components/schemas/${target}`;
48126
+ valuesByRef.set(targetRef, [...valuesByRef.get(targetRef) ?? [], value]);
48127
+ refByMappingKey.set(value, targetRef);
48128
+ }
48129
+ sourceSchema[branchKey] = members.map((member, index) => {
48130
+ const memberRef = memberRefs[index] ?? "";
48131
+ const tail = (memberRef.split("/").pop() ?? "").replace(/~1/g, "/").replace(/~0/g, "~");
48132
+ const values = [...valuesByRef.get(memberRef) ?? []];
48133
+ const tailClaimedElsewhere = refByMappingKey.has(tail) && refByMappingKey.get(tail) !== memberRef;
48134
+ if (tail && !tailClaimedElsewhere && !values.includes(tail)) values.push(tail);
48135
+ const dispatch = values.length === 1 ? { const: values[0] } : { enum: values };
48136
+ return { allOf: [member, { type: "object", required: [propertyName], properties: { [propertyName]: dispatch } }] };
48137
+ });
48138
+ } else {
48139
+ ctx.notes.add("discriminator");
48140
+ }
48141
+ }
48110
48142
  const directionStrippedFlag = ctx.direction === "request" ? "readOnly" : "writeOnly";
48111
48143
  const strippedProperties = /* @__PURE__ */ new Set();
48112
48144
  const rawProperties = asRecord3(sourceSchema.properties);
@@ -48279,7 +48311,7 @@ function packSchema(root, schema2, version, direction = "response") {
48279
48311
  if (schema2 === true) return { schema: { $schema: rootDialect(root, version) } };
48280
48312
  if (schema2 === false) return unsupported("Boolean false JSON Schema rejects every instance and is unsupported");
48281
48313
  const dialect = rootDialect(root, version, asRecord3(schema2) ?? void 0);
48282
- const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map() };
48314
+ const ctx = { root, version, direction, dialect, defs: /* @__PURE__ */ new Map(), notes: /* @__PURE__ */ new Set() };
48283
48315
  const normalized = normalizeSchema(ctx, schema2, { depth: 0, rootSchema: true });
48284
48316
  const message = hasUnsupported(normalized);
48285
48317
  if (message) return unsupported(message);
@@ -48304,7 +48336,9 @@ function packSchema(root, schema2, version, direction = "response") {
48304
48336
  if (!normalizedRecord) return unsupported("Referenced schemas require an object root schema");
48305
48337
  normalizedRecord.$defs = Object.fromEntries([...ctx.defs.values()].map((entry) => [entry.name, entry.schema]));
48306
48338
  }
48307
- return { schema: normalized };
48339
+ const packed = { schema: normalized };
48340
+ if (ctx.notes.size > 0) packed.notes = [...ctx.notes].sort();
48341
+ return packed;
48308
48342
  } catch (error) {
48309
48343
  return unsupported(error instanceof Error ? error.message : String(error));
48310
48344
  }
@@ -48477,6 +48511,7 @@ function securityCheckFor(schemeName, scheme) {
48477
48511
  const httpScheme = String(scheme.scheme || "").toLowerCase();
48478
48512
  if (httpScheme === "basic") return { scheme: schemeName, kind, checkable: true, prefix: "Basic " };
48479
48513
  if (httpScheme === "bearer") return { scheme: schemeName, kind, checkable: true, prefix: "Bearer " };
48514
+ if (httpScheme) return { scheme: schemeName, kind, checkable: true, prefix: `${httpScheme.charAt(0).toUpperCase()}${httpScheme.slice(1)} ` };
48480
48515
  return { scheme: schemeName, kind, checkable: true, in: "header", name: "Authorization" };
48481
48516
  }
48482
48517
  if (scheme?.type === "oauth2" || scheme?.type === "openIdConnect") {
@@ -48510,7 +48545,18 @@ var IGNORED_HEADER_PARAMS = /* @__PURE__ */ new Set(["accept", "content-type", "
48510
48545
  function isIgnoredParameter(location2, name) {
48511
48546
  return location2 === "header" && IGNORED_HEADER_PARAMS.has(name.toLowerCase());
48512
48547
  }
48513
- function collectSerializationWarnings(root, pathItem, operation) {
48548
+ function jsonContentParameterMedia(param) {
48549
+ const content = asRecord4(param.content);
48550
+ if (!content) return void 0;
48551
+ const entries = Object.entries(content);
48552
+ if (entries.length !== 1) return void 0;
48553
+ const [contentType, mediaObject] = entries[0];
48554
+ const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
48555
+ if (!isJsonBaseType(base)) return void 0;
48556
+ const schema2 = asRecord4(mediaObject)?.schema;
48557
+ return schema2 === void 0 ? void 0 : schema2;
48558
+ }
48559
+ function collectSerializationWarnings(root, pathItem, operation, decodedKeys) {
48514
48560
  const warnings = [];
48515
48561
  for (const param of resolvedParameters(root, pathItem, operation)) {
48516
48562
  const location2 = String(param.in || "").toLowerCase();
@@ -48520,7 +48566,9 @@ function collectSerializationWarnings(root, pathItem, operation) {
48520
48566
  const style = typeof param.style === "string" ? param.style : defaultStyle;
48521
48567
  const defaultExplode = style === "form";
48522
48568
  const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
48523
- if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || param.content !== void 0) {
48569
+ const unvalidatedContent = param.content !== void 0 && (jsonContentParameterMedia(param) === void 0 || location2 !== "query" && location2 !== "header");
48570
+ if (style !== defaultStyle || explode !== defaultExplode || param.allowReserved === true || unvalidatedContent) {
48571
+ if (decodedKeys.has(`${location2}:${name.toLowerCase()}`) && param.allowReserved !== true && param.content === void 0) continue;
48524
48572
  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
48573
  }
48526
48574
  }
@@ -48535,7 +48583,27 @@ function packedScalarSchema(packed) {
48535
48583
  if (!types2.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
48536
48584
  return packed.schema;
48537
48585
  }
48538
- function collectParameterChecks(root, pathItem, operation, version, operationId, warnings) {
48586
+ function packedArrayItemsSchema(packed) {
48587
+ if (packed.unsupported || packed.schema === void 0) return void 0;
48588
+ const record = asRecord4(packed.schema);
48589
+ if (!record) return void 0;
48590
+ const types2 = Array.isArray(record.type) ? record.type : [record.type];
48591
+ if (types2.length !== 1 || types2[0] !== "array") return void 0;
48592
+ if (record.prefixItems !== void 0 || Array.isArray(record.items)) return void 0;
48593
+ if (record.items === void 0) return {};
48594
+ const items = asRecord4(record.items);
48595
+ if (!items || typeof items.$ref === "string") return void 0;
48596
+ const itemTypes = Array.isArray(items.type) ? items.type : [items.type];
48597
+ if (!itemTypes.every((entry) => typeof entry === "string" && SCALAR_SCHEMA_TYPES.has(entry))) return void 0;
48598
+ return items;
48599
+ }
48600
+ var QUERY_ARRAY_DECODES = {
48601
+ "form:true": "multi",
48602
+ "form:false": "csv",
48603
+ "spaceDelimited:false": "ssv",
48604
+ "pipeDelimited:false": "pipes"
48605
+ };
48606
+ function collectParameterChecks(root, pathItem, operation, version, operationId, pathTemplate, warnings) {
48539
48607
  const securityKeys = collectSecurityApiKeys(root, operation);
48540
48608
  const checks = [];
48541
48609
  const seen = /* @__PURE__ */ new Set();
@@ -48548,32 +48616,65 @@ function collectParameterChecks(root, pathItem, operation, version, operationId,
48548
48616
  }).filter((param) => Boolean(param));
48549
48617
  for (const param of orderedParams) {
48550
48618
  const location2 = String(param.in || "").toLowerCase();
48551
- if (location2 !== "query" && location2 !== "header") continue;
48619
+ if (location2 !== "query" && location2 !== "header" && location2 !== "path" && location2 !== "cookie") continue;
48552
48620
  const name = String(param.name || "");
48553
48621
  if (!name || isIgnoredParameter(location2, name)) continue;
48554
48622
  const key = `${location2}:${name.toLowerCase()}`;
48555
48623
  if (seen.has(key)) continue;
48556
48624
  seen.add(key);
48557
48625
  if (securityKeys.has(key)) continue;
48626
+ const contentMedia = jsonContentParameterMedia(param);
48627
+ if (contentMedia !== void 0 && (location2 === "query" || location2 === "header")) {
48628
+ const packed2 = packSchema(root, contentMedia, version, "request");
48629
+ warnings.push(...packNoteWarnings(packed2, `parameter ${location2}:${name} of ${operationId}`));
48630
+ if (packed2.unsupported) {
48631
+ warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed2.unsupported})`);
48632
+ } else if (packed2.schema !== void 0) {
48633
+ const check2 = { in: location2, name, required: param.required === true, content: true, schema: packed2.schema };
48634
+ if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
48635
+ checks.push(check2);
48636
+ }
48637
+ continue;
48638
+ }
48558
48639
  if (param.content !== void 0 || param.schema === void 0) continue;
48559
48640
  const defaultStyle = DEFAULT_PARAM_STYLES[location2];
48560
48641
  const style = typeof param.style === "string" ? param.style : defaultStyle;
48561
48642
  const defaultExplode = style === "form";
48562
48643
  const explode = typeof param.explode === "boolean" ? param.explode : defaultExplode;
48563
- if (style !== defaultStyle || explode !== defaultExplode) continue;
48644
+ const defaultSerialization = style === defaultStyle && explode === defaultExplode;
48564
48645
  const packed = packSchema(root, param.schema, version);
48646
+ const noteWarnings = packNoteWarnings(packed, `parameter ${location2}:${name} of ${operationId}`);
48647
+ if (defaultSerialization) warnings.push(...noteWarnings);
48565
48648
  if (packed.unsupported) {
48566
- warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
48649
+ if (defaultSerialization) warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: parameter ${location2}:${name} schema on ${operationId} skipped (${packed.unsupported})`);
48567
48650
  continue;
48568
48651
  }
48569
- const schema2 = packedScalarSchema(packed);
48570
- if (schema2 === void 0) continue;
48571
- const check = { in: location2, name, required: param.required === true, schema: schema2 };
48652
+ if (location2 === "path" && !pathTemplate.split("/").includes(`{${name}}`)) continue;
48653
+ const scalarSchema = packedScalarSchema(packed);
48654
+ if (scalarSchema !== void 0) {
48655
+ if (!defaultSerialization) continue;
48656
+ const check2 = { in: location2, name, required: param.required === true, schema: scalarSchema };
48657
+ if (location2 === "query" && param.allowEmptyValue === true) check2.allowEmptyValue = true;
48658
+ checks.push(check2);
48659
+ continue;
48660
+ }
48661
+ if (location2 !== "query" && location2 !== "header") continue;
48662
+ const items = packedArrayItemsSchema(packed);
48663
+ if (items === void 0) continue;
48664
+ const decode = location2 === "query" ? QUERY_ARRAY_DECODES[`${style}:${explode}`] : style === "simple" && !explode ? "csv" : void 0;
48665
+ if (!decode) continue;
48666
+ if (!defaultSerialization) warnings.push(...noteWarnings);
48667
+ const check = { in: location2, name, required: param.required === true, schema: packed.schema, decode, items };
48572
48668
  if (location2 === "query" && param.allowEmptyValue === true) check.allowEmptyValue = true;
48573
48669
  checks.push(check);
48574
48670
  }
48575
48671
  return checks.length > 0 ? checks : void 0;
48576
48672
  }
48673
+ function packNoteWarnings(packed, context) {
48674
+ return (packed.notes ?? []).map(
48675
+ (note) => note === "discriminator" ? `CONTRACT_DISCRIMINATOR_NOT_VALIDATED: discriminator on ${context} has no sibling oneOf/anyOf of internal $ref members and is not validated` : `CONTRACT_SCHEMA_NOT_COMPILED: ${note} on ${context} is not validated`
48676
+ );
48677
+ }
48577
48678
  function collectDeclaredQueryParameters(root, pathItem, operation) {
48578
48679
  const names = /* @__PURE__ */ new Set();
48579
48680
  for (const param of resolvedParameters(root, pathItem, operation)) {
@@ -48651,7 +48752,10 @@ function propertyIsBinary(root, properties, name) {
48651
48752
  }
48652
48753
  if (!schema2) return false;
48653
48754
  if (schema2.format === "binary") return true;
48654
- return typeof schema2.contentMediaType === "string" && schema2.contentMediaType.toLowerCase().startsWith("application/octet-stream");
48755
+ if (typeof schema2.contentMediaType !== "string" || typeof schema2.contentEncoding === "string") return false;
48756
+ const media = schema2.contentMediaType.toLowerCase().split(";")[0]?.trim() ?? "";
48757
+ if (!media) return false;
48758
+ return media !== "application/json" && !media.endsWith("+json") && !media.startsWith("text/");
48655
48759
  }
48656
48760
  function fieldEncodings(root, base, mediaObject, properties) {
48657
48761
  const declared = asRecord4(mediaObject?.encoding);
@@ -48669,6 +48773,9 @@ function fieldEncodings(root, base, mediaObject, properties) {
48669
48773
  if (typeof encoding.contentType === "string" && encoding.contentType.trim()) {
48670
48774
  entry.contentType = encoding.contentType.toLowerCase();
48671
48775
  }
48776
+ if (base === "multipart/form-data" && asRecord4(encoding.headers)) {
48777
+ entry.hasHeaders = true;
48778
+ }
48672
48779
  if (base === "application/x-www-form-urlencoded") {
48673
48780
  const style = typeof encoding.style === "string" ? encoding.style : "form";
48674
48781
  const explode = typeof encoding.explode === "boolean" ? encoding.explode : style === "form";
@@ -48681,7 +48788,22 @@ function fieldEncodings(root, base, mediaObject, properties) {
48681
48788
  }
48682
48789
  return Object.keys(encodings).length > 0 ? encodings : void 0;
48683
48790
  }
48684
- function requestBodyFieldRules(root, content) {
48791
+ function formFieldSchemas(root, version, properties, context, warnings) {
48792
+ const schemas = {};
48793
+ for (const name of Object.keys(properties)) {
48794
+ const packed = packSchema(root, properties[name], version, "request");
48795
+ warnings.push(...packNoteWarnings(packed, `field ${name} of ${context}`));
48796
+ if (packed.unsupported) {
48797
+ warnings.push(`CONTRACT_SCHEMA_NOT_COMPILED: field ${name} of ${context} skipped (${packed.unsupported})`);
48798
+ continue;
48799
+ }
48800
+ if (packed.schema === void 0) continue;
48801
+ const schema2 = packedScalarSchema(packed);
48802
+ if (schema2 !== void 0) schemas[name] = schema2;
48803
+ }
48804
+ return Object.keys(schemas).length > 0 ? schemas : void 0;
48805
+ }
48806
+ function requestBodyFieldRules(root, content, version, operationId, warnings) {
48685
48807
  const rules = {};
48686
48808
  for (const [contentType, mediaObject] of Object.entries(content)) {
48687
48809
  const base = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
@@ -48691,10 +48813,13 @@ function requestBodyFieldRules(root, content) {
48691
48813
  if (!merged) continue;
48692
48814
  const readOnly = Object.keys(merged.properties).filter((name) => propertyIsReadOnly(root, merged.properties, name));
48693
48815
  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) {
48816
+ const formRules = BODY_FIELD_RULE_TYPES.has(base);
48817
+ const encodings = formRules ? fieldEncodings(root, base, mediaRecord, merged.properties) : void 0;
48818
+ const fieldSchemas = formRules ? formFieldSchemas(root, version, merged.properties, `request body ${contentType} of ${operationId}`, warnings) : void 0;
48819
+ if (required.length > 0 || readOnly.length > 0 || encodings || fieldSchemas) {
48696
48820
  const rule = { required, readOnly };
48697
48821
  if (encodings) rule.encodings = encodings;
48822
+ if (fieldSchemas) rule.fieldSchemas = fieldSchemas;
48698
48823
  rules[base] = rule;
48699
48824
  }
48700
48825
  }
@@ -48715,6 +48840,7 @@ function requestBodyJsonSchemas(root, content, version, operationId, warnings) {
48715
48840
  }
48716
48841
  if (schema2 === void 0) continue;
48717
48842
  const packed = packSchema(root, schema2, version, "request");
48843
+ warnings.push(...packNoteWarnings(packed, `request body ${contentType} of ${operationId}`));
48718
48844
  if (mediaRecord) validateExamples(root, mediaRecord, packed, contentType, operationId, exampleWarnings);
48719
48845
  if (packed.unsupported) {
48720
48846
  warnings.push(`CONTRACT_REQUEST_SCHEMA_NOT_VALIDATED: request body schema for ${contentType} on ${operationId} is not validated (${packed.unsupported})`);
@@ -48729,7 +48855,7 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
48729
48855
  const body = resolveInternalRef(root, operation.requestBody);
48730
48856
  if (!body) return void 0;
48731
48857
  const content = asRecord4(body.content);
48732
- const fieldRules = content ? requestBodyFieldRules(root, content) : void 0;
48858
+ const fieldRules = content ? requestBodyFieldRules(root, content, version, operationId, warnings) : void 0;
48733
48859
  if (fieldRules) {
48734
48860
  for (const [base, rule] of Object.entries(fieldRules)) {
48735
48861
  for (const [field, encoding] of Object.entries(rule.encodings ?? {})) {
@@ -48738,6 +48864,11 @@ function collectRequestBody(root, operation, version, operationId, warnings) {
48738
48864
  `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
48865
  );
48740
48866
  }
48867
+ if (encoding.hasHeaders) {
48868
+ warnings.push(
48869
+ `CONTRACT_ENCODING_HEADERS_NOT_VALIDATED: ${base} request body field ${field} on ${operationId} declares per-part headers that generated formdata entries cannot carry`
48870
+ );
48871
+ }
48741
48872
  }
48742
48873
  }
48743
48874
  }
@@ -48785,6 +48916,7 @@ function responseContent(root, version, response, context, warnings) {
48785
48916
  const mediaRecord = asRecord4(mediaObject);
48786
48917
  const schema2 = mediaRecord?.schema;
48787
48918
  let packed = schema2 === void 0 ? {} : packSchema(root, schema2, version);
48919
+ for (const warning of packNoteWarnings(packed, `response ${contentType} of ${context}`)) warnings.add(warning);
48788
48920
  if (isSchemaGraphOverflow(packed)) {
48789
48921
  warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response schema for ${contentType} on ${context} skipped (${packed.unsupported})`);
48790
48922
  packed = {};
@@ -48816,12 +48948,18 @@ function responseHeaders(root, version, response, context, warnings) {
48816
48948
  continue;
48817
48949
  }
48818
48950
  const packed = packSchema(root, header.schema, version);
48951
+ for (const warning of packNoteWarnings(packed, `response header ${name} of ${context}`)) warnings.add(warning);
48819
48952
  if (isSchemaGraphOverflow(packed)) {
48820
48953
  warnings.add(`CONTRACT_SCHEMA_NOT_COMPILED: response header ${name} schema on ${context} skipped (${packed.unsupported})`);
48821
48954
  entries.push({ name, required });
48822
48955
  continue;
48823
48956
  }
48824
48957
  if (!packed.unsupported && packed.schema !== void 0 && packedScalarSchema(packed) === void 0) {
48958
+ const items = packedArrayItemsSchema(packed);
48959
+ if (items !== void 0) {
48960
+ entries.push({ name, required, schema: packed.schema, items });
48961
+ continue;
48962
+ }
48825
48963
  warnings.add(`CONTRACT_HEADER_SCHEMA_NOT_VALIDATED: response header ${name} on ${context} declares a non-scalar schema and its value is not validated`);
48826
48964
  entries.push({ name, required });
48827
48965
  continue;
@@ -48880,10 +49018,14 @@ function buildContractIndex(root) {
48880
49018
  path6,
48881
49019
  ...operationServers(root, pathItem, operation).map((server) => joinPaths(serverPathPrefix(server), path6))
48882
49020
  ].map(normalizePath))];
49021
+ const operationId = `${lowerMethod.toUpperCase()} ${path6}`;
48883
49022
  const opWarnings = [];
48884
49023
  opWarnings.push(...responseWarnings);
48885
49024
  opWarnings.push(...collectSecuritySchemeWarnings(root, operation));
48886
- opWarnings.push(...collectSerializationWarnings(root, pathItem, operation));
49025
+ const parameterChecks = collectParameterChecks(root, pathItem, operation, version, operationId, path6, opWarnings);
49026
+ const checkedKeys = new Set((parameterChecks ?? []).map((check) => `${check.in}:${check.name.toLowerCase()}`));
49027
+ const decodedKeys = new Set((parameterChecks ?? []).filter((check) => check.decode).map((check) => `${check.in}:${check.name.toLowerCase()}`));
49028
+ opWarnings.push(...collectSerializationWarnings(root, pathItem, operation, decodedKeys));
48887
49029
  if (operation.deprecated === true) {
48888
49030
  opWarnings.push(`CONTRACT_OPERATION_DEPRECATED: ${lowerMethod.toUpperCase()} ${path6} is marked deprecated in the OpenAPI document`);
48889
49031
  }
@@ -48892,17 +49034,19 @@ function buildContractIndex(root) {
48892
49034
  opWarnings.push(`CONTRACT_SECURITY_NOT_VALIDATED: security parameter ${parameter.in}:${parameter.name} is not statically required in generated requests`);
48893
49035
  }
48894
49036
  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`);
49037
+ opWarnings.push(checkedKeys.has(`cookie:${parameter.name.toLowerCase()}`) ? `CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not included in generated requests; the runtime test fails until the cookie is supplied at send time` : `CONTRACT_COOKIE_PARAM_NOT_VALIDATED: required cookie parameter ${parameter.name} is not included in generated requests and its value is not runtime-validated`);
48896
49038
  }
48897
49039
  const pathParamWarnings = /* @__PURE__ */ new Set();
48898
49040
  for (const param of resolvedParameters(root, pathItem, operation)) {
48899
49041
  if (String(param.in || "").toLowerCase() !== "path") continue;
48900
49042
  const name = String(param.name || "");
48901
- if (name) pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
49043
+ if (name && !checkedKeys.has(`path:${name.toLowerCase()}`)) {
49044
+ pathParamWarnings.add(`CONTRACT_PATH_PARAM_NOT_VALIDATED: path parameter ${name} value is not validated at runtime`);
49045
+ }
48902
49046
  }
48903
49047
  opWarnings.push(...pathParamWarnings);
48904
49048
  operations.push({
48905
- id: `${lowerMethod.toUpperCase()} ${path6}`,
49049
+ id: operationId,
48906
49050
  method: lowerMethod.toUpperCase(),
48907
49051
  path: path6,
48908
49052
  pointer: `/paths/${path6.replace(/~/g, "~0").replace(/\//g, "~1")}/${lowerMethod}`,
@@ -48910,8 +49054,8 @@ function buildContractIndex(root) {
48910
49054
  responses: contractResponses,
48911
49055
  requiredParameters,
48912
49056
  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),
49057
+ parameterChecks,
49058
+ requestBody: collectRequestBody(root, operation, version, operationId, opWarnings),
48915
49059
  security: collectSecurityRuntimeChecks(root, operation),
48916
49060
  warnings: opWarnings
48917
49061
  });
@@ -49118,8 +49262,11 @@ function createContractScript(operation, warnings = []) {
49118
49262
  " if (!actual) return;",
49119
49263
  ' if (header.unsupported) pm.expect.fail("OpenAPI response header unsupported for " + contract.method + " " + contract.path + ": " + header.unsupported);',
49120
49264
  " var headerValidator = validators[selected.key] && validators[selected.key].__headers && validators[selected.key].__headers[String(header.name).toLowerCase()];",
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 || []));',
49265
+ " if (!headerValidator || headerValidator.skip) return;",
49266
+ " var expected;",
49267
+ ' if (header.items) { var joined = String(actual).trim(); expected = joined === "" ? [] : joined.split(",").map(function (entry) { return coerceBySchema(entry.trim(), header.items); }); }',
49268
+ " else expected = coerceBySchema(actual, header.schema);",
49269
+ ' if (!headerValidator(expected)) pm.expect.fail("OpenAPI response header validation failed for " + header.name + ": " + JSON.stringify(headerValidator.errors || []));',
49123
49270
  " });",
49124
49271
  "});",
49125
49272
  "pm.test('Response body matches OpenAPI body contract', function () {",
@@ -49175,17 +49322,64 @@ function createContractScript(operation, warnings = []) {
49175
49322
  ...operation.parameterChecks && operation.parameterChecks.length > 0 ? [
49176
49323
  "pm.test('Request parameters match OpenAPI schemas', function () {",
49177
49324
  ' 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; }',
49325
+ ' function queryValues(name) { var values = []; pm.request.url.query.each(function (param) { if (param && param.disabled !== true && String(param.key).toLowerCase() === name) values.push(param.value === null || param.value === undefined ? "" : String(param.value)); }); return values; }',
49178
49326
  ' 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; }',
49327
+ ' function cookieValue(name) { try { if (pm.cookies && pm.cookies.get) { var jar = pm.cookies.get(String(name)); if (jar !== null && jar !== undefined) return String(jar); } } catch (ignored) {} var raw = requestHeader("Cookie"); if (!raw) return undefined; var found; raw.split(";").forEach(function (part) { var split = part.indexOf("="); if (split === -1) return; if (part.slice(0, split).trim() === String(name)) found = part.slice(split + 1).trim(); }); return found; }',
49328
+ ' function requestPathSegments() { var raw = ""; try { raw = typeof pm.request.url.getPath === "function" ? String(pm.request.url.getPath() || "") : ""; } catch (ignored) {} if (!raw) { var path = pm.request.url.path; raw = Array.isArray(path) ? "/" + path.join("/") : String(path || ""); } return raw.split("?")[0].split("#")[0].split("/").filter(function (segment) { return segment.length > 0; }); }',
49329
+ // Server path prefixes sit ahead of the template segments, so the
49330
+ // template aligns against the trailing request segments.
49331
+ ' function pathParamValue(name) { var template = String(contract.path).split("/").filter(function (segment) { return segment.length > 0; }); var actual = requestPathSegments(); var offset = actual.length - template.length; if (offset < 0) return undefined; for (var i = 0; i < template.length; i += 1) { if (template[i] === "{" + name + "}") { var segment = actual[offset + i]; try { return decodeURIComponent(segment); } catch (ignored) { return segment; } } } return undefined; }',
49179
49332
  ' function isPlaceholder(value) { var text = String(value).trim(); return /^<[^<>]*>$/.test(text) || text.indexOf("{{") !== -1; }',
49333
+ ' function splitDelimited(value, decode) { if (decode === "csv") return value.split(","); if (decode === "ssv") return value.split(/%20| /); return value.split(/%7C|\\|/i); }',
49334
+ " function decodeComponent(value) { try { return decodeURIComponent(value); } catch (ignored) { return value; } }",
49180
49335
  " (contract.parameters || []).forEach(function (param) {",
49181
49336
  ' var key = param.in + ":" + String(param.name).toLowerCase();',
49182
49337
  " var validate = paramValidators[key];",
49183
49338
  " 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 || []));',
49339
+ " var value;",
49340
+ ' if (param.decode === "multi") {',
49341
+ " var entries = queryValues(String(param.name).toLowerCase());",
49342
+ ' if (entries.length === 0) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
49343
+ ' if (param.allowEmptyValue && entries.length === 1 && entries[0] === "") return;',
49344
+ " if (entries.some(isPlaceholder)) return;",
49345
+ " value = entries.map(function (entry) { return coerceBySchema(decodeComponent(entry), param.items); });",
49346
+ " } else if (param.decode) {",
49347
+ ' var joined = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
49348
+ ' if (joined === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
49349
+ ' if (joined === "" && param.allowEmptyValue) return;',
49350
+ " if (isPlaceholder(joined)) return;",
49351
+ ' var parts = joined === "" ? [] : splitDelimited(String(joined), param.decode);',
49352
+ // HTTP header lists allow optional whitespace after the comma, so
49353
+ // header-sourced items are trimmed; query values stay literal.
49354
+ ' if (param.in === "header") parts = parts.map(function (entry) { return entry.trim(); });',
49355
+ " if (parts.some(isPlaceholder)) return;",
49356
+ // Query values arrive percent-encoded while the server validates the
49357
+ // decoded form; header values are never percent-encoded.
49358
+ ' value = parts.map(function (entry) { return coerceBySchema(param.in === "query" ? decodeComponent(entry) : entry, param.items); });',
49359
+ ' } else if (param.in === "path") {',
49360
+ " value = pathParamValue(param.name);",
49361
+ " if (value === undefined) return;",
49362
+ ' if (isPlaceholder(value) || value.charAt(0) === ":" || value.charAt(0) === "{") return;',
49363
+ " value = coerceBySchema(value, param.schema);",
49364
+ ' } else if (param.in === "cookie") {',
49365
+ " value = cookieValue(param.name);",
49366
+ ' if (value === undefined) { if (param.required) pm.expect.fail("Required cookie parameter " + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
49367
+ " if (isPlaceholder(value)) return;",
49368
+ " value = coerceBySchema(value, param.schema);",
49369
+ " } else {",
49370
+ ' value = param.in === "query" ? queryValue(String(param.name).toLowerCase()) : headerValue(param.name);',
49371
+ ' if (value === undefined) { if (param.required) pm.expect.fail("Required parameter " + param.in + ":" + param.name + " was not sent for " + contract.method + " " + contract.path); return; }',
49372
+ ' if (value === "" && param.allowEmptyValue) return;',
49373
+ " if (isPlaceholder(value)) return;",
49374
+ " if (param.content) {",
49375
+ " var parsed;",
49376
+ ' try { parsed = JSON.parse(value); } catch (parseError) { pm.expect.fail("Parameter " + param.in + ":" + param.name + " declares JSON content but its value is not parseable JSON for " + contract.method + " " + contract.path); return; }',
49377
+ ' if (!validate(parsed)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
49378
+ " return;",
49379
+ " }",
49380
+ " value = coerceBySchema(value, param.schema);",
49381
+ " }",
49382
+ ' if (!validate(value)) pm.expect.fail("Parameter " + param.in + ":" + param.name + " failed OpenAPI schema validation for " + contract.method + " " + contract.path + ": " + JSON.stringify(validate.errors || []));',
49189
49383
  " });",
49190
49384
  "});"
49191
49385
  ] : [],
@@ -49308,15 +49502,25 @@ function assertStaticRequestShape(operation, request) {
49308
49502
  if (!contentType) {
49309
49503
  throw new Error(`CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} missing required request Content-Type`);
49310
49504
  }
49311
- const actual = contentType.toLowerCase().split(";")[0]?.trim();
49312
- const matches = operation.requestBody.contentTypes.some((expected) => expected.toLowerCase() === actual);
49505
+ const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
49506
+ const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
49313
49507
  if (!matches) {
49314
49508
  throw new Error(
49315
49509
  `CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} request Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
49316
49510
  );
49317
49511
  }
49318
49512
  }
49319
- return collectStaticBodyWarnings(operation, request, contentType);
49513
+ const warnings = collectStaticBodyWarnings(operation, request, contentType);
49514
+ if (operation.requestBody && !operation.requestBody.required && operation.requestBody.contentTypes.length > 0 && hasRequestBody(request) && contentType) {
49515
+ const actual = contentType.toLowerCase().split(";")[0]?.trim() ?? "";
49516
+ const matches = operation.requestBody.contentTypes.some((expected) => mediaTypeMatchesPattern(expected.toLowerCase(), actual));
49517
+ if (!matches) {
49518
+ warnings.push(
49519
+ `CONTRACT_STATIC_REQUEST_CHECK_FAILED: ${operation.id} optional request body Content-Type ${contentType} does not match ${operation.requestBody.contentTypes.join(", ")}`
49520
+ );
49521
+ }
49522
+ }
49523
+ return warnings;
49320
49524
  }
49321
49525
  function requestBodyFieldNames(request, base) {
49322
49526
  const body = asRecord5(request.body);
@@ -49351,28 +49555,80 @@ function mediaTypeMatchesPattern(pattern, actual) {
49351
49555
  return false;
49352
49556
  });
49353
49557
  }
49558
+ function isJsonEncodingContentType(declared) {
49559
+ return declared.split(",").some((candidate) => {
49560
+ const entry = (candidate.split(";")[0] ?? "").trim();
49561
+ return entry === "application/json" || entry.endsWith("+json");
49562
+ });
49563
+ }
49564
+ function isPlaceholderValue(value) {
49565
+ return /^<[^>]*>$/.test(value.trim()) || value.includes("{{");
49566
+ }
49354
49567
  function collectStaticEncodingWarnings(operation, request, base, rule) {
49355
49568
  const encodings = rule.encodings;
49356
- if (!encodings || base !== "multipart/form-data") return [];
49569
+ if (!encodings) return [];
49570
+ const multipart = base === "multipart/form-data";
49357
49571
  const entries = requestBodyEntries(request, base);
49358
49572
  if (!entries) return [];
49359
49573
  const warnings = [];
49574
+ const part = multipart ? "multipart" : "urlencoded";
49360
49575
  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
- );
49576
+ for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
49577
+ if (multipart && encoding.binary && String(entry.type || "") !== "file") {
49578
+ warnings.push(`CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} should be a file part per its binary schema`);
49579
+ }
49580
+ if (multipart && encoding.contentType) {
49581
+ const actual = typeof entry.contentType === "string" ? (entry.contentType.toLowerCase().split(";")[0] ?? "").trim() : "";
49582
+ if (!actual) {
49583
+ warnings.push(
49584
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} does not declare Content-Type ${encoding.contentType} from its encoding object`
49585
+ );
49586
+ } else if (!mediaTypeMatchesPattern(encoding.contentType, actual)) {
49587
+ warnings.push(
49588
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated multipart field ${field} Content-Type ${actual} does not match declared encoding ${encoding.contentType}`
49589
+ );
49590
+ }
49591
+ }
49592
+ if (encoding.contentType && isJsonEncodingContentType(encoding.contentType) && String(entry.type || "text") !== "file") {
49593
+ const value = typeof entry.value === "string" ? entry.value : "";
49594
+ if (value.trim() && !isPlaceholderValue(value)) {
49595
+ try {
49596
+ JSON.parse(value);
49597
+ } catch {
49598
+ warnings.push(
49599
+ `CONTRACT_ENCODING_MISMATCH: ${operation.id} generated ${part} field ${field} declares JSON encoding ${encoding.contentType} but its value is not parseable JSON`
49600
+ );
49601
+ }
49602
+ }
49603
+ }
49604
+ }
49605
+ }
49606
+ return warnings;
49607
+ }
49608
+ function coerceFormValue(value, schema2) {
49609
+ const record = asRecord5(schema2);
49610
+ const type2 = record?.type;
49611
+ const types2 = Array.isArray(type2) ? type2 : [type2];
49612
+ if ((types2.includes("integer") || types2.includes("number")) && /^-?[0-9]+([.][0-9]+)?([eE][+-]?[0-9]+)?$/.test(value.trim())) return Number(value);
49613
+ if (types2.includes("boolean") && (value === "true" || value === "false")) return value === "true";
49614
+ return value;
49615
+ }
49616
+ function collectStaticFieldSchemaWarnings(operation, request, base, rule) {
49617
+ const fieldSchemas = rule.fieldSchemas;
49618
+ if (!fieldSchemas) return [];
49619
+ const entries = requestBodyEntries(request, base);
49620
+ if (!entries) return [];
49621
+ const part = base === "multipart/form-data" ? "multipart" : "urlencoded";
49622
+ const warnings = [];
49623
+ for (const [field, schema2] of Object.entries(fieldSchemas)) {
49624
+ const validate2 = compileSchemaValidator(schema2);
49625
+ if (!validate2) continue;
49626
+ for (const entry of entries.filter((candidate) => String(candidate.key || "") === field)) {
49627
+ if (String(entry.type || "text") === "file") continue;
49628
+ const value = typeof entry.value === "string" ? entry.value : "";
49629
+ if (!value.trim() || isPlaceholderValue(value)) continue;
49630
+ if (!validate2(coerceFormValue(value, schema2))) {
49631
+ warnings.push(`CONTRACT_FORM_FIELD_SCHEMA_MISMATCH: ${operation.id} generated ${part} field ${field} value does not match its schema`);
49376
49632
  }
49377
49633
  }
49378
49634
  }
@@ -49384,7 +49640,10 @@ function collectStaticBodyWarnings(operation, request, contentType) {
49384
49640
  const base = (contentType || "").toLowerCase().split(";")[0]?.trim() ?? "";
49385
49641
  const rule = rules[base];
49386
49642
  if (!rule) return [];
49387
- const warnings = [...collectStaticEncodingWarnings(operation, request, base, rule)];
49643
+ const warnings = [
49644
+ ...collectStaticEncodingWarnings(operation, request, base, rule),
49645
+ ...collectStaticFieldSchemaWarnings(operation, request, base, rule)
49646
+ ];
49388
49647
  const names = requestBodyFieldNames(request, base);
49389
49648
  if (!names) return warnings;
49390
49649
  const present = new Set(names);