1pub mod schema;
45pub mod session;
46
47use serde_json::Value;
48
49#[derive(Debug, Clone, PartialEq)]
53pub enum JsonRpcId {
54 Number(i64),
55 String(String),
56}
57
58impl std::fmt::Display for JsonRpcId {
59 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
60 match self {
61 Self::Number(n) => write!(f, "{n}"),
62 Self::String(s) => write!(f, "{s}"),
63 }
64 }
65}
66
67#[derive(Debug)]
69pub enum JsonRpcMessage {
70 Request(JsonRpcRequest),
72 Notification(JsonRpcNotification),
74 Response(JsonRpcResponse),
76}
77
78#[derive(Debug)]
79pub struct JsonRpcRequest {
80 pub id: JsonRpcId,
81 pub method: String,
82 pub params: Option<Value>,
83}
84
85#[derive(Debug)]
86pub struct JsonRpcNotification {
87 pub method: String,
88 pub params: Option<Value>,
89}
90
91#[derive(Debug)]
92pub struct JsonRpcResponse {
93 pub id: JsonRpcId,
94 pub result: Option<Value>,
95 pub error: Option<JsonRpcError>,
96}
97
98#[derive(Debug)]
99pub struct JsonRpcError {
100 pub code: i64,
101 pub message: String,
102 pub data: Option<Value>,
103}
104
105pub mod error_code {
108 pub const PARSE_ERROR: i64 = -32700;
110 pub const INVALID_REQUEST: i64 = -32600;
112 pub const METHOD_NOT_FOUND: i64 = -32601;
114 pub const INVALID_PARAMS: i64 = -32602;
116 pub const INTERNAL_ERROR: i64 = -32603;
118
119 pub fn label(code: i64) -> &'static str {
121 match code {
122 PARSE_ERROR => "Parse error",
123 INVALID_REQUEST => "Invalid request",
124 METHOD_NOT_FOUND => "Method not found",
125 INVALID_PARAMS => "Invalid params",
126 INTERNAL_ERROR => "Internal error",
127 -32099..=-32000 => "Server error",
128 _ => "Unknown error",
129 }
130 }
131}
132
133pub fn error_response(id: &Value, code: i64, message: &str) -> Vec<u8> {
138 let resp = serde_json::json!({
139 "jsonrpc": "2.0",
140 "id": id,
141 "error": {
142 "code": code,
143 "message": message,
144 }
145 });
146 serde_json::to_vec(&resp).unwrap_or_default()
147}
148
149pub fn extract_error_code(body: &Value) -> Option<(i64, &str)> {
151 let err = body.get("error")?;
152 let code = err.get("code")?.as_i64()?;
153 let message = err.get("message")?.as_str()?;
154 Some((code, message))
155}
156
157#[derive(Debug, Clone, PartialEq)]
161pub enum McpMethod {
162 Initialize,
163 Initialized,
164 Ping,
165 ToolsList,
166 ToolsCall,
167 ResourcesList,
168 ResourcesRead,
169 ResourcesTemplatesList,
170 ResourcesSubscribe,
171 ResourcesUnsubscribe,
172 PromptsList,
173 PromptsGet,
174 LoggingSetLevel,
175 CompletionComplete,
176 NotificationsToolsListChanged,
177 NotificationsCancelled,
178 NotificationsProgress,
179 Notification(String),
181 Unknown(String),
183}
184
185pub const INITIALIZE: &str = "initialize";
187pub const INITIALIZED: &str = "notifications/initialized";
188pub const PING: &str = "ping";
189pub const TOOLS_LIST: &str = "tools/list";
190pub const TOOLS_CALL: &str = "tools/call";
191pub const RESOURCES_LIST: &str = "resources/list";
192pub const RESOURCES_READ: &str = "resources/read";
193pub const RESOURCES_SUBSCRIBE: &str = "resources/subscribe";
194pub const RESOURCES_UNSUBSCRIBE: &str = "resources/unsubscribe";
195pub const PROMPTS_LIST: &str = "prompts/list";
196pub const PROMPTS_GET: &str = "prompts/get";
197pub const LOGGING_SET_LEVEL: &str = "logging/setLevel";
198pub const COMPLETION_COMPLETE: &str = "completion/complete";
199pub const RESOURCES_TEMPLATES_LIST: &str = "resources/templates/list";
200pub const NOTIFICATIONS_TOOLS_LIST_CHANGED: &str = "notifications/tools/list_changed";
201pub const NOTIFICATIONS_CANCELLED: &str = "notifications/cancelled";
202pub const NOTIFICATIONS_PROGRESS: &str = "notifications/progress";
203
204impl McpMethod {
205 pub fn parse(method: &str) -> Self {
206 match method {
207 INITIALIZE => Self::Initialize,
208 INITIALIZED => Self::Initialized,
209 PING => Self::Ping,
210 TOOLS_LIST => Self::ToolsList,
211 TOOLS_CALL => Self::ToolsCall,
212 RESOURCES_LIST => Self::ResourcesList,
213 RESOURCES_READ => Self::ResourcesRead,
214 RESOURCES_TEMPLATES_LIST => Self::ResourcesTemplatesList,
215 RESOURCES_SUBSCRIBE => Self::ResourcesSubscribe,
216 RESOURCES_UNSUBSCRIBE => Self::ResourcesUnsubscribe,
217 PROMPTS_LIST => Self::PromptsList,
218 PROMPTS_GET => Self::PromptsGet,
219 LOGGING_SET_LEVEL => Self::LoggingSetLevel,
220 COMPLETION_COMPLETE => Self::CompletionComplete,
221 NOTIFICATIONS_TOOLS_LIST_CHANGED => Self::NotificationsToolsListChanged,
222 NOTIFICATIONS_CANCELLED => Self::NotificationsCancelled,
223 NOTIFICATIONS_PROGRESS => Self::NotificationsProgress,
224 m if m.starts_with("notifications/") => Self::Notification(m.to_string()),
225 m => Self::Unknown(m.to_string()),
226 }
227 }
228
229 pub fn as_str(&self) -> &str {
231 match self {
232 Self::Initialize => INITIALIZE,
233 Self::Initialized => INITIALIZED,
234 Self::Ping => PING,
235 Self::ToolsList => TOOLS_LIST,
236 Self::ToolsCall => TOOLS_CALL,
237 Self::ResourcesList => RESOURCES_LIST,
238 Self::ResourcesRead => RESOURCES_READ,
239 Self::ResourcesTemplatesList => RESOURCES_TEMPLATES_LIST,
240 Self::ResourcesSubscribe => RESOURCES_SUBSCRIBE,
241 Self::ResourcesUnsubscribe => RESOURCES_UNSUBSCRIBE,
242 Self::PromptsList => PROMPTS_LIST,
243 Self::PromptsGet => PROMPTS_GET,
244 Self::LoggingSetLevel => LOGGING_SET_LEVEL,
245 Self::CompletionComplete => COMPLETION_COMPLETE,
246 Self::NotificationsToolsListChanged => NOTIFICATIONS_TOOLS_LIST_CHANGED,
247 Self::NotificationsCancelled => NOTIFICATIONS_CANCELLED,
248 Self::NotificationsProgress => NOTIFICATIONS_PROGRESS,
249 Self::Notification(m) => m.as_str(),
250 Self::Unknown(m) => m.as_str(),
251 }
252 }
253}
254
255fn parse_id(value: &Value) -> Option<JsonRpcId> {
259 match value {
260 Value::Number(n) => n.as_i64().map(JsonRpcId::Number),
261 Value::String(s) => Some(JsonRpcId::String(s.clone())),
262 _ => None,
263 }
264}
265
266fn parse_error(value: &Value) -> Option<JsonRpcError> {
268 let obj = value.as_object()?;
269 Some(JsonRpcError {
270 code: obj.get("code")?.as_i64()?,
271 message: obj.get("message")?.as_str()?.to_string(),
272 data: obj.get("data").cloned(),
273 })
274}
275
276pub fn parse_message(value: &Value) -> Option<JsonRpcMessage> {
279 let obj = value.as_object()?;
280
281 if obj.get("jsonrpc")?.as_str()? != "2.0" {
283 return None;
284 }
285
286 let id = obj.get("id").and_then(parse_id);
287 let method = obj.get("method").and_then(|m| m.as_str()).map(String::from);
288 let params = obj.get("params").cloned();
289
290 match (method, id) {
291 (Some(method), Some(id)) => Some(JsonRpcMessage::Request(JsonRpcRequest {
293 id,
294 method,
295 params,
296 })),
297 (Some(method), None) => Some(JsonRpcMessage::Notification(JsonRpcNotification {
299 method,
300 params,
301 })),
302 (None, Some(id)) => {
304 let result = obj.get("result").cloned();
305 let error = obj.get("error").and_then(parse_error);
306 Some(JsonRpcMessage::Response(JsonRpcResponse {
307 id,
308 result,
309 error,
310 }))
311 }
312 (None, None) => None,
314 }
315}
316
317#[derive(Debug)]
319pub struct ParsedBody {
320 pub messages: Vec<JsonRpcMessage>,
321 pub is_batch: bool,
322}
323
324impl ParsedBody {
325 pub fn method_str(&self) -> &str {
328 self.messages
329 .iter()
330 .find_map(|m| match m {
331 JsonRpcMessage::Request(r) => Some(r.method.as_str()),
332 JsonRpcMessage::Notification(n) => Some(n.method.as_str()),
333 _ => None,
334 })
335 .unwrap_or("unknown")
336 }
337
338 pub fn mcp_method(&self) -> McpMethod {
340 McpMethod::parse(self.method_str())
341 }
342
343 pub fn first_request_id(&self) -> Option<&JsonRpcId> {
345 self.messages.iter().find_map(|m| match m {
346 JsonRpcMessage::Request(r) => Some(&r.id),
347 _ => None,
348 })
349 }
350
351 pub fn is_notification_only(&self) -> bool {
353 self.messages
354 .iter()
355 .all(|m| matches!(m, JsonRpcMessage::Notification(_)))
356 }
357
358 pub fn detail(&self) -> Option<String> {
363 let params = self.first_params()?;
364 let method = self.mcp_method();
365 match method {
366 McpMethod::ToolsCall => params.get("name")?.as_str().map(String::from),
367 McpMethod::ResourcesRead => params.get("uri")?.as_str().map(String::from),
368 McpMethod::PromptsGet => params.get("name")?.as_str().map(String::from),
369 McpMethod::NotificationsCancelled => {
370 params.get("requestId").map(|v| match v {
372 Value::String(s) => s.clone(),
373 Value::Number(n) => n.to_string(),
374 _ => v.to_string(),
375 })
376 }
377 McpMethod::NotificationsProgress => {
378 params.get("progressToken").map(|v| match v {
380 Value::String(s) => s.clone(),
381 Value::Number(n) => n.to_string(),
382 _ => v.to_string(),
383 })
384 }
385 _ => None,
386 }
387 }
388
389 pub fn first_params(&self) -> Option<&Value> {
391 self.messages.iter().find_map(|m| match m {
392 JsonRpcMessage::Request(r) => r.params.as_ref(),
393 JsonRpcMessage::Notification(n) => n.params.as_ref(),
394 _ => None,
395 })
396 }
397}
398
399pub fn parse_body(body: &[u8]) -> Option<ParsedBody> {
402 let value: Value = serde_json::from_slice(body).ok()?;
403
404 if let Some(arr) = value.as_array() {
405 let messages: Vec<_> = arr.iter().filter_map(parse_message).collect();
407 if messages.is_empty() {
408 return None;
409 }
410 Some(ParsedBody {
411 messages,
412 is_batch: true,
413 })
414 } else {
415 let msg = parse_message(&value)?;
417 Some(ParsedBody {
418 messages: vec![msg],
419 is_batch: false,
420 })
421 }
422}
423
424#[cfg(test)]
427#[allow(non_snake_case)]
428mod tests {
429 use super::*;
430 use serde_json::json;
431
432 #[test]
435 fn parse_message__request() {
436 let val = json!({"jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": {"name": "get_weather"}});
437 let msg = parse_message(&val).unwrap();
438 match msg {
439 JsonRpcMessage::Request(r) => {
440 assert_eq!(r.id, JsonRpcId::Number(1));
441 assert_eq!(r.method, "tools/call");
442 assert!(r.params.is_some());
443 }
444 _ => panic!("expected Request"),
445 }
446 }
447
448 #[test]
449 fn parse_message__request_string_id() {
450 let val = json!({"jsonrpc": "2.0", "id": "abc-123", "method": "initialize"});
451 let msg = parse_message(&val).unwrap();
452 match msg {
453 JsonRpcMessage::Request(r) => {
454 assert_eq!(r.id, JsonRpcId::String("abc-123".into()));
455 assert_eq!(r.method, "initialize");
456 }
457 _ => panic!("expected Request"),
458 }
459 }
460
461 #[test]
462 fn parse_message__notification() {
463 let val = json!({"jsonrpc": "2.0", "method": "notifications/initialized"});
464 let msg = parse_message(&val).unwrap();
465 match msg {
466 JsonRpcMessage::Notification(n) => {
467 assert_eq!(n.method, "notifications/initialized");
468 assert!(n.params.is_none());
469 }
470 _ => panic!("expected Notification"),
471 }
472 }
473
474 #[test]
475 fn parse_message__response_result() {
476 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
477 let msg = parse_message(&val).unwrap();
478 match msg {
479 JsonRpcMessage::Response(r) => {
480 assert_eq!(r.id, JsonRpcId::Number(1));
481 assert!(r.result.is_some());
482 assert!(r.error.is_none());
483 }
484 _ => panic!("expected Response"),
485 }
486 }
487
488 #[test]
489 fn parse_message__response_error() {
490 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
491 let msg = parse_message(&val).unwrap();
492 match msg {
493 JsonRpcMessage::Response(r) => {
494 assert_eq!(r.id, JsonRpcId::Number(1));
495 assert!(r.result.is_none());
496 let err = r.error.unwrap();
497 assert_eq!(err.code, -32601);
498 assert_eq!(err.message, "Method not found");
499 }
500 _ => panic!("expected Response"),
501 }
502 }
503
504 #[test]
505 fn parse_message__rejects_wrong_version() {
506 let val = json!({"jsonrpc": "1.0", "id": 1, "method": "test"});
507 assert!(parse_message(&val).is_none());
508 }
509
510 #[test]
511 fn parse_message__rejects_missing_jsonrpc() {
512 let val = json!({"id": 1, "method": "test"});
513 assert!(parse_message(&val).is_none());
514 }
515
516 #[test]
517 fn parse_message__rejects_no_method_no_id() {
518 let val = json!({"jsonrpc": "2.0"});
519 assert!(parse_message(&val).is_none());
520 }
521
522 #[test]
523 fn parse_message__rejects_non_object() {
524 let val = json!("hello");
525 assert!(parse_message(&val).is_none());
526 }
527
528 #[test]
529 fn parse_message__rejects_oauth_register() {
530 let val = json!({
531 "client_name": "My App",
532 "redirect_uris": ["https://example.com/callback"],
533 "grant_types": ["authorization_code"]
534 });
535 assert!(parse_message(&val).is_none());
536 }
537
538 #[test]
541 fn parse_body__single_request() {
542 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
543 let parsed = parse_body(body).unwrap();
544 assert!(!parsed.is_batch);
545 assert_eq!(parsed.messages.len(), 1);
546 assert_eq!(parsed.method_str(), "tools/list");
547 assert_eq!(parsed.mcp_method(), McpMethod::ToolsList);
548 }
549
550 #[test]
551 fn parse_body__batch_requests() {
552 let body = br#"[
553 {"jsonrpc":"2.0","id":1,"method":"tools/list"},
554 {"jsonrpc":"2.0","id":2,"method":"resources/list"}
555 ]"#;
556 let parsed = parse_body(body).unwrap();
557 assert!(parsed.is_batch);
558 assert_eq!(parsed.messages.len(), 2);
559 assert_eq!(parsed.method_str(), "tools/list");
560 }
561
562 #[test]
563 fn parse_body__notification_only() {
564 let body = br#"{"jsonrpc":"2.0","method":"notifications/initialized"}"#;
565 let parsed = parse_body(body).unwrap();
566 assert!(parsed.is_notification_only());
567 assert_eq!(parsed.mcp_method(), McpMethod::Initialized);
568 }
569
570 #[test]
571 fn parse_body__mixed_batch() {
572 let body = br#"[
573 {"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":1}},
574 {"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"get_weather"}}
575 ]"#;
576 let parsed = parse_body(body).unwrap();
577 assert!(parsed.is_batch);
578 assert!(!parsed.is_notification_only());
579 assert_eq!(parsed.first_request_id(), Some(&JsonRpcId::Number(2)));
580 }
581
582 #[test]
583 fn parse_body__rejects_empty_batch() {
584 let body = b"[]";
585 assert!(parse_body(body).is_none());
586 }
587
588 #[test]
589 fn parse_body__rejects_invalid_json() {
590 assert!(parse_body(b"not json").is_none());
591 }
592
593 #[test]
594 fn parse_body__rejects_non_jsonrpc() {
595 let body = br#"{"grant_type":"client_credentials","client_id":"abc"}"#;
596 assert!(parse_body(body).is_none());
597 }
598
599 #[test]
600 fn parse_body__rejects_batch_of_non_jsonrpc() {
601 let body = br#"[{"foo":"bar"},{"baz":1}]"#;
602 assert!(parse_body(body).is_none());
603 }
604
605 #[test]
608 fn mcp_method__known_methods() {
609 assert_eq!(McpMethod::parse("initialize"), McpMethod::Initialize);
610 assert_eq!(McpMethod::parse("tools/call"), McpMethod::ToolsCall);
611 assert_eq!(McpMethod::parse("tools/list"), McpMethod::ToolsList);
612 assert_eq!(McpMethod::parse("resources/read"), McpMethod::ResourcesRead);
613 assert_eq!(McpMethod::parse("resources/list"), McpMethod::ResourcesList);
614 assert_eq!(
615 McpMethod::parse("resources/templates/list"),
616 McpMethod::ResourcesTemplatesList
617 );
618 assert_eq!(McpMethod::parse("prompts/list"), McpMethod::PromptsList);
619 assert_eq!(McpMethod::parse("prompts/get"), McpMethod::PromptsGet);
620 assert_eq!(McpMethod::parse("ping"), McpMethod::Ping);
621 assert_eq!(
622 McpMethod::parse("logging/setLevel"),
623 McpMethod::LoggingSetLevel
624 );
625 assert_eq!(
626 McpMethod::parse("completion/complete"),
627 McpMethod::CompletionComplete
628 );
629 assert_eq!(
630 McpMethod::parse("notifications/tools/list_changed"),
631 McpMethod::NotificationsToolsListChanged
632 );
633 assert_eq!(
634 McpMethod::parse("notifications/cancelled"),
635 McpMethod::NotificationsCancelled
636 );
637 assert_eq!(
638 McpMethod::parse("notifications/progress"),
639 McpMethod::NotificationsProgress
640 );
641 }
642
643 #[test]
644 fn mcp_method__notifications() {
645 assert_eq!(
646 McpMethod::parse("notifications/initialized"),
647 McpMethod::Initialized
648 );
649 assert_eq!(
651 McpMethod::parse("notifications/cancelled"),
652 McpMethod::NotificationsCancelled
653 );
654 assert_eq!(
655 McpMethod::parse("notifications/progress"),
656 McpMethod::NotificationsProgress
657 );
658 assert_eq!(
659 McpMethod::parse("notifications/tools/list_changed"),
660 McpMethod::NotificationsToolsListChanged
661 );
662 assert_eq!(
664 McpMethod::parse("notifications/resources/updated"),
665 McpMethod::Notification("notifications/resources/updated".into())
666 );
667 }
668
669 #[test]
670 fn mcp_method__unknown() {
671 assert_eq!(
672 McpMethod::parse("custom/method"),
673 McpMethod::Unknown("custom/method".into())
674 );
675 }
676
677 #[test]
678 fn mcp_method__as_str_roundtrip() {
679 let methods = [
680 "initialize",
681 "notifications/initialized",
682 "ping",
683 "tools/list",
684 "tools/call",
685 "resources/list",
686 "resources/read",
687 "resources/templates/list",
688 "prompts/list",
689 "prompts/get",
690 "logging/setLevel",
691 "completion/complete",
692 "notifications/tools/list_changed",
693 "notifications/cancelled",
694 "notifications/progress",
695 ];
696 for m in methods {
697 assert_eq!(McpMethod::parse(m).as_str(), m);
698 }
699 }
700
701 #[test]
704 fn jsonrpc_id__display() {
705 assert_eq!(JsonRpcId::Number(42).to_string(), "42");
706 assert_eq!(JsonRpcId::String("abc".into()).to_string(), "abc");
707 }
708
709 #[test]
712 fn parsed_body__first_params_from_request() {
713 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo"}}"#;
714 let parsed = parse_body(body).unwrap();
715 let params = parsed.first_params().unwrap();
716 assert_eq!(params["name"], "echo");
717 }
718
719 #[test]
720 fn parsed_body__first_params_none_for_response() {
721 let body = br#"{"jsonrpc":"2.0","id":1,"result":{}}"#;
722 let parsed = parse_body(body).unwrap();
723 assert!(parsed.first_params().is_none());
724 }
725
726 #[test]
727 fn parsed_body__method_str_defaults_to_unknown() {
728 let body = br#"{"jsonrpc":"2.0","id":1,"result":{"tools":[]}}"#;
729 let parsed = parse_body(body).unwrap();
730 assert_eq!(parsed.method_str(), "unknown");
731 }
732
733 #[test]
736 fn parsed_body__detail_tools_call() {
737 let body =
738 br#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"get_weather"}}"#;
739 let parsed = parse_body(body).unwrap();
740 assert_eq!(parsed.detail().as_deref(), Some("get_weather"));
741 }
742
743 #[test]
744 fn parsed_body__detail_resources_read() {
745 let body = br#"{"jsonrpc":"2.0","id":1,"method":"resources/read","params":{"uri":"ui://widget/clock.html"}}"#;
746 let parsed = parse_body(body).unwrap();
747 assert_eq!(parsed.detail().as_deref(), Some("ui://widget/clock.html"));
748 }
749
750 #[test]
751 fn parsed_body__detail_none_for_tools_list() {
752 let body = br#"{"jsonrpc":"2.0","id":1,"method":"tools/list"}"#;
753 let parsed = parse_body(body).unwrap();
754 assert!(parsed.detail().is_none());
755 }
756
757 #[test]
758 fn parsed_body__detail_notifications_cancelled() {
759 let body = br#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":"req-42","reason":"timeout"}}"#;
760 let parsed = parse_body(body).unwrap();
761 assert_eq!(parsed.detail().as_deref(), Some("req-42"));
762 }
763
764 #[test]
765 fn parsed_body__detail_cancelled_numeric_id() {
766 let body =
767 br#"{"jsonrpc":"2.0","method":"notifications/cancelled","params":{"requestId":7}}"#;
768 let parsed = parse_body(body).unwrap();
769 assert_eq!(parsed.detail().as_deref(), Some("7"));
770 }
771
772 #[test]
773 fn parsed_body__detail_notifications_progress() {
774 let body = br#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":"tok-1","progress":50,"total":100}}"#;
775 let parsed = parse_body(body).unwrap();
776 assert_eq!(parsed.detail().as_deref(), Some("tok-1"));
777 }
778
779 #[test]
780 fn parsed_body__detail_progress_numeric_token() {
781 let body = br#"{"jsonrpc":"2.0","method":"notifications/progress","params":{"progressToken":99,"progress":10}}"#;
782 let parsed = parse_body(body).unwrap();
783 assert_eq!(parsed.detail().as_deref(), Some("99"));
784 }
785
786 #[test]
789 fn error_code__labels() {
790 assert_eq!(error_code::label(error_code::PARSE_ERROR), "Parse error");
791 assert_eq!(
792 error_code::label(error_code::METHOD_NOT_FOUND),
793 "Method not found"
794 );
795 assert_eq!(
796 error_code::label(error_code::INVALID_PARAMS),
797 "Invalid params"
798 );
799 assert_eq!(
800 error_code::label(error_code::INTERNAL_ERROR),
801 "Internal error"
802 );
803 assert_eq!(error_code::label(-32000), "Server error");
804 assert_eq!(error_code::label(-32099), "Server error");
805 assert_eq!(error_code::label(42), "Unknown error");
806 }
807
808 #[test]
811 fn error_response__numeric_id() {
812 let body = error_response(&json!(1), error_code::METHOD_NOT_FOUND, "Method not found");
813 let parsed: Value = serde_json::from_slice(&body).unwrap();
814 assert_eq!(parsed["jsonrpc"], "2.0");
815 assert_eq!(parsed["id"], 1);
816 assert_eq!(parsed["error"]["code"], -32601);
817 assert_eq!(parsed["error"]["message"], "Method not found");
818 }
819
820 #[test]
821 fn error_response__null_id() {
822 let body = error_response(&Value::Null, error_code::PARSE_ERROR, "Parse error");
823 let parsed: Value = serde_json::from_slice(&body).unwrap();
824 assert_eq!(parsed["id"], Value::Null);
825 assert_eq!(parsed["error"]["code"], -32700);
826 }
827
828 #[test]
831 fn extract_error_code__from_error_response() {
832 let val = json!({"jsonrpc": "2.0", "id": 1, "error": {"code": -32601, "message": "Method not found"}});
833 let (code, msg) = extract_error_code(&val).unwrap();
834 assert_eq!(code, -32601);
835 assert_eq!(msg, "Method not found");
836 }
837
838 #[test]
839 fn extract_error_code__none_for_success() {
840 let val = json!({"jsonrpc": "2.0", "id": 1, "result": {"tools": []}});
841 assert!(extract_error_code(&val).is_none());
842 }
843}