1use serde_json::{Value, json};
10
11pub const PROTOCOL_VERSION: &str = "2024-11-05";
17
18pub const JSONRPC_VERSION: &str = "2.0";
20
21#[must_use]
27pub fn valid_initialize_request(request_id: u64) -> Value {
28 json!({
29 "jsonrpc": JSONRPC_VERSION,
30 "id": request_id,
31 "method": "initialize",
32 "params": {
33 "protocolVersion": PROTOCOL_VERSION,
34 "capabilities": {
35 "roots": { "listChanged": true }
36 },
37 "clientInfo": {
38 "name": "test-client",
39 "version": "1.0.0"
40 }
41 }
42 })
43}
44
45#[must_use]
47pub fn valid_initialized_notification() -> Value {
48 json!({
49 "jsonrpc": JSONRPC_VERSION,
50 "method": "notifications/initialized"
51 })
52}
53
54#[must_use]
56pub fn valid_tools_list_request(request_id: u64) -> Value {
57 json!({
58 "jsonrpc": JSONRPC_VERSION,
59 "id": request_id,
60 "method": "tools/list"
61 })
62}
63
64#[must_use]
66pub fn valid_tools_call_request(request_id: u64, name: &str, arguments: Value) -> Value {
67 json!({
68 "jsonrpc": JSONRPC_VERSION,
69 "id": request_id,
70 "method": "tools/call",
71 "params": {
72 "name": name,
73 "arguments": arguments
74 }
75 })
76}
77
78#[must_use]
80pub fn valid_resources_list_request(request_id: u64) -> Value {
81 json!({
82 "jsonrpc": JSONRPC_VERSION,
83 "id": request_id,
84 "method": "resources/list"
85 })
86}
87
88#[must_use]
90pub fn valid_resources_read_request(request_id: u64, uri: &str) -> Value {
91 json!({
92 "jsonrpc": JSONRPC_VERSION,
93 "id": request_id,
94 "method": "resources/read",
95 "params": {
96 "uri": uri
97 }
98 })
99}
100
101#[must_use]
103pub fn valid_prompts_list_request(request_id: u64) -> Value {
104 json!({
105 "jsonrpc": JSONRPC_VERSION,
106 "id": request_id,
107 "method": "prompts/list"
108 })
109}
110
111#[must_use]
113pub fn valid_prompts_get_request(request_id: u64, name: &str, arguments: Option<Value>) -> Value {
114 let mut params = json!({ "name": name });
115 if let Some(args) = arguments {
116 params["arguments"] = args;
117 }
118 json!({
119 "jsonrpc": JSONRPC_VERSION,
120 "id": request_id,
121 "method": "prompts/get",
122 "params": params
123 })
124}
125
126#[must_use]
128pub fn valid_ping_request(request_id: u64) -> Value {
129 json!({
130 "jsonrpc": JSONRPC_VERSION,
131 "id": request_id,
132 "method": "ping"
133 })
134}
135
136#[must_use]
142pub fn valid_success_response(request_id: u64, result: Value) -> Value {
143 json!({
144 "jsonrpc": JSONRPC_VERSION,
145 "id": request_id,
146 "result": result
147 })
148}
149
150#[must_use]
152pub fn valid_initialize_response(request_id: u64) -> Value {
153 valid_success_response(
154 request_id,
155 json!({
156 "protocolVersion": PROTOCOL_VERSION,
157 "serverInfo": {
158 "name": "test-server",
159 "version": "1.0.0"
160 },
161 "capabilities": {
162 "tools": { "listChanged": false },
163 "resources": { "subscribe": false, "listChanged": false },
164 "prompts": { "listChanged": false }
165 }
166 }),
167 )
168}
169
170#[must_use]
172pub fn valid_tools_list_response(request_id: u64, tools: Vec<Value>) -> Value {
173 valid_success_response(request_id, json!({ "tools": tools }))
174}
175
176#[must_use]
178pub fn valid_tools_call_response(request_id: u64, content: Vec<Value>, is_error: bool) -> Value {
179 valid_success_response(
180 request_id,
181 json!({
182 "content": content,
183 "isError": is_error
184 }),
185 )
186}
187
188#[must_use]
190pub fn valid_resources_list_response(request_id: u64, resources: Vec<Value>) -> Value {
191 valid_success_response(request_id, json!({ "resources": resources }))
192}
193
194#[must_use]
196pub fn valid_resources_read_response(request_id: u64, contents: Vec<Value>) -> Value {
197 valid_success_response(request_id, json!({ "contents": contents }))
198}
199
200#[must_use]
202pub fn valid_prompts_list_response(request_id: u64, prompts: Vec<Value>) -> Value {
203 valid_success_response(request_id, json!({ "prompts": prompts }))
204}
205
206#[must_use]
208pub fn valid_prompts_get_response(
209 request_id: u64,
210 description: Option<&str>,
211 messages: Vec<Value>,
212) -> Value {
213 let mut result = json!({ "messages": messages });
214 if let Some(desc) = description {
215 result["description"] = json!(desc);
216 }
217 valid_success_response(request_id, result)
218}
219
220#[must_use]
222pub fn valid_pong_response(request_id: u64) -> Value {
223 valid_success_response(request_id, json!({}))
224}
225
226#[must_use]
232pub fn error_response(request_id: u64, code: i32, message: &str, data: Option<Value>) -> Value {
233 let mut error = json!({
234 "code": code,
235 "message": message
236 });
237 if let Some(d) = data {
238 error["data"] = d;
239 }
240 json!({
241 "jsonrpc": JSONRPC_VERSION,
242 "id": request_id,
243 "error": error
244 })
245}
246
247#[must_use]
249pub fn parse_error_response(request_id: u64) -> Value {
250 error_response(request_id, -32700, "Parse error", None)
251}
252
253#[must_use]
255pub fn invalid_request_error_response(request_id: u64) -> Value {
256 error_response(request_id, -32600, "Invalid Request", None)
257}
258
259#[must_use]
261pub fn method_not_found_error_response(request_id: u64, method: &str) -> Value {
262 error_response(
263 request_id,
264 -32601,
265 &format!("Method not found: {method}"),
266 None,
267 )
268}
269
270#[must_use]
272pub fn invalid_params_error_response(request_id: u64, details: &str) -> Value {
273 error_response(
274 request_id,
275 -32602,
276 "Invalid params",
277 Some(json!({ "details": details })),
278 )
279}
280
281#[must_use]
283pub fn internal_error_response(request_id: u64, details: Option<&str>) -> Value {
284 error_response(
285 request_id,
286 -32603,
287 "Internal error",
288 details.map(|d| json!({ "details": d })),
289 )
290}
291
292#[must_use]
294pub fn resource_not_found_error_response(request_id: u64, uri: &str) -> Value {
295 error_response(
296 request_id,
297 -32002,
298 &format!("Resource not found: {uri}"),
299 None,
300 )
301}
302
303#[must_use]
305pub fn tool_not_found_error_response(request_id: u64, name: &str) -> Value {
306 error_response(request_id, -32002, &format!("Tool not found: {name}"), None)
307}
308
309#[must_use]
311pub fn prompt_not_found_error_response(request_id: u64, name: &str) -> Value {
312 error_response(
313 request_id,
314 -32002,
315 &format!("Prompt not found: {name}"),
316 None,
317 )
318}
319
320pub mod invalid {
326 use serde_json::{Value, json};
327
328 #[must_use]
330 pub fn missing_jsonrpc() -> Value {
331 json!({
332 "id": 1,
333 "method": "test"
334 })
335 }
336
337 #[must_use]
339 pub fn wrong_jsonrpc_version() -> Value {
340 json!({
341 "jsonrpc": "1.0",
342 "id": 1,
343 "method": "test"
344 })
345 }
346
347 #[must_use]
349 pub fn missing_method() -> Value {
350 json!({
351 "jsonrpc": "2.0",
352 "id": 1
353 })
354 }
355
356 #[must_use]
358 pub fn invalid_method_type() -> Value {
359 json!({
360 "jsonrpc": "2.0",
361 "id": 1,
362 "method": 123
363 })
364 }
365
366 #[must_use]
368 pub fn invalid_id_type() -> Value {
369 json!({
370 "jsonrpc": "2.0",
371 "id": [1, 2, 3],
372 "method": "test"
373 })
374 }
375
376 #[must_use]
378 pub fn both_result_and_error() -> Value {
379 json!({
380 "jsonrpc": "2.0",
381 "id": 1,
382 "result": {},
383 "error": { "code": -32600, "message": "Error" }
384 })
385 }
386
387 #[must_use]
389 pub fn malformed_json_string() -> &'static str {
390 r#"{"jsonrpc": "2.0", "id": 1, "method": "test""#
391 }
392
393 #[must_use]
395 pub fn empty_object() -> Value {
396 json!({})
397 }
398
399 #[must_use]
401 pub fn null_value() -> Value {
402 Value::Null
403 }
404
405 #[must_use]
407 pub fn array_instead_of_object() -> Value {
408 json!([1, 2, 3])
409 }
410}
411
412#[must_use]
418pub fn notification(method: &str, params: Option<Value>) -> Value {
419 let mut msg = json!({
420 "jsonrpc": JSONRPC_VERSION,
421 "method": method
422 });
423 if let Some(p) = params {
424 msg["params"] = p;
425 }
426 msg
427}
428
429#[must_use]
431pub fn progress_notification(token: &str, progress: f64, message: Option<&str>) -> Value {
432 notification(
433 "notifications/progress",
434 Some(json!({
435 "progressToken": token,
436 "progress": progress,
437 "message": message
438 })),
439 )
440}
441
442#[must_use]
444pub fn log_notification(level: &str, data: Value) -> Value {
445 notification(
446 "notifications/log",
447 Some(json!({
448 "level": level,
449 "data": data
450 })),
451 )
452}
453
454#[must_use]
456pub fn cancelled_notification(request_id: u64, reason: Option<&str>) -> Value {
457 notification(
458 "notifications/cancelled",
459 Some(json!({
460 "requestId": request_id,
461 "reason": reason
462 })),
463 )
464}
465
466#[must_use]
472pub fn large_string_payload(size_kb: usize) -> String {
473 let base = "abcdefghijklmnopqrstuvwxyz0123456789";
474 let iterations = (size_kb * 1024) / base.len() + 1;
475 base.repeat(iterations)
476}
477
478#[must_use]
480pub fn large_json_payload(num_fields: usize) -> Value {
481 let mut obj = serde_json::Map::new();
482 for i in 0..num_fields {
483 obj.insert(
484 format!("field_{i:06}"),
485 json!({
486 "index": i,
487 "value": format!("value_{i}"),
488 "nested": {
489 "a": i * 2,
490 "b": format!("nested_{i}")
491 }
492 }),
493 );
494 }
495 Value::Object(obj)
496}
497
498#[must_use]
500pub fn large_array_payload(num_items: usize) -> Value {
501 let items: Vec<Value> = (0..num_items)
502 .map(|i| {
503 json!({
504 "id": i,
505 "name": format!("item_{i}"),
506 "data": large_string_payload(1) })
508 })
509 .collect();
510 Value::Array(items)
511}
512
513#[must_use]
515pub fn large_tools_call_request(request_id: u64, size_kb: usize) -> Value {
516 valid_tools_call_request(
517 request_id,
518 "large_input_tool",
519 json!({
520 "data": large_string_payload(size_kb)
521 }),
522 )
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528
529 #[test]
530 fn test_valid_initialize_request() {
531 let req = valid_initialize_request(1);
532 assert_eq!(req["jsonrpc"], "2.0");
533 assert_eq!(req["id"], 1);
534 assert_eq!(req["method"], "initialize");
535 assert!(req["params"]["protocolVersion"].is_string());
536 }
537
538 #[test]
539 fn test_valid_tools_call_request() {
540 let req = valid_tools_call_request(2, "greeting", json!({"name": "World"}));
541 assert_eq!(req["method"], "tools/call");
542 assert_eq!(req["params"]["name"], "greeting");
543 assert_eq!(req["params"]["arguments"]["name"], "World");
544 }
545
546 #[test]
547 fn test_valid_success_response() {
548 let resp = valid_success_response(1, json!({"value": 42}));
549 assert_eq!(resp["id"], 1);
550 assert!(resp.get("result").is_some());
551 assert!(resp.get("error").is_none());
552 }
553
554 #[test]
555 fn test_error_response() {
556 let resp = error_response(1, -32600, "Invalid Request", None);
557 assert_eq!(resp["id"], 1);
558 assert_eq!(resp["error"]["code"], -32600);
559 assert!(resp.get("result").is_none());
560 }
561
562 #[test]
563 fn test_parse_error_response() {
564 let resp = parse_error_response(1);
565 assert_eq!(resp["error"]["code"], -32700);
566 }
567
568 #[test]
569 fn test_method_not_found_error() {
570 let resp = method_not_found_error_response(1, "unknown_method");
571 assert_eq!(resp["error"]["code"], -32601);
572 assert!(
573 resp["error"]["message"]
574 .as_str()
575 .unwrap()
576 .contains("unknown_method")
577 );
578 }
579
580 #[test]
581 fn test_invalid_messages() {
582 let missing = invalid::missing_jsonrpc();
583 assert!(missing.get("jsonrpc").is_none());
584
585 let wrong_version = invalid::wrong_jsonrpc_version();
586 assert_eq!(wrong_version["jsonrpc"], "1.0");
587
588 let missing_method = invalid::missing_method();
589 assert!(missing_method.get("method").is_none());
590 }
591
592 #[test]
593 fn test_notification() {
594 let notif = notification("test/notification", Some(json!({"key": "value"})));
595 assert!(notif.get("id").is_none());
596 assert_eq!(notif["method"], "test/notification");
597 }
598
599 #[test]
600 fn test_progress_notification() {
601 let notif = progress_notification("token123", 0.5, Some("Halfway done"));
602 assert_eq!(notif["params"]["progressToken"], "token123");
603 assert_eq!(notif["params"]["progress"], 0.5);
604 }
605
606 #[test]
607 fn test_large_string_payload() {
608 let payload = large_string_payload(10);
609 assert!(payload.len() >= 10 * 1024);
611 }
612
613 #[test]
614 fn test_large_json_payload() {
615 let payload = large_json_payload(100);
616 let obj = payload.as_object().unwrap();
617 assert_eq!(obj.len(), 100);
618 }
619
620 #[test]
621 fn test_large_array_payload() {
622 let payload = large_array_payload(50);
623 let arr = payload.as_array().unwrap();
624 assert_eq!(arr.len(), 50);
625 }
626
627 #[test]
628 fn test_large_tools_call_request() {
629 let req = large_tools_call_request(1, 5);
630 let data = req["params"]["arguments"]["data"].as_str().unwrap();
631 assert!(data.len() >= 5 * 1024);
632 }
633
634 #[test]
639 fn valid_initialized_notification_has_no_id() {
640 let notif = valid_initialized_notification();
641 assert!(notif.get("id").is_none());
642 assert_eq!(notif["method"], "notifications/initialized");
643 }
644
645 #[test]
646 fn valid_resources_and_prompts_requests() {
647 let rl = valid_resources_list_request(10);
648 assert_eq!(rl["method"], "resources/list");
649 assert_eq!(rl["id"], 10);
650
651 let rr = valid_resources_read_request(11, "file:///test.txt");
652 assert_eq!(rr["params"]["uri"], "file:///test.txt");
653
654 let pl = valid_prompts_list_request(12);
655 assert_eq!(pl["method"], "prompts/list");
656
657 let pg = valid_prompts_get_request(13, "greet", Some(json!({"name": "Alice"})));
658 assert_eq!(pg["params"]["name"], "greet");
659 assert_eq!(pg["params"]["arguments"]["name"], "Alice");
660
661 let pg_no_args = valid_prompts_get_request(14, "simple", None);
662 assert!(pg_no_args["params"].get("arguments").is_none());
663 }
664
665 #[test]
666 fn valid_ping_pong() {
667 let ping = valid_ping_request(20);
668 assert_eq!(ping["method"], "ping");
669
670 let pong = valid_pong_response(20);
671 assert_eq!(pong["id"], 20);
672 assert!(pong["result"].is_object());
673 }
674
675 #[test]
676 fn valid_initialize_response_structure() {
677 let resp = valid_initialize_response(1);
678 assert_eq!(resp["result"]["protocolVersion"], PROTOCOL_VERSION);
679 assert!(resp["result"]["serverInfo"].is_object());
680 assert!(resp["result"]["capabilities"].is_object());
681 }
682
683 #[test]
684 fn valid_list_and_call_responses() {
685 let tl = valid_tools_list_response(1, vec![json!({"name": "t1"})]);
686 assert_eq!(tl["result"]["tools"].as_array().unwrap().len(), 1);
687
688 let tc = valid_tools_call_response(2, vec![json!({"type": "text", "text": "hi"})], false);
689 assert_eq!(tc["result"]["isError"], false);
690
691 let tc_err = valid_tools_call_response(3, vec![], true);
692 assert_eq!(tc_err["result"]["isError"], true);
693
694 let rl = valid_resources_list_response(4, vec![]);
695 assert!(rl["result"]["resources"].as_array().unwrap().is_empty());
696
697 let rr = valid_resources_read_response(5, vec![json!({"uri": "x", "text": "data"})]);
698 assert_eq!(rr["result"]["contents"].as_array().unwrap().len(), 1);
699
700 let pl = valid_prompts_list_response(6, vec![json!({"name": "p1"})]);
701 assert_eq!(pl["result"]["prompts"].as_array().unwrap().len(), 1);
702
703 let pg = valid_prompts_get_response(7, Some("desc"), vec![]);
704 assert_eq!(pg["result"]["description"], "desc");
705
706 let pg_no_desc = valid_prompts_get_response(8, None, vec![]);
707 assert!(pg_no_desc["result"].get("description").is_none());
708 }
709
710 #[test]
711 fn error_response_variants() {
712 let ir = invalid_request_error_response(1);
713 assert_eq!(ir["error"]["code"], -32600);
714
715 let ip = invalid_params_error_response(2, "bad param");
716 assert_eq!(ip["error"]["code"], -32602);
717 assert_eq!(ip["error"]["data"]["details"], "bad param");
718
719 let ie = internal_error_response(3, Some("crash"));
720 assert_eq!(ie["error"]["code"], -32603);
721 assert_eq!(ie["error"]["data"]["details"], "crash");
722
723 let ie_none = internal_error_response(4, None);
724 assert!(ie_none["error"].get("data").is_none());
725
726 let rnf = resource_not_found_error_response(5, "file:///x");
727 assert!(
728 rnf["error"]["message"]
729 .as_str()
730 .unwrap()
731 .contains("file:///x")
732 );
733
734 let tnf = tool_not_found_error_response(6, "missing_tool");
735 assert!(
736 tnf["error"]["message"]
737 .as_str()
738 .unwrap()
739 .contains("missing_tool")
740 );
741
742 let pnf = prompt_not_found_error_response(7, "missing_prompt");
743 assert!(
744 pnf["error"]["message"]
745 .as_str()
746 .unwrap()
747 .contains("missing_prompt")
748 );
749 }
750
751 #[test]
752 fn invalid_message_remaining_variants() {
753 let imt = invalid::invalid_method_type();
754 assert_eq!(imt["method"], 123);
755
756 let iid = invalid::invalid_id_type();
757 assert!(iid["id"].is_array());
758
759 let bre = invalid::both_result_and_error();
760 assert!(bre.get("result").is_some());
761 assert!(bre.get("error").is_some());
762
763 let mjs = invalid::malformed_json_string();
764 assert!(!mjs.is_empty());
765
766 let eo = invalid::empty_object();
767 assert!(eo.as_object().unwrap().is_empty());
768
769 let nv = invalid::null_value();
770 assert!(nv.is_null());
771
772 let aio = invalid::array_instead_of_object();
773 assert!(aio.is_array());
774 }
775
776 #[test]
777 fn notification_without_params() {
778 let notif = notification("test/event", None);
779 assert!(notif.get("params").is_none());
780 assert!(notif.get("id").is_none());
781 }
782
783 #[test]
784 fn log_and_cancelled_notifications() {
785 let log = log_notification("error", json!("something failed"));
786 assert_eq!(log["method"], "notifications/log");
787 assert_eq!(log["params"]["level"], "error");
788
789 let cancel = cancelled_notification(42, Some("timeout"));
790 assert_eq!(cancel["method"], "notifications/cancelled");
791 assert_eq!(cancel["params"]["requestId"], 42);
792 assert_eq!(cancel["params"]["reason"], "timeout");
793 }
794
795 #[test]
796 fn error_response_with_data() {
797 let resp = error_response(1, -32000, "Custom error", Some(json!({"extra": true})));
798 assert_eq!(resp["error"]["data"]["extra"], true);
799 }
800}