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