@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 +221 -4
- package/dist/cli.cjs +221 -4
- package/dist/index.cjs +221 -4
- package/package.json +1 -1
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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") {
|