@postman-cse/onboarding-bootstrap 2.5.0 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/action.cjs CHANGED
@@ -296954,12 +296954,36 @@ function buildOperationScript(operation, index, warnings) {
296954
296954
  ' if (mediaType === "application/json" && gqlBody.data !== undefined && gqlBody.data !== null && pm.response.code !== 200) { pm.expect.fail("a GraphQL response carrying data over application/json must be HTTP 200; got " + pm.response.code); }',
296955
296955
  "});"
296956
296956
  );
296957
+ lines.push(
296958
+ `pm.test(${JSON.stringify(`[${label}] GraphQL response map follows the spec response format`)}, function () {`,
296959
+ ' var extraKeys = Object.keys(gqlBody).filter(function (key) { return key !== "data" && key !== "errors" && key !== "extensions"; });',
296960
+ ' if (extraKeys.length > 0) pm.expect.fail("the GraphQL response map must not contain entries other than data, errors, and extensions (GraphQL spec 7.1): " + extraKeys.join(", "));',
296961
+ ' if (Object.prototype.hasOwnProperty.call(gqlBody, "extensions") && (typeof gqlBody.extensions !== "object" || gqlBody.extensions === null || Array.isArray(gqlBody.extensions))) pm.expect.fail("the GraphQL extensions entry must be a map (GraphQL spec 7.1)");',
296962
+ ' if (!Object.prototype.hasOwnProperty.call(gqlBody, "data") && (!Array.isArray(gqlBody.errors) || gqlBody.errors.length === 0)) pm.expect.fail("a GraphQL request-error result (no data entry) must carry a non-empty errors list (GraphQL spec 7.1)");',
296963
+ "});"
296964
+ );
296957
296965
  lines.push(
296958
296966
  `pm.test(${JSON.stringify(`[${label}] GraphQL errors are well-formed and not a total failure`)}, function () {`,
296959
296967
  " var errors = gqlBody.errors;",
296960
296968
  " if (errors === undefined || errors === null) return;",
296961
296969
  ` pm.expect(errors, "GraphQL 'errors' must be an array when present").to.be.an("array");`,
296962
- ' errors.forEach(function (err) { pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message"); });',
296970
+ " errors.forEach(function (err, errIndex) {",
296971
+ ' pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message");',
296972
+ ' if (typeof err.message !== "string") pm.expect.fail("errors[" + errIndex + "].message must be a string (GraphQL spec 7.1.2)");',
296973
+ " if (err.locations !== undefined && err.locations !== null) {",
296974
+ ' if (!Array.isArray(err.locations)) { pm.expect.fail("errors[" + errIndex + "].locations must be a list (GraphQL spec 7.1.2)"); return; }',
296975
+ " err.locations.forEach(function (loc, locIndex) {",
296976
+ ' var locLabel = "errors[" + errIndex + "].locations[" + locIndex + "]";',
296977
+ ' if (!loc || typeof loc !== "object" || Array.isArray(loc)) { pm.expect.fail(locLabel + " must be a map with line and column (GraphQL spec 7.1.2)"); return; }',
296978
+ ' if (!Number.isInteger(loc.line) || loc.line < 1 || !Number.isInteger(loc.column) || loc.column < 1) pm.expect.fail(locLabel + " line and column must be positive integers starting from 1 (GraphQL spec 7.1.2)");',
296979
+ " });",
296980
+ " }",
296981
+ " if (err.path !== undefined && err.path !== null) {",
296982
+ ' if (!Array.isArray(err.path)) { pm.expect.fail("errors[" + errIndex + "].path must be a list of path segments (GraphQL spec 7.1.2)"); return; }',
296983
+ ' err.path.forEach(function (segment, segIndex) { if (typeof segment !== "string" && !Number.isInteger(segment)) pm.expect.fail("errors[" + errIndex + "].path[" + segIndex + "] must be a string field name or an integer list index (GraphQL spec 7.1.2)"); });',
296984
+ " }",
296985
+ ' if (err.extensions !== undefined && err.extensions !== null && (typeof err.extensions !== "object" || Array.isArray(err.extensions))) pm.expect.fail("errors[" + errIndex + "].extensions must be a map (GraphQL spec 7.1.2)");',
296986
+ " });",
296963
296987
  " var hasData = gqlBody.data !== undefined && gqlBody.data !== null;",
296964
296988
  ' if (!hasData && errors.length > 0) { pm.expect.fail("GraphQL request returned errors and no data: " + JSON.stringify(errors)); }',
296965
296989
  "});"
@@ -297794,6 +297818,71 @@ function createGrpcScript(spec) {
297794
297818
  ' else if (!/^[\\x20-\\x7e]*$/.test(value)) pm.expect.fail(label + " " + key + " value must be printable ASCII (gRPC ASCII-Value)");',
297795
297819
  " });",
297796
297820
  "}",
297821
+ // grpc-status-details-bin carries a base64-encoded google.rpc.Status protobuf
297822
+ // (the gRPC richer-error model). The sandbox has no protobuf runtime, so the
297823
+ // script walks the protobuf wire format directly: base64 -> bytes -> tagged
297824
+ // fields (varint / 64-bit / length-delimited / 32-bit). Groups (wire types
297825
+ // 3/4) are proto2-deprecated and never emitted by google.rpc.Status.
297826
+ "function grpcB64Bytes(value) {",
297827
+ ' var clean = String(value).replace(/\\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");',
297828
+ " if (clean.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(clean)) return null;",
297829
+ ' var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";',
297830
+ ' var unpadded = clean.replace(/=+$/, ""); var bytes = []; var buffer = 0; var bits = 0;',
297831
+ " for (var i = 0; i < unpadded.length; i++) {",
297832
+ " var idx = alphabet.indexOf(unpadded.charAt(i));",
297833
+ " if (idx === -1) return null;",
297834
+ " buffer = (buffer << 6) | idx; bits += 6;",
297835
+ " if (bits >= 8) { bits -= 8; bytes.push((buffer >> bits) & 0xff); }",
297836
+ " }",
297837
+ " return bytes;",
297838
+ "}",
297839
+ "function grpcPbVarint(bytes, pos) {",
297840
+ " var value = 0; var shift = 0;",
297841
+ " while (pos < bytes.length) {",
297842
+ " var b = bytes[pos]; pos += 1;",
297843
+ " value += (b & 0x7f) * Math.pow(2, shift); shift += 7;",
297844
+ " if ((b & 0x80) === 0) return value > 9007199254740991 ? null : { value: value, pos: pos };",
297845
+ " if (shift > 63) return null;",
297846
+ " }",
297847
+ " return null;",
297848
+ "}",
297849
+ "function grpcPbFields(bytes) {",
297850
+ " var pos = 0; var fields = [];",
297851
+ " while (pos < bytes.length) {",
297852
+ " var tag = grpcPbVarint(bytes, pos); if (!tag) return null;",
297853
+ " pos = tag.pos;",
297854
+ " var fieldNumber = Math.floor(tag.value / 8); var wire = tag.value % 8;",
297855
+ " if (fieldNumber < 1) return null;",
297856
+ " if (wire === 0) { var varint = grpcPbVarint(bytes, pos); if (!varint) return null; fields.push({ field: fieldNumber, wire: 0, value: varint.value }); pos = varint.pos; }",
297857
+ " else if (wire === 1) { if (pos + 8 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 1 }); pos += 8; }",
297858
+ " else if (wire === 2) { var len = grpcPbVarint(bytes, pos); if (!len) return null; pos = len.pos; if (pos + len.value > bytes.length) return null; fields.push({ field: fieldNumber, wire: 2, bytes: bytes.slice(pos, pos + len.value) }); pos += len.value; }",
297859
+ " else if (wire === 5) { if (pos + 4 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 5 }); pos += 4; }",
297860
+ " else return null;",
297861
+ " }",
297862
+ " return fields;",
297863
+ "}",
297864
+ "function grpcCheckStatusDetailsBin(entry) {",
297865
+ ' if (!entry || entry.value === undefined || entry.value === null || String(entry.value) === "") return;',
297866
+ " var detailBytes = grpcB64Bytes(entry.value);",
297867
+ ' if (!detailBytes) { pm.expect.fail("grpc-status-details-bin trailer must carry base64-encoded bytes but was " + JSON.stringify(String(entry.value))); return; }',
297868
+ " var statusFields = grpcPbFields(detailBytes);",
297869
+ ' if (!statusFields) { pm.expect.fail("grpc-status-details-bin does not decode as a protobuf wire message (google.rpc.Status)"); return; }',
297870
+ " statusFields.forEach(function (statusField) {",
297871
+ " if (statusField.field === 1) {",
297872
+ ' if (statusField.wire !== 0) { pm.expect.fail("google.rpc.Status.code (field 1) in grpc-status-details-bin must be varint-encoded"); return; }',
297873
+ ' if (statusField.value < 0 || statusField.value > 16) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin must be a canonical gRPC code 0-16 but was " + statusField.value);',
297874
+ ' else if (statusField.value !== pm.response.code) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin (" + statusField.value + ") disagrees with the reported status code (" + pm.response.code + ")");',
297875
+ " } else if (statusField.field === 2) {",
297876
+ ' if (statusField.wire !== 2) pm.expect.fail("google.rpc.Status.message (field 2) in grpc-status-details-bin must be length-delimited");',
297877
+ " } else if (statusField.field === 3) {",
297878
+ ' if (statusField.wire !== 2) { pm.expect.fail("google.rpc.Status.details (field 3) in grpc-status-details-bin must be length-delimited google.protobuf.Any messages"); return; }',
297879
+ " var anyFields = grpcPbFields(statusField.bytes);",
297880
+ ' if (!anyFields) { pm.expect.fail("google.rpc.Status.details entry in grpc-status-details-bin is not a decodable google.protobuf.Any message"); return; }',
297881
+ " var hasTypeUrl = anyFields.some(function (anyField) { return anyField.field === 1 && anyField.wire === 2 && anyField.bytes.length > 0; });",
297882
+ ' if (!hasTypeUrl) pm.expect.fail("google.protobuf.Any in grpc-status-details-bin details must carry a non-empty type_url (field 1)");',
297883
+ " }",
297884
+ " });",
297885
+ "}",
297797
297886
  `pm.test('gRPC status is OK for ' + grpcSpec.methodPath, function () {`,
297798
297887
  ' pm.expect(pm.response.code, "gRPC call for " + grpcSpec.methodPath + " returned " + grpcStatusName() + " (" + pm.response.code + ")").to.eql(0);',
297799
297888
  "});",
@@ -297810,6 +297899,7 @@ function createGrpcScript(spec) {
297810
297899
  ' if (!Number.isInteger(echoedCode) || echoedCode < 0 || echoedCode > 16) pm.expect.fail("grpc-status trailer must be a canonical gRPC code 0-16 but was " + JSON.stringify(echoedStatus.value));',
297811
297900
  ' else if (echoedCode !== pm.response.code) pm.expect.fail("grpc-status trailer (" + echoedCode + ") disagrees with the reported status code (" + pm.response.code + ")");',
297812
297901
  " }",
297902
+ ' grpcCheckStatusDetailsBin(grpcMetaOne(pm.response.trailers, "grpc-status-details-bin"));',
297813
297903
  "});",
297814
297904
  // Terminal-response-message expectation. unary and client-streaming RPCs
297815
297905
  // return exactly one terminal response message; server/bidi streaming may
@@ -303648,6 +303738,7 @@ function createSoapScript(operation, warnings = []) {
303648
303738
  };
303649
303739
  const responseRegex = operation.expectedResponseElement ? elementPresenceRegex(operation.expectedResponseElement) : "";
303650
303740
  const mediaType = operation.soapVersion === "1.2" ? "application/soap+xml" : "text/xml";
303741
+ const envelopeNs = operation.soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
303651
303742
  const faultStatusLine = operation.soapVersion === "1.2" ? ' if (faulted && code !== 500 && code !== 400) pm.expect.fail("SOAP 1.2 Faults ride HTTP 500, or 400 for env:Sender faults (SOAP 1.2 Part 2 HTTP binding); got HTTP " + code);' : ' if (faulted && code !== 500) pm.expect.fail("SOAP 1.1 Faults must ride HTTP 500 (WS-I Basic Profile R1126); got HTTP " + code);';
303652
303743
  const lines = [
303653
303744
  `var soap = JSON.parse(${JSON.stringify(JSON.stringify(meta))});`,
@@ -303670,6 +303761,11 @@ function createSoapScript(operation, warnings = []) {
303670
303761
  ' pm.expect(bodyText, "response body is not a SOAP envelope").to.match(matchTag("Envelope"));',
303671
303762
  "});",
303672
303763
  "",
303764
+ `pm.test('SOAP Envelope namespace matches SOAP ${operation.soapVersion}', function () {`,
303765
+ ' if (!matchTag("Envelope").test(bodyText)) return;',
303766
+ ` pm.expect(bodyText.indexOf(${JSON.stringify(envelopeNs)}) !== -1, "SOAP ${operation.soapVersion} envelopes must declare the ${envelopeNs} namespace").to.equal(true);`,
303767
+ "});",
303768
+ "",
303673
303769
  "pm.test('SOAP Body element is present', function () {",
303674
303770
  ' pm.expect(bodyText, "SOAP envelope has no Body element").to.match(matchTag("Body"));',
303675
303771
  "});",
@@ -303687,7 +303783,33 @@ function createSoapScript(operation, warnings = []) {
303687
303783
  " var code = pm.response.code;",
303688
303784
  faultStatusLine,
303689
303785
  ' if (!faulted && code === 500) pm.expect.fail("HTTP 500 from a SOAP endpoint must carry a SOAP Fault in the body");',
303690
- "});"
303786
+ "});",
303787
+ // Fault well-formedness (diagnostic companion to the Fault-absence test: a
303788
+ // faulted run already fails, this pinpoints a malformed Fault). SOAP 1.1
303789
+ // section 4.4 requires faultcode + faultstring children; SOAP 1.2 Part 1
303790
+ // section 5.4 requires Code/Value + Reason/Text and pins the top-level
303791
+ // Value QName to the five defined fault codes.
303792
+ ...operation.soapVersion === "1.2" ? [
303793
+ "",
303794
+ "pm.test('SOAP Fault is well-formed for SOAP 1.2', function () {",
303795
+ ' if (!matchTag("Fault").test(bodyText)) return;',
303796
+ ' if (!matchTag("Code").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault must carry an env:Code child (SOAP 1.2 Part 1 section 5.4)"); return; }',
303797
+ ' if (!matchTag("Value").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault Code must carry an env:Value child (SOAP 1.2 Part 1 section 5.4.1)"); return; }',
303798
+ ' if (!matchTag("Reason").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault must carry an env:Reason child (SOAP 1.2 Part 1 section 5.4)");',
303799
+ ' else if (!matchTag("Text").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault Reason must carry an env:Text child (SOAP 1.2 Part 1 section 5.4.2)");',
303800
+ ' var faultValue = (bodyText.match(/<(?:[A-Za-z_][\\w.-]*:)?Value[^>]*>([\\s\\S]*?)<\\//) || [])[1] || "";',
303801
+ ' var faultLocal = faultValue.trim().split(":").pop();',
303802
+ ' var faultCodes = ["VersionMismatch", "MustUnderstand", "DataEncodingUnknown", "Sender", "Receiver"];',
303803
+ ' if (faultLocal && faultCodes.indexOf(faultLocal) === -1) pm.expect.fail("the SOAP 1.2 Fault Code Value must be one of " + faultCodes.join(", ") + " (SOAP 1.2 Part 1 table 4); got " + faultValue.trim());',
303804
+ "});"
303805
+ ] : [
303806
+ "",
303807
+ "pm.test('SOAP Fault is well-formed for SOAP 1.1', function () {",
303808
+ ' if (!matchTag("Fault").test(bodyText)) return;',
303809
+ ' if (!matchTag("faultcode").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultcode child (SOAP 1.1 section 4.4)");',
303810
+ ' if (!matchTag("faultstring").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultstring child (SOAP 1.1 section 4.4)");',
303811
+ "});"
303812
+ ]
303691
303813
  ];
303692
303814
  if (responseRegex) {
303693
303815
  lines.push(
@@ -303864,7 +303986,8 @@ function wsBindingKeyValues(channel) {
303864
303986
  const value = asRecord15(wsBinding.value()) ?? {};
303865
303987
  return {
303866
303988
  headers: bindingKeyValues(value.headers),
303867
- queryParams: bindingKeyValues(value.query)
303989
+ queryParams: bindingKeyValues(value.query),
303990
+ binding: value
303868
303991
  };
303869
303992
  }
303870
303993
  function collectMqttInfo(channel, servers, messagesRaw, documentJson) {
@@ -303938,6 +304061,8 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
303938
304061
  const replySchema = replyByMessageId.get(message.id());
303939
304062
  const ackSchema = xAckSchema ?? replySchema;
303940
304063
  const ackSource = xAckSchema ? "x-ack" : replySchema ? "reply" : void 0;
304064
+ const correlationRaw = asRecord15(rawMessage.correlationId)?.location;
304065
+ const correlationLocation = typeof correlationRaw === "string" ? correlationRaw : void 0;
303941
304066
  const examples = message.examples().all().filter((example) => example.hasPayload());
303942
304067
  const hasExample = examples.length > 0;
303943
304068
  let sample;
@@ -303959,6 +304084,7 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
303959
304084
  payloadSchema,
303960
304085
  ackSchema,
303961
304086
  ackSource,
304087
+ correlationLocation,
303962
304088
  sample,
303963
304089
  hasExample,
303964
304090
  contentKind,
@@ -303971,6 +304097,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
303971
304097
  const servers = resolveServers(channel, document2);
303972
304098
  const messagesRaw = channel.messages().all();
303973
304099
  const transport = detectTransport(channel, servers, messagesRaw, documentJson, warnings);
304100
+ const parameterNames = Object.keys(asRecord15(channel.json().parameters) ?? {}).sort();
303974
304101
  const serverUrl = servers.find((server) => server.url())?.url() || options.endpointUrl?.trim() || "";
303975
304102
  if (!serverUrl) {
303976
304103
  warnings.push(`ASYNCAPI_NO_SERVER_URL: channel ${channel.id()} has no resolvable server url; the generated request url is derived from the channel address only and must be completed before use`);
@@ -303990,6 +304117,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
303990
304117
  headers: [],
303991
304118
  queryParams: [],
303992
304119
  mqtt: collectMqttInfo(channel, servers, messagesRaw, documentJson),
304120
+ parameterNames,
303993
304121
  messages,
303994
304122
  warnings
303995
304123
  };
@@ -304004,11 +304132,12 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
304004
304132
  queryParams: [],
304005
304133
  socketioNamespace: address.startsWith("/") ? address : `/${address}`,
304006
304134
  socketioPath: DEFAULT_SOCKETIO_PATH,
304135
+ parameterNames,
304007
304136
  messages,
304008
304137
  warnings
304009
304138
  };
304010
304139
  }
304011
- const { headers, queryParams } = wsBindingKeyValues(channel);
304140
+ const { headers, queryParams, binding } = wsBindingKeyValues(channel);
304012
304141
  return {
304013
304142
  id: channel.id(),
304014
304143
  address,
@@ -304016,6 +304145,8 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
304016
304145
  url: joinUrl(serverUrl, address),
304017
304146
  headers,
304018
304147
  queryParams,
304148
+ parameterNames,
304149
+ wsBinding: binding,
304019
304150
  messages,
304020
304151
  warnings
304021
304152
  };
@@ -304453,14 +304584,68 @@ function collectMessageNodeIds(node, ids, path10) {
304453
304584
  if (record) collectMessageNodeIds(record, ids, `${path10}/${i}`);
304454
304585
  });
304455
304586
  }
304587
+ function validateChannelParameters(channel, warnings) {
304588
+ const declared = new Set(channel.parameterNames ?? []);
304589
+ const used = /* @__PURE__ */ new Set();
304590
+ for (const match of channel.address.matchAll(/\{([^{}]*)\}/g)) {
304591
+ const name = match[1] ?? "";
304592
+ if (!name) {
304593
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_INVALID: channel ${channel.id} address ${channel.address} contains an empty {} parameter expression`);
304594
+ continue;
304595
+ }
304596
+ used.add(name);
304597
+ if (!declared.has(name)) {
304598
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNDECLARED: channel ${channel.id} address parameter {${name}} has no entry in the channel parameters object; AsyncAPI requires every address parameter to be declared`);
304599
+ }
304600
+ }
304601
+ for (const name of declared) {
304602
+ if (!used.has(name)) {
304603
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNUSED: channel ${channel.id} declares parameter ${name} that never appears in the channel address ${channel.address}`);
304604
+ }
304605
+ }
304606
+ }
304607
+ var CORRELATION_LOCATION_GRAMMAR = /^\$message\.(header|payload)#(\/(?:[^~/]|~[01])*)+$/;
304608
+ function validateCorrelationLocation(channelId, message, warnings) {
304609
+ if (message.correlationLocation === void 0) return;
304610
+ if (!CORRELATION_LOCATION_GRAMMAR.test(message.correlationLocation)) {
304611
+ warnings.push(
304612
+ `ASYNCAPI_CORRELATION_LOCATION_INVALID: message ${message.id} on channel ${channelId} correlationId location ${JSON.stringify(message.correlationLocation)} is not a valid AsyncAPI runtime expression ($message.header#/<pointer> or $message.payload#/<pointer>)`
304613
+ );
304614
+ }
304615
+ }
304616
+ var SOCKETIO_RESERVED_EVENTS = /* @__PURE__ */ new Set(["connect", "connect_error", "disconnect", "disconnecting", "newListener", "removeListener"]);
304617
+ function validateWsBinding(channel, warnings) {
304618
+ const binding = channel.wsBinding;
304619
+ if (!binding) return;
304620
+ const method = binding.method;
304621
+ if (method !== void 0 && (typeof method !== "string" || method !== "GET" && method !== "POST")) {
304622
+ warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding method must be "GET" or "POST" but was ${JSON.stringify(method)}`);
304623
+ }
304624
+ for (const key of ["query", "headers"]) {
304625
+ const schema = binding[key];
304626
+ if (schema === void 0) continue;
304627
+ const record = asRecord16(schema);
304628
+ if (!record || record.type !== void 0 && record.type !== "object") {
304629
+ warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding ${key} must be a Schema Object of type object`);
304630
+ }
304631
+ }
304632
+ }
304456
304633
  function instrumentAsyncApiCollection(collection, index) {
304457
304634
  const warnings = [
304458
304635
  ...index.warnings,
304459
304636
  ...index.channels.flatMap((channel) => channel.warnings)
304460
304637
  ];
304461
304638
  for (const channel of index.channels) {
304639
+ validateChannelParameters(channel, warnings);
304462
304640
  if (channel.transport === "mqtt") validateMqttChannel(channel, warnings);
304641
+ if (channel.transport === "ws-raw") validateWsBinding(channel, warnings);
304463
304642
  for (const message of channel.messages) {
304643
+ if (channel.transport === "socketio" && SOCKETIO_RESERVED_EVENTS.has(message.eventName)) {
304644
+ warnings.push(
304645
+ `ASYNCAPI_SOCKETIO_RESERVED_EVENT: message ${message.id} on channel ${channel.id} uses the reserved Socket.IO event name "${message.eventName}"; reserved lifecycle events cannot be emitted or received as application events`
304646
+ );
304647
+ }
304648
+ validateCorrelationLocation(channel.id, message, warnings);
304464
304649
  validateMessage(index, channel.id, message, warnings);
304465
304650
  }
304466
304651
  }
@@ -304638,6 +304823,7 @@ function toolDescriptor(tool, warnings) {
304638
304823
  description: typeof tool.description === "string" ? tool.description : void 0,
304639
304824
  inputSchema,
304640
304825
  outputSchema: asRecord17(tool.outputSchema) ?? void 0,
304826
+ annotations: asRecord17(tool.annotations) ?? void 0,
304641
304827
  sampleArguments: inputSchema ? sampleFromSchema2(inputSchema, 0) : {},
304642
304828
  warnings: toolWarnings
304643
304829
  };
@@ -304838,10 +305024,41 @@ function validateServer(server, warnings) {
304838
305024
  }
304839
305025
  }
304840
305026
  }
305027
+ var TOOL_ANNOTATION_BOOLEAN_HINTS = ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"];
305028
+ function validateToolAnnotations(tool, warnings) {
305029
+ if (!tool.annotations) return;
305030
+ for (const hint of TOOL_ANNOTATION_BOOLEAN_HINTS) {
305031
+ const value = tool.annotations[hint];
305032
+ if (value !== void 0 && typeof value !== "boolean") {
305033
+ warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.${hint} must be a boolean (MCP ToolAnnotations); got ${JSON.stringify(value)}`);
305034
+ }
305035
+ }
305036
+ if (tool.annotations.title !== void 0 && typeof tool.annotations.title !== "string") {
305037
+ warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.title must be a string (MCP ToolAnnotations); got ${JSON.stringify(tool.annotations.title)}`);
305038
+ }
305039
+ }
305040
+ function validateToolOutputSchema(index, tool, warnings) {
305041
+ if (!tool.outputSchema) return;
305042
+ const declaredType = tool.outputSchema.type;
305043
+ if (declaredType !== void 0 && declaredType !== "object") {
305044
+ warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_INVALID: tool ${tool.name} outputSchema type is ${JSON.stringify(declaredType)}; the MCP specification requires tool output schemas to describe the structuredContent object`);
305045
+ }
305046
+ const packed = packSchema(index.documentJson, tool.outputSchema, "3.0", "response");
305047
+ if (packed.unsupported) {
305048
+ const code = isSchemaGraphOverflow(packed) ? "MCP_SCHEMA_NOT_COMPILED" : "MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED";
305049
+ warnings.push(`${code}: tool ${tool.name} outputSchema is not validated (${packed.unsupported})`);
305050
+ return;
305051
+ }
305052
+ if (!compileSchemaValidator(packed.schema)) {
305053
+ warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED: tool ${tool.name} outputSchema could not be compiled to a validator`);
305054
+ }
305055
+ }
304841
305056
  function validateTool(index, tool, warnings) {
304842
305057
  if (!TOOL_NAME_RE.test(tool.name)) {
304843
305058
  warnings.push(`MCP_TOOL_NAME_UNCONVENTIONAL: tool name "${tool.name}" is outside the conventional [A-Za-z0-9_./-]{1,128} identifier set`);
304844
305059
  }
305060
+ validateToolAnnotations(tool, warnings);
305061
+ validateToolOutputSchema(index, tool, warnings);
304845
305062
  if (!tool.inputSchema) return;
304846
305063
  const declaredType = tool.inputSchema.type;
304847
305064
  if (declaredType !== void 0 && declaredType !== "object") {
package/dist/cli.cjs CHANGED
@@ -295272,12 +295272,36 @@ function buildOperationScript(operation, index, warnings) {
295272
295272
  ' if (mediaType === "application/json" && gqlBody.data !== undefined && gqlBody.data !== null && pm.response.code !== 200) { pm.expect.fail("a GraphQL response carrying data over application/json must be HTTP 200; got " + pm.response.code); }',
295273
295273
  "});"
295274
295274
  );
295275
+ lines.push(
295276
+ `pm.test(${JSON.stringify(`[${label}] GraphQL response map follows the spec response format`)}, function () {`,
295277
+ ' var extraKeys = Object.keys(gqlBody).filter(function (key) { return key !== "data" && key !== "errors" && key !== "extensions"; });',
295278
+ ' if (extraKeys.length > 0) pm.expect.fail("the GraphQL response map must not contain entries other than data, errors, and extensions (GraphQL spec 7.1): " + extraKeys.join(", "));',
295279
+ ' if (Object.prototype.hasOwnProperty.call(gqlBody, "extensions") && (typeof gqlBody.extensions !== "object" || gqlBody.extensions === null || Array.isArray(gqlBody.extensions))) pm.expect.fail("the GraphQL extensions entry must be a map (GraphQL spec 7.1)");',
295280
+ ' if (!Object.prototype.hasOwnProperty.call(gqlBody, "data") && (!Array.isArray(gqlBody.errors) || gqlBody.errors.length === 0)) pm.expect.fail("a GraphQL request-error result (no data entry) must carry a non-empty errors list (GraphQL spec 7.1)");',
295281
+ "});"
295282
+ );
295275
295283
  lines.push(
295276
295284
  `pm.test(${JSON.stringify(`[${label}] GraphQL errors are well-formed and not a total failure`)}, function () {`,
295277
295285
  " var errors = gqlBody.errors;",
295278
295286
  " if (errors === undefined || errors === null) return;",
295279
295287
  ` pm.expect(errors, "GraphQL 'errors' must be an array when present").to.be.an("array");`,
295280
- ' errors.forEach(function (err) { pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message"); });',
295288
+ " errors.forEach(function (err, errIndex) {",
295289
+ ' pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message");',
295290
+ ' if (typeof err.message !== "string") pm.expect.fail("errors[" + errIndex + "].message must be a string (GraphQL spec 7.1.2)");',
295291
+ " if (err.locations !== undefined && err.locations !== null) {",
295292
+ ' if (!Array.isArray(err.locations)) { pm.expect.fail("errors[" + errIndex + "].locations must be a list (GraphQL spec 7.1.2)"); return; }',
295293
+ " err.locations.forEach(function (loc, locIndex) {",
295294
+ ' var locLabel = "errors[" + errIndex + "].locations[" + locIndex + "]";',
295295
+ ' if (!loc || typeof loc !== "object" || Array.isArray(loc)) { pm.expect.fail(locLabel + " must be a map with line and column (GraphQL spec 7.1.2)"); return; }',
295296
+ ' if (!Number.isInteger(loc.line) || loc.line < 1 || !Number.isInteger(loc.column) || loc.column < 1) pm.expect.fail(locLabel + " line and column must be positive integers starting from 1 (GraphQL spec 7.1.2)");',
295297
+ " });",
295298
+ " }",
295299
+ " if (err.path !== undefined && err.path !== null) {",
295300
+ ' if (!Array.isArray(err.path)) { pm.expect.fail("errors[" + errIndex + "].path must be a list of path segments (GraphQL spec 7.1.2)"); return; }',
295301
+ ' err.path.forEach(function (segment, segIndex) { if (typeof segment !== "string" && !Number.isInteger(segment)) pm.expect.fail("errors[" + errIndex + "].path[" + segIndex + "] must be a string field name or an integer list index (GraphQL spec 7.1.2)"); });',
295302
+ " }",
295303
+ ' if (err.extensions !== undefined && err.extensions !== null && (typeof err.extensions !== "object" || Array.isArray(err.extensions))) pm.expect.fail("errors[" + errIndex + "].extensions must be a map (GraphQL spec 7.1.2)");',
295304
+ " });",
295281
295305
  " var hasData = gqlBody.data !== undefined && gqlBody.data !== null;",
295282
295306
  ' if (!hasData && errors.length > 0) { pm.expect.fail("GraphQL request returned errors and no data: " + JSON.stringify(errors)); }',
295283
295307
  "});"
@@ -296112,6 +296136,71 @@ function createGrpcScript(spec) {
296112
296136
  ' else if (!/^[\\x20-\\x7e]*$/.test(value)) pm.expect.fail(label + " " + key + " value must be printable ASCII (gRPC ASCII-Value)");',
296113
296137
  " });",
296114
296138
  "}",
296139
+ // grpc-status-details-bin carries a base64-encoded google.rpc.Status protobuf
296140
+ // (the gRPC richer-error model). The sandbox has no protobuf runtime, so the
296141
+ // script walks the protobuf wire format directly: base64 -> bytes -> tagged
296142
+ // fields (varint / 64-bit / length-delimited / 32-bit). Groups (wire types
296143
+ // 3/4) are proto2-deprecated and never emitted by google.rpc.Status.
296144
+ "function grpcB64Bytes(value) {",
296145
+ ' var clean = String(value).replace(/\\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");',
296146
+ " if (clean.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(clean)) return null;",
296147
+ ' var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";',
296148
+ ' var unpadded = clean.replace(/=+$/, ""); var bytes = []; var buffer = 0; var bits = 0;',
296149
+ " for (var i = 0; i < unpadded.length; i++) {",
296150
+ " var idx = alphabet.indexOf(unpadded.charAt(i));",
296151
+ " if (idx === -1) return null;",
296152
+ " buffer = (buffer << 6) | idx; bits += 6;",
296153
+ " if (bits >= 8) { bits -= 8; bytes.push((buffer >> bits) & 0xff); }",
296154
+ " }",
296155
+ " return bytes;",
296156
+ "}",
296157
+ "function grpcPbVarint(bytes, pos) {",
296158
+ " var value = 0; var shift = 0;",
296159
+ " while (pos < bytes.length) {",
296160
+ " var b = bytes[pos]; pos += 1;",
296161
+ " value += (b & 0x7f) * Math.pow(2, shift); shift += 7;",
296162
+ " if ((b & 0x80) === 0) return value > 9007199254740991 ? null : { value: value, pos: pos };",
296163
+ " if (shift > 63) return null;",
296164
+ " }",
296165
+ " return null;",
296166
+ "}",
296167
+ "function grpcPbFields(bytes) {",
296168
+ " var pos = 0; var fields = [];",
296169
+ " while (pos < bytes.length) {",
296170
+ " var tag = grpcPbVarint(bytes, pos); if (!tag) return null;",
296171
+ " pos = tag.pos;",
296172
+ " var fieldNumber = Math.floor(tag.value / 8); var wire = tag.value % 8;",
296173
+ " if (fieldNumber < 1) return null;",
296174
+ " if (wire === 0) { var varint = grpcPbVarint(bytes, pos); if (!varint) return null; fields.push({ field: fieldNumber, wire: 0, value: varint.value }); pos = varint.pos; }",
296175
+ " else if (wire === 1) { if (pos + 8 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 1 }); pos += 8; }",
296176
+ " else if (wire === 2) { var len = grpcPbVarint(bytes, pos); if (!len) return null; pos = len.pos; if (pos + len.value > bytes.length) return null; fields.push({ field: fieldNumber, wire: 2, bytes: bytes.slice(pos, pos + len.value) }); pos += len.value; }",
296177
+ " else if (wire === 5) { if (pos + 4 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 5 }); pos += 4; }",
296178
+ " else return null;",
296179
+ " }",
296180
+ " return fields;",
296181
+ "}",
296182
+ "function grpcCheckStatusDetailsBin(entry) {",
296183
+ ' if (!entry || entry.value === undefined || entry.value === null || String(entry.value) === "") return;',
296184
+ " var detailBytes = grpcB64Bytes(entry.value);",
296185
+ ' if (!detailBytes) { pm.expect.fail("grpc-status-details-bin trailer must carry base64-encoded bytes but was " + JSON.stringify(String(entry.value))); return; }',
296186
+ " var statusFields = grpcPbFields(detailBytes);",
296187
+ ' if (!statusFields) { pm.expect.fail("grpc-status-details-bin does not decode as a protobuf wire message (google.rpc.Status)"); return; }',
296188
+ " statusFields.forEach(function (statusField) {",
296189
+ " if (statusField.field === 1) {",
296190
+ ' if (statusField.wire !== 0) { pm.expect.fail("google.rpc.Status.code (field 1) in grpc-status-details-bin must be varint-encoded"); return; }',
296191
+ ' if (statusField.value < 0 || statusField.value > 16) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin must be a canonical gRPC code 0-16 but was " + statusField.value);',
296192
+ ' else if (statusField.value !== pm.response.code) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin (" + statusField.value + ") disagrees with the reported status code (" + pm.response.code + ")");',
296193
+ " } else if (statusField.field === 2) {",
296194
+ ' if (statusField.wire !== 2) pm.expect.fail("google.rpc.Status.message (field 2) in grpc-status-details-bin must be length-delimited");',
296195
+ " } else if (statusField.field === 3) {",
296196
+ ' if (statusField.wire !== 2) { pm.expect.fail("google.rpc.Status.details (field 3) in grpc-status-details-bin must be length-delimited google.protobuf.Any messages"); return; }',
296197
+ " var anyFields = grpcPbFields(statusField.bytes);",
296198
+ ' if (!anyFields) { pm.expect.fail("google.rpc.Status.details entry in grpc-status-details-bin is not a decodable google.protobuf.Any message"); return; }',
296199
+ " var hasTypeUrl = anyFields.some(function (anyField) { return anyField.field === 1 && anyField.wire === 2 && anyField.bytes.length > 0; });",
296200
+ ' if (!hasTypeUrl) pm.expect.fail("google.protobuf.Any in grpc-status-details-bin details must carry a non-empty type_url (field 1)");',
296201
+ " }",
296202
+ " });",
296203
+ "}",
296115
296204
  `pm.test('gRPC status is OK for ' + grpcSpec.methodPath, function () {`,
296116
296205
  ' pm.expect(pm.response.code, "gRPC call for " + grpcSpec.methodPath + " returned " + grpcStatusName() + " (" + pm.response.code + ")").to.eql(0);',
296117
296206
  "});",
@@ -296128,6 +296217,7 @@ function createGrpcScript(spec) {
296128
296217
  ' if (!Number.isInteger(echoedCode) || echoedCode < 0 || echoedCode > 16) pm.expect.fail("grpc-status trailer must be a canonical gRPC code 0-16 but was " + JSON.stringify(echoedStatus.value));',
296129
296218
  ' else if (echoedCode !== pm.response.code) pm.expect.fail("grpc-status trailer (" + echoedCode + ") disagrees with the reported status code (" + pm.response.code + ")");',
296130
296219
  " }",
296220
+ ' grpcCheckStatusDetailsBin(grpcMetaOne(pm.response.trailers, "grpc-status-details-bin"));',
296131
296221
  "});",
296132
296222
  // Terminal-response-message expectation. unary and client-streaming RPCs
296133
296223
  // return exactly one terminal response message; server/bidi streaming may
@@ -301966,6 +302056,7 @@ function createSoapScript(operation, warnings = []) {
301966
302056
  };
301967
302057
  const responseRegex = operation.expectedResponseElement ? elementPresenceRegex(operation.expectedResponseElement) : "";
301968
302058
  const mediaType = operation.soapVersion === "1.2" ? "application/soap+xml" : "text/xml";
302059
+ const envelopeNs = operation.soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
301969
302060
  const faultStatusLine = operation.soapVersion === "1.2" ? ' if (faulted && code !== 500 && code !== 400) pm.expect.fail("SOAP 1.2 Faults ride HTTP 500, or 400 for env:Sender faults (SOAP 1.2 Part 2 HTTP binding); got HTTP " + code);' : ' if (faulted && code !== 500) pm.expect.fail("SOAP 1.1 Faults must ride HTTP 500 (WS-I Basic Profile R1126); got HTTP " + code);';
301970
302061
  const lines = [
301971
302062
  `var soap = JSON.parse(${JSON.stringify(JSON.stringify(meta))});`,
@@ -301988,6 +302079,11 @@ function createSoapScript(operation, warnings = []) {
301988
302079
  ' pm.expect(bodyText, "response body is not a SOAP envelope").to.match(matchTag("Envelope"));',
301989
302080
  "});",
301990
302081
  "",
302082
+ `pm.test('SOAP Envelope namespace matches SOAP ${operation.soapVersion}', function () {`,
302083
+ ' if (!matchTag("Envelope").test(bodyText)) return;',
302084
+ ` pm.expect(bodyText.indexOf(${JSON.stringify(envelopeNs)}) !== -1, "SOAP ${operation.soapVersion} envelopes must declare the ${envelopeNs} namespace").to.equal(true);`,
302085
+ "});",
302086
+ "",
301991
302087
  "pm.test('SOAP Body element is present', function () {",
301992
302088
  ' pm.expect(bodyText, "SOAP envelope has no Body element").to.match(matchTag("Body"));',
301993
302089
  "});",
@@ -302005,7 +302101,33 @@ function createSoapScript(operation, warnings = []) {
302005
302101
  " var code = pm.response.code;",
302006
302102
  faultStatusLine,
302007
302103
  ' if (!faulted && code === 500) pm.expect.fail("HTTP 500 from a SOAP endpoint must carry a SOAP Fault in the body");',
302008
- "});"
302104
+ "});",
302105
+ // Fault well-formedness (diagnostic companion to the Fault-absence test: a
302106
+ // faulted run already fails, this pinpoints a malformed Fault). SOAP 1.1
302107
+ // section 4.4 requires faultcode + faultstring children; SOAP 1.2 Part 1
302108
+ // section 5.4 requires Code/Value + Reason/Text and pins the top-level
302109
+ // Value QName to the five defined fault codes.
302110
+ ...operation.soapVersion === "1.2" ? [
302111
+ "",
302112
+ "pm.test('SOAP Fault is well-formed for SOAP 1.2', function () {",
302113
+ ' if (!matchTag("Fault").test(bodyText)) return;',
302114
+ ' if (!matchTag("Code").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault must carry an env:Code child (SOAP 1.2 Part 1 section 5.4)"); return; }',
302115
+ ' if (!matchTag("Value").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault Code must carry an env:Value child (SOAP 1.2 Part 1 section 5.4.1)"); return; }',
302116
+ ' if (!matchTag("Reason").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault must carry an env:Reason child (SOAP 1.2 Part 1 section 5.4)");',
302117
+ ' else if (!matchTag("Text").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault Reason must carry an env:Text child (SOAP 1.2 Part 1 section 5.4.2)");',
302118
+ ' var faultValue = (bodyText.match(/<(?:[A-Za-z_][\\w.-]*:)?Value[^>]*>([\\s\\S]*?)<\\//) || [])[1] || "";',
302119
+ ' var faultLocal = faultValue.trim().split(":").pop();',
302120
+ ' var faultCodes = ["VersionMismatch", "MustUnderstand", "DataEncodingUnknown", "Sender", "Receiver"];',
302121
+ ' if (faultLocal && faultCodes.indexOf(faultLocal) === -1) pm.expect.fail("the SOAP 1.2 Fault Code Value must be one of " + faultCodes.join(", ") + " (SOAP 1.2 Part 1 table 4); got " + faultValue.trim());',
302122
+ "});"
302123
+ ] : [
302124
+ "",
302125
+ "pm.test('SOAP Fault is well-formed for SOAP 1.1', function () {",
302126
+ ' if (!matchTag("Fault").test(bodyText)) return;',
302127
+ ' if (!matchTag("faultcode").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultcode child (SOAP 1.1 section 4.4)");',
302128
+ ' if (!matchTag("faultstring").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultstring child (SOAP 1.1 section 4.4)");',
302129
+ "});"
302130
+ ]
302009
302131
  ];
302010
302132
  if (responseRegex) {
302011
302133
  lines.push(
@@ -302182,7 +302304,8 @@ function wsBindingKeyValues(channel) {
302182
302304
  const value = asRecord15(wsBinding.value()) ?? {};
302183
302305
  return {
302184
302306
  headers: bindingKeyValues(value.headers),
302185
- queryParams: bindingKeyValues(value.query)
302307
+ queryParams: bindingKeyValues(value.query),
302308
+ binding: value
302186
302309
  };
302187
302310
  }
302188
302311
  function collectMqttInfo(channel, servers, messagesRaw, documentJson) {
@@ -302256,6 +302379,8 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
302256
302379
  const replySchema = replyByMessageId.get(message.id());
302257
302380
  const ackSchema = xAckSchema ?? replySchema;
302258
302381
  const ackSource = xAckSchema ? "x-ack" : replySchema ? "reply" : void 0;
302382
+ const correlationRaw = asRecord15(rawMessage.correlationId)?.location;
302383
+ const correlationLocation = typeof correlationRaw === "string" ? correlationRaw : void 0;
302259
302384
  const examples = message.examples().all().filter((example) => example.hasPayload());
302260
302385
  const hasExample = examples.length > 0;
302261
302386
  let sample;
@@ -302277,6 +302402,7 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
302277
302402
  payloadSchema,
302278
302403
  ackSchema,
302279
302404
  ackSource,
302405
+ correlationLocation,
302280
302406
  sample,
302281
302407
  hasExample,
302282
302408
  contentKind,
@@ -302289,6 +302415,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
302289
302415
  const servers = resolveServers(channel, document2);
302290
302416
  const messagesRaw = channel.messages().all();
302291
302417
  const transport = detectTransport(channel, servers, messagesRaw, documentJson, warnings);
302418
+ const parameterNames = Object.keys(asRecord15(channel.json().parameters) ?? {}).sort();
302292
302419
  const serverUrl = servers.find((server) => server.url())?.url() || options.endpointUrl?.trim() || "";
302293
302420
  if (!serverUrl) {
302294
302421
  warnings.push(`ASYNCAPI_NO_SERVER_URL: channel ${channel.id()} has no resolvable server url; the generated request url is derived from the channel address only and must be completed before use`);
@@ -302308,6 +302435,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
302308
302435
  headers: [],
302309
302436
  queryParams: [],
302310
302437
  mqtt: collectMqttInfo(channel, servers, messagesRaw, documentJson),
302438
+ parameterNames,
302311
302439
  messages,
302312
302440
  warnings
302313
302441
  };
@@ -302322,11 +302450,12 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
302322
302450
  queryParams: [],
302323
302451
  socketioNamespace: address.startsWith("/") ? address : `/${address}`,
302324
302452
  socketioPath: DEFAULT_SOCKETIO_PATH,
302453
+ parameterNames,
302325
302454
  messages,
302326
302455
  warnings
302327
302456
  };
302328
302457
  }
302329
- const { headers, queryParams } = wsBindingKeyValues(channel);
302458
+ const { headers, queryParams, binding } = wsBindingKeyValues(channel);
302330
302459
  return {
302331
302460
  id: channel.id(),
302332
302461
  address,
@@ -302334,6 +302463,8 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
302334
302463
  url: joinUrl(serverUrl, address),
302335
302464
  headers,
302336
302465
  queryParams,
302466
+ parameterNames,
302467
+ wsBinding: binding,
302337
302468
  messages,
302338
302469
  warnings
302339
302470
  };
@@ -302771,14 +302902,68 @@ function collectMessageNodeIds(node, ids, path8) {
302771
302902
  if (record) collectMessageNodeIds(record, ids, `${path8}/${i}`);
302772
302903
  });
302773
302904
  }
302905
+ function validateChannelParameters(channel, warnings) {
302906
+ const declared = new Set(channel.parameterNames ?? []);
302907
+ const used = /* @__PURE__ */ new Set();
302908
+ for (const match of channel.address.matchAll(/\{([^{}]*)\}/g)) {
302909
+ const name = match[1] ?? "";
302910
+ if (!name) {
302911
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_INVALID: channel ${channel.id} address ${channel.address} contains an empty {} parameter expression`);
302912
+ continue;
302913
+ }
302914
+ used.add(name);
302915
+ if (!declared.has(name)) {
302916
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNDECLARED: channel ${channel.id} address parameter {${name}} has no entry in the channel parameters object; AsyncAPI requires every address parameter to be declared`);
302917
+ }
302918
+ }
302919
+ for (const name of declared) {
302920
+ if (!used.has(name)) {
302921
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNUSED: channel ${channel.id} declares parameter ${name} that never appears in the channel address ${channel.address}`);
302922
+ }
302923
+ }
302924
+ }
302925
+ var CORRELATION_LOCATION_GRAMMAR = /^\$message\.(header|payload)#(\/(?:[^~/]|~[01])*)+$/;
302926
+ function validateCorrelationLocation(channelId, message, warnings) {
302927
+ if (message.correlationLocation === void 0) return;
302928
+ if (!CORRELATION_LOCATION_GRAMMAR.test(message.correlationLocation)) {
302929
+ warnings.push(
302930
+ `ASYNCAPI_CORRELATION_LOCATION_INVALID: message ${message.id} on channel ${channelId} correlationId location ${JSON.stringify(message.correlationLocation)} is not a valid AsyncAPI runtime expression ($message.header#/<pointer> or $message.payload#/<pointer>)`
302931
+ );
302932
+ }
302933
+ }
302934
+ var SOCKETIO_RESERVED_EVENTS = /* @__PURE__ */ new Set(["connect", "connect_error", "disconnect", "disconnecting", "newListener", "removeListener"]);
302935
+ function validateWsBinding(channel, warnings) {
302936
+ const binding = channel.wsBinding;
302937
+ if (!binding) return;
302938
+ const method = binding.method;
302939
+ if (method !== void 0 && (typeof method !== "string" || method !== "GET" && method !== "POST")) {
302940
+ warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding method must be "GET" or "POST" but was ${JSON.stringify(method)}`);
302941
+ }
302942
+ for (const key of ["query", "headers"]) {
302943
+ const schema = binding[key];
302944
+ if (schema === void 0) continue;
302945
+ const record = asRecord16(schema);
302946
+ if (!record || record.type !== void 0 && record.type !== "object") {
302947
+ warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding ${key} must be a Schema Object of type object`);
302948
+ }
302949
+ }
302950
+ }
302774
302951
  function instrumentAsyncApiCollection(collection, index) {
302775
302952
  const warnings = [
302776
302953
  ...index.warnings,
302777
302954
  ...index.channels.flatMap((channel) => channel.warnings)
302778
302955
  ];
302779
302956
  for (const channel of index.channels) {
302957
+ validateChannelParameters(channel, warnings);
302780
302958
  if (channel.transport === "mqtt") validateMqttChannel(channel, warnings);
302959
+ if (channel.transport === "ws-raw") validateWsBinding(channel, warnings);
302781
302960
  for (const message of channel.messages) {
302961
+ if (channel.transport === "socketio" && SOCKETIO_RESERVED_EVENTS.has(message.eventName)) {
302962
+ warnings.push(
302963
+ `ASYNCAPI_SOCKETIO_RESERVED_EVENT: message ${message.id} on channel ${channel.id} uses the reserved Socket.IO event name "${message.eventName}"; reserved lifecycle events cannot be emitted or received as application events`
302964
+ );
302965
+ }
302966
+ validateCorrelationLocation(channel.id, message, warnings);
302782
302967
  validateMessage(index, channel.id, message, warnings);
302783
302968
  }
302784
302969
  }
@@ -302956,6 +303141,7 @@ function toolDescriptor(tool, warnings) {
302956
303141
  description: typeof tool.description === "string" ? tool.description : void 0,
302957
303142
  inputSchema,
302958
303143
  outputSchema: asRecord17(tool.outputSchema) ?? void 0,
303144
+ annotations: asRecord17(tool.annotations) ?? void 0,
302959
303145
  sampleArguments: inputSchema ? sampleFromSchema2(inputSchema, 0) : {},
302960
303146
  warnings: toolWarnings
302961
303147
  };
@@ -303156,10 +303342,41 @@ function validateServer(server, warnings) {
303156
303342
  }
303157
303343
  }
303158
303344
  }
303345
+ var TOOL_ANNOTATION_BOOLEAN_HINTS = ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"];
303346
+ function validateToolAnnotations(tool, warnings) {
303347
+ if (!tool.annotations) return;
303348
+ for (const hint of TOOL_ANNOTATION_BOOLEAN_HINTS) {
303349
+ const value = tool.annotations[hint];
303350
+ if (value !== void 0 && typeof value !== "boolean") {
303351
+ warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.${hint} must be a boolean (MCP ToolAnnotations); got ${JSON.stringify(value)}`);
303352
+ }
303353
+ }
303354
+ if (tool.annotations.title !== void 0 && typeof tool.annotations.title !== "string") {
303355
+ warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.title must be a string (MCP ToolAnnotations); got ${JSON.stringify(tool.annotations.title)}`);
303356
+ }
303357
+ }
303358
+ function validateToolOutputSchema(index, tool, warnings) {
303359
+ if (!tool.outputSchema) return;
303360
+ const declaredType = tool.outputSchema.type;
303361
+ if (declaredType !== void 0 && declaredType !== "object") {
303362
+ warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_INVALID: tool ${tool.name} outputSchema type is ${JSON.stringify(declaredType)}; the MCP specification requires tool output schemas to describe the structuredContent object`);
303363
+ }
303364
+ const packed = packSchema(index.documentJson, tool.outputSchema, "3.0", "response");
303365
+ if (packed.unsupported) {
303366
+ const code = isSchemaGraphOverflow(packed) ? "MCP_SCHEMA_NOT_COMPILED" : "MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED";
303367
+ warnings.push(`${code}: tool ${tool.name} outputSchema is not validated (${packed.unsupported})`);
303368
+ return;
303369
+ }
303370
+ if (!compileSchemaValidator(packed.schema)) {
303371
+ warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED: tool ${tool.name} outputSchema could not be compiled to a validator`);
303372
+ }
303373
+ }
303159
303374
  function validateTool(index, tool, warnings) {
303160
303375
  if (!TOOL_NAME_RE.test(tool.name)) {
303161
303376
  warnings.push(`MCP_TOOL_NAME_UNCONVENTIONAL: tool name "${tool.name}" is outside the conventional [A-Za-z0-9_./-]{1,128} identifier set`);
303162
303377
  }
303378
+ validateToolAnnotations(tool, warnings);
303379
+ validateToolOutputSchema(index, tool, warnings);
303163
303380
  if (!tool.inputSchema) return;
303164
303381
  const declaredType = tool.inputSchema.type;
303165
303382
  if (declaredType !== void 0 && declaredType !== "object") {
package/dist/index.cjs CHANGED
@@ -296971,12 +296971,36 @@ function buildOperationScript(operation, index, warnings) {
296971
296971
  ' if (mediaType === "application/json" && gqlBody.data !== undefined && gqlBody.data !== null && pm.response.code !== 200) { pm.expect.fail("a GraphQL response carrying data over application/json must be HTTP 200; got " + pm.response.code); }',
296972
296972
  "});"
296973
296973
  );
296974
+ lines.push(
296975
+ `pm.test(${JSON.stringify(`[${label}] GraphQL response map follows the spec response format`)}, function () {`,
296976
+ ' var extraKeys = Object.keys(gqlBody).filter(function (key) { return key !== "data" && key !== "errors" && key !== "extensions"; });',
296977
+ ' if (extraKeys.length > 0) pm.expect.fail("the GraphQL response map must not contain entries other than data, errors, and extensions (GraphQL spec 7.1): " + extraKeys.join(", "));',
296978
+ ' if (Object.prototype.hasOwnProperty.call(gqlBody, "extensions") && (typeof gqlBody.extensions !== "object" || gqlBody.extensions === null || Array.isArray(gqlBody.extensions))) pm.expect.fail("the GraphQL extensions entry must be a map (GraphQL spec 7.1)");',
296979
+ ' if (!Object.prototype.hasOwnProperty.call(gqlBody, "data") && (!Array.isArray(gqlBody.errors) || gqlBody.errors.length === 0)) pm.expect.fail("a GraphQL request-error result (no data entry) must carry a non-empty errors list (GraphQL spec 7.1)");',
296980
+ "});"
296981
+ );
296974
296982
  lines.push(
296975
296983
  `pm.test(${JSON.stringify(`[${label}] GraphQL errors are well-formed and not a total failure`)}, function () {`,
296976
296984
  " var errors = gqlBody.errors;",
296977
296985
  " if (errors === undefined || errors === null) return;",
296978
296986
  ` pm.expect(errors, "GraphQL 'errors' must be an array when present").to.be.an("array");`,
296979
- ' errors.forEach(function (err) { pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message"); });',
296987
+ " errors.forEach(function (err, errIndex) {",
296988
+ ' pm.expect(err, "each GraphQL error must carry a message").to.be.an("object").that.has.property("message");',
296989
+ ' if (typeof err.message !== "string") pm.expect.fail("errors[" + errIndex + "].message must be a string (GraphQL spec 7.1.2)");',
296990
+ " if (err.locations !== undefined && err.locations !== null) {",
296991
+ ' if (!Array.isArray(err.locations)) { pm.expect.fail("errors[" + errIndex + "].locations must be a list (GraphQL spec 7.1.2)"); return; }',
296992
+ " err.locations.forEach(function (loc, locIndex) {",
296993
+ ' var locLabel = "errors[" + errIndex + "].locations[" + locIndex + "]";',
296994
+ ' if (!loc || typeof loc !== "object" || Array.isArray(loc)) { pm.expect.fail(locLabel + " must be a map with line and column (GraphQL spec 7.1.2)"); return; }',
296995
+ ' if (!Number.isInteger(loc.line) || loc.line < 1 || !Number.isInteger(loc.column) || loc.column < 1) pm.expect.fail(locLabel + " line and column must be positive integers starting from 1 (GraphQL spec 7.1.2)");',
296996
+ " });",
296997
+ " }",
296998
+ " if (err.path !== undefined && err.path !== null) {",
296999
+ ' if (!Array.isArray(err.path)) { pm.expect.fail("errors[" + errIndex + "].path must be a list of path segments (GraphQL spec 7.1.2)"); return; }',
297000
+ ' err.path.forEach(function (segment, segIndex) { if (typeof segment !== "string" && !Number.isInteger(segment)) pm.expect.fail("errors[" + errIndex + "].path[" + segIndex + "] must be a string field name or an integer list index (GraphQL spec 7.1.2)"); });',
297001
+ " }",
297002
+ ' if (err.extensions !== undefined && err.extensions !== null && (typeof err.extensions !== "object" || Array.isArray(err.extensions))) pm.expect.fail("errors[" + errIndex + "].extensions must be a map (GraphQL spec 7.1.2)");',
297003
+ " });",
296980
297004
  " var hasData = gqlBody.data !== undefined && gqlBody.data !== null;",
296981
297005
  ' if (!hasData && errors.length > 0) { pm.expect.fail("GraphQL request returned errors and no data: " + JSON.stringify(errors)); }',
296982
297006
  "});"
@@ -297811,6 +297835,71 @@ function createGrpcScript(spec) {
297811
297835
  ' else if (!/^[\\x20-\\x7e]*$/.test(value)) pm.expect.fail(label + " " + key + " value must be printable ASCII (gRPC ASCII-Value)");',
297812
297836
  " });",
297813
297837
  "}",
297838
+ // grpc-status-details-bin carries a base64-encoded google.rpc.Status protobuf
297839
+ // (the gRPC richer-error model). The sandbox has no protobuf runtime, so the
297840
+ // script walks the protobuf wire format directly: base64 -> bytes -> tagged
297841
+ // fields (varint / 64-bit / length-delimited / 32-bit). Groups (wire types
297842
+ // 3/4) are proto2-deprecated and never emitted by google.rpc.Status.
297843
+ "function grpcB64Bytes(value) {",
297844
+ ' var clean = String(value).replace(/\\s+/g, "").replace(/-/g, "+").replace(/_/g, "/");',
297845
+ " if (clean.length % 4 === 1 || !/^[A-Za-z0-9+/]*={0,2}$/.test(clean)) return null;",
297846
+ ' var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";',
297847
+ ' var unpadded = clean.replace(/=+$/, ""); var bytes = []; var buffer = 0; var bits = 0;',
297848
+ " for (var i = 0; i < unpadded.length; i++) {",
297849
+ " var idx = alphabet.indexOf(unpadded.charAt(i));",
297850
+ " if (idx === -1) return null;",
297851
+ " buffer = (buffer << 6) | idx; bits += 6;",
297852
+ " if (bits >= 8) { bits -= 8; bytes.push((buffer >> bits) & 0xff); }",
297853
+ " }",
297854
+ " return bytes;",
297855
+ "}",
297856
+ "function grpcPbVarint(bytes, pos) {",
297857
+ " var value = 0; var shift = 0;",
297858
+ " while (pos < bytes.length) {",
297859
+ " var b = bytes[pos]; pos += 1;",
297860
+ " value += (b & 0x7f) * Math.pow(2, shift); shift += 7;",
297861
+ " if ((b & 0x80) === 0) return value > 9007199254740991 ? null : { value: value, pos: pos };",
297862
+ " if (shift > 63) return null;",
297863
+ " }",
297864
+ " return null;",
297865
+ "}",
297866
+ "function grpcPbFields(bytes) {",
297867
+ " var pos = 0; var fields = [];",
297868
+ " while (pos < bytes.length) {",
297869
+ " var tag = grpcPbVarint(bytes, pos); if (!tag) return null;",
297870
+ " pos = tag.pos;",
297871
+ " var fieldNumber = Math.floor(tag.value / 8); var wire = tag.value % 8;",
297872
+ " if (fieldNumber < 1) return null;",
297873
+ " if (wire === 0) { var varint = grpcPbVarint(bytes, pos); if (!varint) return null; fields.push({ field: fieldNumber, wire: 0, value: varint.value }); pos = varint.pos; }",
297874
+ " else if (wire === 1) { if (pos + 8 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 1 }); pos += 8; }",
297875
+ " else if (wire === 2) { var len = grpcPbVarint(bytes, pos); if (!len) return null; pos = len.pos; if (pos + len.value > bytes.length) return null; fields.push({ field: fieldNumber, wire: 2, bytes: bytes.slice(pos, pos + len.value) }); pos += len.value; }",
297876
+ " else if (wire === 5) { if (pos + 4 > bytes.length) return null; fields.push({ field: fieldNumber, wire: 5 }); pos += 4; }",
297877
+ " else return null;",
297878
+ " }",
297879
+ " return fields;",
297880
+ "}",
297881
+ "function grpcCheckStatusDetailsBin(entry) {",
297882
+ ' if (!entry || entry.value === undefined || entry.value === null || String(entry.value) === "") return;',
297883
+ " var detailBytes = grpcB64Bytes(entry.value);",
297884
+ ' if (!detailBytes) { pm.expect.fail("grpc-status-details-bin trailer must carry base64-encoded bytes but was " + JSON.stringify(String(entry.value))); return; }',
297885
+ " var statusFields = grpcPbFields(detailBytes);",
297886
+ ' if (!statusFields) { pm.expect.fail("grpc-status-details-bin does not decode as a protobuf wire message (google.rpc.Status)"); return; }',
297887
+ " statusFields.forEach(function (statusField) {",
297888
+ " if (statusField.field === 1) {",
297889
+ ' if (statusField.wire !== 0) { pm.expect.fail("google.rpc.Status.code (field 1) in grpc-status-details-bin must be varint-encoded"); return; }',
297890
+ ' if (statusField.value < 0 || statusField.value > 16) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin must be a canonical gRPC code 0-16 but was " + statusField.value);',
297891
+ ' else if (statusField.value !== pm.response.code) pm.expect.fail("google.rpc.Status.code in grpc-status-details-bin (" + statusField.value + ") disagrees with the reported status code (" + pm.response.code + ")");',
297892
+ " } else if (statusField.field === 2) {",
297893
+ ' if (statusField.wire !== 2) pm.expect.fail("google.rpc.Status.message (field 2) in grpc-status-details-bin must be length-delimited");',
297894
+ " } else if (statusField.field === 3) {",
297895
+ ' if (statusField.wire !== 2) { pm.expect.fail("google.rpc.Status.details (field 3) in grpc-status-details-bin must be length-delimited google.protobuf.Any messages"); return; }',
297896
+ " var anyFields = grpcPbFields(statusField.bytes);",
297897
+ ' if (!anyFields) { pm.expect.fail("google.rpc.Status.details entry in grpc-status-details-bin is not a decodable google.protobuf.Any message"); return; }',
297898
+ " var hasTypeUrl = anyFields.some(function (anyField) { return anyField.field === 1 && anyField.wire === 2 && anyField.bytes.length > 0; });",
297899
+ ' if (!hasTypeUrl) pm.expect.fail("google.protobuf.Any in grpc-status-details-bin details must carry a non-empty type_url (field 1)");',
297900
+ " }",
297901
+ " });",
297902
+ "}",
297814
297903
  `pm.test('gRPC status is OK for ' + grpcSpec.methodPath, function () {`,
297815
297904
  ' pm.expect(pm.response.code, "gRPC call for " + grpcSpec.methodPath + " returned " + grpcStatusName() + " (" + pm.response.code + ")").to.eql(0);',
297816
297905
  "});",
@@ -297827,6 +297916,7 @@ function createGrpcScript(spec) {
297827
297916
  ' if (!Number.isInteger(echoedCode) || echoedCode < 0 || echoedCode > 16) pm.expect.fail("grpc-status trailer must be a canonical gRPC code 0-16 but was " + JSON.stringify(echoedStatus.value));',
297828
297917
  ' else if (echoedCode !== pm.response.code) pm.expect.fail("grpc-status trailer (" + echoedCode + ") disagrees with the reported status code (" + pm.response.code + ")");',
297829
297918
  " }",
297919
+ ' grpcCheckStatusDetailsBin(grpcMetaOne(pm.response.trailers, "grpc-status-details-bin"));',
297830
297920
  "});",
297831
297921
  // Terminal-response-message expectation. unary and client-streaming RPCs
297832
297922
  // return exactly one terminal response message; server/bidi streaming may
@@ -303665,6 +303755,7 @@ function createSoapScript(operation, warnings = []) {
303665
303755
  };
303666
303756
  const responseRegex = operation.expectedResponseElement ? elementPresenceRegex(operation.expectedResponseElement) : "";
303667
303757
  const mediaType = operation.soapVersion === "1.2" ? "application/soap+xml" : "text/xml";
303758
+ const envelopeNs = operation.soapVersion === "1.2" ? "http://www.w3.org/2003/05/soap-envelope" : "http://schemas.xmlsoap.org/soap/envelope/";
303668
303759
  const faultStatusLine = operation.soapVersion === "1.2" ? ' if (faulted && code !== 500 && code !== 400) pm.expect.fail("SOAP 1.2 Faults ride HTTP 500, or 400 for env:Sender faults (SOAP 1.2 Part 2 HTTP binding); got HTTP " + code);' : ' if (faulted && code !== 500) pm.expect.fail("SOAP 1.1 Faults must ride HTTP 500 (WS-I Basic Profile R1126); got HTTP " + code);';
303669
303760
  const lines = [
303670
303761
  `var soap = JSON.parse(${JSON.stringify(JSON.stringify(meta))});`,
@@ -303687,6 +303778,11 @@ function createSoapScript(operation, warnings = []) {
303687
303778
  ' pm.expect(bodyText, "response body is not a SOAP envelope").to.match(matchTag("Envelope"));',
303688
303779
  "});",
303689
303780
  "",
303781
+ `pm.test('SOAP Envelope namespace matches SOAP ${operation.soapVersion}', function () {`,
303782
+ ' if (!matchTag("Envelope").test(bodyText)) return;',
303783
+ ` pm.expect(bodyText.indexOf(${JSON.stringify(envelopeNs)}) !== -1, "SOAP ${operation.soapVersion} envelopes must declare the ${envelopeNs} namespace").to.equal(true);`,
303784
+ "});",
303785
+ "",
303690
303786
  "pm.test('SOAP Body element is present', function () {",
303691
303787
  ' pm.expect(bodyText, "SOAP envelope has no Body element").to.match(matchTag("Body"));',
303692
303788
  "});",
@@ -303704,7 +303800,33 @@ function createSoapScript(operation, warnings = []) {
303704
303800
  " var code = pm.response.code;",
303705
303801
  faultStatusLine,
303706
303802
  ' if (!faulted && code === 500) pm.expect.fail("HTTP 500 from a SOAP endpoint must carry a SOAP Fault in the body");',
303707
- "});"
303803
+ "});",
303804
+ // Fault well-formedness (diagnostic companion to the Fault-absence test: a
303805
+ // faulted run already fails, this pinpoints a malformed Fault). SOAP 1.1
303806
+ // section 4.4 requires faultcode + faultstring children; SOAP 1.2 Part 1
303807
+ // section 5.4 requires Code/Value + Reason/Text and pins the top-level
303808
+ // Value QName to the five defined fault codes.
303809
+ ...operation.soapVersion === "1.2" ? [
303810
+ "",
303811
+ "pm.test('SOAP Fault is well-formed for SOAP 1.2', function () {",
303812
+ ' if (!matchTag("Fault").test(bodyText)) return;',
303813
+ ' if (!matchTag("Code").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault must carry an env:Code child (SOAP 1.2 Part 1 section 5.4)"); return; }',
303814
+ ' if (!matchTag("Value").test(bodyText)) { pm.expect.fail("a SOAP 1.2 Fault Code must carry an env:Value child (SOAP 1.2 Part 1 section 5.4.1)"); return; }',
303815
+ ' if (!matchTag("Reason").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault must carry an env:Reason child (SOAP 1.2 Part 1 section 5.4)");',
303816
+ ' else if (!matchTag("Text").test(bodyText)) pm.expect.fail("a SOAP 1.2 Fault Reason must carry an env:Text child (SOAP 1.2 Part 1 section 5.4.2)");',
303817
+ ' var faultValue = (bodyText.match(/<(?:[A-Za-z_][\\w.-]*:)?Value[^>]*>([\\s\\S]*?)<\\//) || [])[1] || "";',
303818
+ ' var faultLocal = faultValue.trim().split(":").pop();',
303819
+ ' var faultCodes = ["VersionMismatch", "MustUnderstand", "DataEncodingUnknown", "Sender", "Receiver"];',
303820
+ ' if (faultLocal && faultCodes.indexOf(faultLocal) === -1) pm.expect.fail("the SOAP 1.2 Fault Code Value must be one of " + faultCodes.join(", ") + " (SOAP 1.2 Part 1 table 4); got " + faultValue.trim());',
303821
+ "});"
303822
+ ] : [
303823
+ "",
303824
+ "pm.test('SOAP Fault is well-formed for SOAP 1.1', function () {",
303825
+ ' if (!matchTag("Fault").test(bodyText)) return;',
303826
+ ' if (!matchTag("faultcode").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultcode child (SOAP 1.1 section 4.4)");',
303827
+ ' if (!matchTag("faultstring").test(bodyText)) pm.expect.fail("a SOAP 1.1 Fault must carry a faultstring child (SOAP 1.1 section 4.4)");',
303828
+ "});"
303829
+ ]
303708
303830
  ];
303709
303831
  if (responseRegex) {
303710
303832
  lines.push(
@@ -303881,7 +304003,8 @@ function wsBindingKeyValues(channel) {
303881
304003
  const value = asRecord15(wsBinding.value()) ?? {};
303882
304004
  return {
303883
304005
  headers: bindingKeyValues(value.headers),
303884
- queryParams: bindingKeyValues(value.query)
304006
+ queryParams: bindingKeyValues(value.query),
304007
+ binding: value
303885
304008
  };
303886
304009
  }
303887
304010
  function collectMqttInfo(channel, servers, messagesRaw, documentJson) {
@@ -303955,6 +304078,8 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
303955
304078
  const replySchema = replyByMessageId.get(message.id());
303956
304079
  const ackSchema = xAckSchema ?? replySchema;
303957
304080
  const ackSource = xAckSchema ? "x-ack" : replySchema ? "reply" : void 0;
304081
+ const correlationRaw = asRecord15(rawMessage.correlationId)?.location;
304082
+ const correlationLocation = typeof correlationRaw === "string" ? correlationRaw : void 0;
303958
304083
  const examples = message.examples().all().filter((example) => example.hasPayload());
303959
304084
  const hasExample = examples.length > 0;
303960
304085
  let sample;
@@ -303976,6 +304101,7 @@ function messageDescriptor2(message, warnings, channelId, defaultContentType, re
303976
304101
  payloadSchema,
303977
304102
  ackSchema,
303978
304103
  ackSource,
304104
+ correlationLocation,
303979
304105
  sample,
303980
304106
  hasExample,
303981
304107
  contentKind,
@@ -303988,6 +304114,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
303988
304114
  const servers = resolveServers(channel, document2);
303989
304115
  const messagesRaw = channel.messages().all();
303990
304116
  const transport = detectTransport(channel, servers, messagesRaw, documentJson, warnings);
304117
+ const parameterNames = Object.keys(asRecord15(channel.json().parameters) ?? {}).sort();
303991
304118
  const serverUrl = servers.find((server) => server.url())?.url() || options.endpointUrl?.trim() || "";
303992
304119
  if (!serverUrl) {
303993
304120
  warnings.push(`ASYNCAPI_NO_SERVER_URL: channel ${channel.id()} has no resolvable server url; the generated request url is derived from the channel address only and must be completed before use`);
@@ -304007,6 +304134,7 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
304007
304134
  headers: [],
304008
304135
  queryParams: [],
304009
304136
  mqtt: collectMqttInfo(channel, servers, messagesRaw, documentJson),
304137
+ parameterNames,
304010
304138
  messages,
304011
304139
  warnings
304012
304140
  };
@@ -304021,11 +304149,12 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
304021
304149
  queryParams: [],
304022
304150
  socketioNamespace: address.startsWith("/") ? address : `/${address}`,
304023
304151
  socketioPath: DEFAULT_SOCKETIO_PATH,
304152
+ parameterNames,
304024
304153
  messages,
304025
304154
  warnings
304026
304155
  };
304027
304156
  }
304028
- const { headers, queryParams } = wsBindingKeyValues(channel);
304157
+ const { headers, queryParams, binding } = wsBindingKeyValues(channel);
304029
304158
  return {
304030
304159
  id: channel.id(),
304031
304160
  address,
@@ -304033,6 +304162,8 @@ function channelDescriptor(channel, document2, documentJson, defaultContentType,
304033
304162
  url: joinUrl(serverUrl, address),
304034
304163
  headers,
304035
304164
  queryParams,
304165
+ parameterNames,
304166
+ wsBinding: binding,
304036
304167
  messages,
304037
304168
  warnings
304038
304169
  };
@@ -304470,14 +304601,68 @@ function collectMessageNodeIds(node, ids, path10) {
304470
304601
  if (record) collectMessageNodeIds(record, ids, `${path10}/${i}`);
304471
304602
  });
304472
304603
  }
304604
+ function validateChannelParameters(channel, warnings) {
304605
+ const declared = new Set(channel.parameterNames ?? []);
304606
+ const used = /* @__PURE__ */ new Set();
304607
+ for (const match of channel.address.matchAll(/\{([^{}]*)\}/g)) {
304608
+ const name = match[1] ?? "";
304609
+ if (!name) {
304610
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_INVALID: channel ${channel.id} address ${channel.address} contains an empty {} parameter expression`);
304611
+ continue;
304612
+ }
304613
+ used.add(name);
304614
+ if (!declared.has(name)) {
304615
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNDECLARED: channel ${channel.id} address parameter {${name}} has no entry in the channel parameters object; AsyncAPI requires every address parameter to be declared`);
304616
+ }
304617
+ }
304618
+ for (const name of declared) {
304619
+ if (!used.has(name)) {
304620
+ warnings.push(`ASYNCAPI_CHANNEL_PARAMETER_UNUSED: channel ${channel.id} declares parameter ${name} that never appears in the channel address ${channel.address}`);
304621
+ }
304622
+ }
304623
+ }
304624
+ var CORRELATION_LOCATION_GRAMMAR = /^\$message\.(header|payload)#(\/(?:[^~/]|~[01])*)+$/;
304625
+ function validateCorrelationLocation(channelId, message, warnings) {
304626
+ if (message.correlationLocation === void 0) return;
304627
+ if (!CORRELATION_LOCATION_GRAMMAR.test(message.correlationLocation)) {
304628
+ warnings.push(
304629
+ `ASYNCAPI_CORRELATION_LOCATION_INVALID: message ${message.id} on channel ${channelId} correlationId location ${JSON.stringify(message.correlationLocation)} is not a valid AsyncAPI runtime expression ($message.header#/<pointer> or $message.payload#/<pointer>)`
304630
+ );
304631
+ }
304632
+ }
304633
+ var SOCKETIO_RESERVED_EVENTS = /* @__PURE__ */ new Set(["connect", "connect_error", "disconnect", "disconnecting", "newListener", "removeListener"]);
304634
+ function validateWsBinding(channel, warnings) {
304635
+ const binding = channel.wsBinding;
304636
+ if (!binding) return;
304637
+ const method = binding.method;
304638
+ if (method !== void 0 && (typeof method !== "string" || method !== "GET" && method !== "POST")) {
304639
+ warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding method must be "GET" or "POST" but was ${JSON.stringify(method)}`);
304640
+ }
304641
+ for (const key of ["query", "headers"]) {
304642
+ const schema = binding[key];
304643
+ if (schema === void 0) continue;
304644
+ const record = asRecord16(schema);
304645
+ if (!record || record.type !== void 0 && record.type !== "object") {
304646
+ warnings.push(`ASYNCAPI_WS_BINDING_INVALID: channel ${channel.id} ws binding ${key} must be a Schema Object of type object`);
304647
+ }
304648
+ }
304649
+ }
304473
304650
  function instrumentAsyncApiCollection(collection, index) {
304474
304651
  const warnings = [
304475
304652
  ...index.warnings,
304476
304653
  ...index.channels.flatMap((channel) => channel.warnings)
304477
304654
  ];
304478
304655
  for (const channel of index.channels) {
304656
+ validateChannelParameters(channel, warnings);
304479
304657
  if (channel.transport === "mqtt") validateMqttChannel(channel, warnings);
304658
+ if (channel.transport === "ws-raw") validateWsBinding(channel, warnings);
304480
304659
  for (const message of channel.messages) {
304660
+ if (channel.transport === "socketio" && SOCKETIO_RESERVED_EVENTS.has(message.eventName)) {
304661
+ warnings.push(
304662
+ `ASYNCAPI_SOCKETIO_RESERVED_EVENT: message ${message.id} on channel ${channel.id} uses the reserved Socket.IO event name "${message.eventName}"; reserved lifecycle events cannot be emitted or received as application events`
304663
+ );
304664
+ }
304665
+ validateCorrelationLocation(channel.id, message, warnings);
304481
304666
  validateMessage(index, channel.id, message, warnings);
304482
304667
  }
304483
304668
  }
@@ -304655,6 +304840,7 @@ function toolDescriptor(tool, warnings) {
304655
304840
  description: typeof tool.description === "string" ? tool.description : void 0,
304656
304841
  inputSchema,
304657
304842
  outputSchema: asRecord17(tool.outputSchema) ?? void 0,
304843
+ annotations: asRecord17(tool.annotations) ?? void 0,
304658
304844
  sampleArguments: inputSchema ? sampleFromSchema2(inputSchema, 0) : {},
304659
304845
  warnings: toolWarnings
304660
304846
  };
@@ -304855,10 +305041,41 @@ function validateServer(server, warnings) {
304855
305041
  }
304856
305042
  }
304857
305043
  }
305044
+ var TOOL_ANNOTATION_BOOLEAN_HINTS = ["readOnlyHint", "destructiveHint", "idempotentHint", "openWorldHint"];
305045
+ function validateToolAnnotations(tool, warnings) {
305046
+ if (!tool.annotations) return;
305047
+ for (const hint of TOOL_ANNOTATION_BOOLEAN_HINTS) {
305048
+ const value = tool.annotations[hint];
305049
+ if (value !== void 0 && typeof value !== "boolean") {
305050
+ warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.${hint} must be a boolean (MCP ToolAnnotations); got ${JSON.stringify(value)}`);
305051
+ }
305052
+ }
305053
+ if (tool.annotations.title !== void 0 && typeof tool.annotations.title !== "string") {
305054
+ warnings.push(`MCP_TOOL_ANNOTATION_INVALID: tool ${tool.name} annotations.title must be a string (MCP ToolAnnotations); got ${JSON.stringify(tool.annotations.title)}`);
305055
+ }
305056
+ }
305057
+ function validateToolOutputSchema(index, tool, warnings) {
305058
+ if (!tool.outputSchema) return;
305059
+ const declaredType = tool.outputSchema.type;
305060
+ if (declaredType !== void 0 && declaredType !== "object") {
305061
+ warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_INVALID: tool ${tool.name} outputSchema type is ${JSON.stringify(declaredType)}; the MCP specification requires tool output schemas to describe the structuredContent object`);
305062
+ }
305063
+ const packed = packSchema(index.documentJson, tool.outputSchema, "3.0", "response");
305064
+ if (packed.unsupported) {
305065
+ const code = isSchemaGraphOverflow(packed) ? "MCP_SCHEMA_NOT_COMPILED" : "MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED";
305066
+ warnings.push(`${code}: tool ${tool.name} outputSchema is not validated (${packed.unsupported})`);
305067
+ return;
305068
+ }
305069
+ if (!compileSchemaValidator(packed.schema)) {
305070
+ warnings.push(`MCP_TOOL_OUTPUT_SCHEMA_NOT_VALIDATED: tool ${tool.name} outputSchema could not be compiled to a validator`);
305071
+ }
305072
+ }
304858
305073
  function validateTool(index, tool, warnings) {
304859
305074
  if (!TOOL_NAME_RE.test(tool.name)) {
304860
305075
  warnings.push(`MCP_TOOL_NAME_UNCONVENTIONAL: tool name "${tool.name}" is outside the conventional [A-Za-z0-9_./-]{1,128} identifier set`);
304861
305076
  }
305077
+ validateToolAnnotations(tool, warnings);
305078
+ validateToolOutputSchema(index, tool, warnings);
304862
305079
  if (!tool.inputSchema) return;
304863
305080
  const declaredType = tool.inputSchema.type;
304864
305081
  if (declaredType !== void 0 && declaredType !== "object") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@postman-cse/onboarding-bootstrap",
3
- "version": "2.5.0",
3
+ "version": "2.6.0",
4
4
  "description": "Bootstrap Postman workspaces, specs, and collections from OpenAPI.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",