Skip to main content

fastmcp_protocol/
jsonrpc.rs

1//! JSON-RPC 2.0 message types.
2
3use std::borrow::Cow;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6use serde_json::Value;
7
8/// The JSON-RPC version string. Used as a static reference to avoid allocations.
9pub const JSONRPC_VERSION: &str = "2.0";
10
11/// Serializes the jsonrpc version field.
12fn serialize_jsonrpc_version<S>(value: &str, serializer: S) -> Result<S::Ok, S::Error>
13where
14    S: Serializer,
15{
16    serializer.serialize_str(value)
17}
18
19/// Deserializes the jsonrpc version field, returning a borrowed reference for "2.0".
20fn deserialize_jsonrpc_version<'de, D>(deserializer: D) -> Result<Cow<'static, str>, D::Error>
21where
22    D: Deserializer<'de>,
23{
24    let s = String::deserialize(deserializer)?;
25    if s == JSONRPC_VERSION {
26        Ok(Cow::Borrowed(JSONRPC_VERSION))
27    } else {
28        Ok(Cow::Owned(s))
29    }
30}
31
32/// JSON-RPC request ID.
33#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[serde(untagged)]
35pub enum RequestId {
36    /// Integer ID.
37    Number(i64),
38    /// String ID.
39    String(String),
40}
41
42impl From<i64> for RequestId {
43    fn from(id: i64) -> Self {
44        RequestId::Number(id)
45    }
46}
47
48impl From<String> for RequestId {
49    fn from(id: String) -> Self {
50        RequestId::String(id)
51    }
52}
53
54impl From<&str> for RequestId {
55    fn from(id: &str) -> Self {
56        RequestId::String(id.to_owned())
57    }
58}
59
60impl std::fmt::Display for RequestId {
61    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62        match self {
63            RequestId::Number(n) => write!(f, "{n}"),
64            RequestId::String(s) => write!(f, "{s}"),
65        }
66    }
67}
68
69/// JSON-RPC 2.0 request.
70#[derive(Debug, Clone, Serialize, Deserialize)]
71pub struct JsonRpcRequest {
72    /// Protocol version (always "2.0").
73    #[serde(
74        serialize_with = "serialize_jsonrpc_version",
75        deserialize_with = "deserialize_jsonrpc_version"
76    )]
77    pub jsonrpc: Cow<'static, str>,
78    /// Method name.
79    pub method: String,
80    /// Request parameters.
81    #[serde(skip_serializing_if = "Option::is_none")]
82    pub params: Option<Value>,
83    /// Request ID (absent for notifications).
84    #[serde(skip_serializing_if = "Option::is_none")]
85    pub id: Option<RequestId>,
86}
87
88impl JsonRpcRequest {
89    /// Creates a new request with the given method and parameters.
90    #[must_use]
91    pub fn new(method: impl Into<String>, params: Option<Value>, id: impl Into<RequestId>) -> Self {
92        Self {
93            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
94            method: method.into(),
95            params,
96            id: Some(id.into()),
97        }
98    }
99
100    /// Creates a notification (request without ID).
101    #[must_use]
102    pub fn notification(method: impl Into<String>, params: Option<Value>) -> Self {
103        Self {
104            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
105            method: method.into(),
106            params,
107            id: None,
108        }
109    }
110
111    /// Returns true if this is a notification (no ID).
112    #[must_use]
113    pub fn is_notification(&self) -> bool {
114        self.id.is_none()
115    }
116}
117
118/// JSON-RPC 2.0 error object.
119#[derive(Debug, Clone, Serialize, Deserialize)]
120pub struct JsonRpcError {
121    /// Error code.
122    pub code: i32,
123    /// Error message.
124    pub message: String,
125    /// Additional error data.
126    #[serde(skip_serializing_if = "Option::is_none")]
127    pub data: Option<Value>,
128}
129
130impl From<fastmcp_core::McpError> for JsonRpcError {
131    fn from(err: fastmcp_core::McpError) -> Self {
132        Self {
133            code: err.code.into(),
134            message: err.message,
135            data: err.data,
136        }
137    }
138}
139
140/// JSON-RPC 2.0 response.
141#[derive(Debug, Clone, Serialize, Deserialize)]
142pub struct JsonRpcResponse {
143    /// Protocol version (always "2.0").
144    #[serde(
145        serialize_with = "serialize_jsonrpc_version",
146        deserialize_with = "deserialize_jsonrpc_version"
147    )]
148    pub jsonrpc: Cow<'static, str>,
149    /// Result (present on success).
150    #[serde(skip_serializing_if = "Option::is_none")]
151    pub result: Option<Value>,
152    /// Error (present on failure).
153    #[serde(skip_serializing_if = "Option::is_none")]
154    pub error: Option<JsonRpcError>,
155    /// Request ID this is responding to.
156    pub id: Option<RequestId>,
157}
158
159impl JsonRpcResponse {
160    /// Creates a success response.
161    #[must_use]
162    pub fn success(id: RequestId, result: Value) -> Self {
163        Self {
164            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
165            result: Some(result),
166            error: None,
167            id: Some(id),
168        }
169    }
170
171    /// Creates an error response.
172    #[must_use]
173    pub fn error(id: Option<RequestId>, error: JsonRpcError) -> Self {
174        Self {
175            jsonrpc: Cow::Borrowed(JSONRPC_VERSION),
176            result: None,
177            error: Some(error),
178            id,
179        }
180    }
181
182    /// Returns true if this is an error response.
183    #[must_use]
184    pub fn is_error(&self) -> bool {
185        self.error.is_some()
186    }
187}
188
189/// A JSON-RPC message (request, response, or notification).
190#[derive(Debug, Clone, Serialize, Deserialize)]
191#[serde(untagged)]
192pub enum JsonRpcMessage {
193    /// A request or notification.
194    Request(JsonRpcRequest),
195    /// A response.
196    Response(JsonRpcResponse),
197}
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202    use serde_json::json;
203
204    // ========================================================================
205    // RequestId Tests
206    // ========================================================================
207
208    #[test]
209    fn request_id_number_serialization() {
210        let id = RequestId::Number(42);
211        let value = serde_json::to_value(&id).expect("serialize");
212        assert_eq!(value, 42);
213    }
214
215    #[test]
216    fn request_id_string_serialization() {
217        let id = RequestId::String("req-1".to_string());
218        let value = serde_json::to_value(&id).expect("serialize");
219        assert_eq!(value, "req-1");
220    }
221
222    #[test]
223    fn request_id_number_deserialization() {
224        let id: RequestId = serde_json::from_value(json!(99)).expect("deserialize");
225        assert_eq!(id, RequestId::Number(99));
226    }
227
228    #[test]
229    fn request_id_string_deserialization() {
230        let id: RequestId = serde_json::from_value(json!("abc")).expect("deserialize");
231        assert_eq!(id, RequestId::String("abc".to_string()));
232    }
233
234    #[test]
235    fn request_id_from_i64() {
236        let id: RequestId = 7i64.into();
237        assert_eq!(id, RequestId::Number(7));
238    }
239
240    #[test]
241    fn request_id_from_string() {
242        let id: RequestId = "test-id".to_string().into();
243        assert_eq!(id, RequestId::String("test-id".to_string()));
244    }
245
246    #[test]
247    fn request_id_from_str() {
248        let id: RequestId = "test-id".into();
249        assert_eq!(id, RequestId::String("test-id".to_string()));
250    }
251
252    #[test]
253    fn request_id_display() {
254        assert_eq!(format!("{}", RequestId::Number(42)), "42");
255        assert_eq!(
256            format!("{}", RequestId::String("req-1".to_string())),
257            "req-1"
258        );
259    }
260
261    #[test]
262    fn request_id_equality() {
263        assert_eq!(RequestId::Number(1), RequestId::Number(1));
264        assert_ne!(RequestId::Number(1), RequestId::Number(2));
265        assert_eq!(
266            RequestId::String("a".to_string()),
267            RequestId::String("a".to_string())
268        );
269        assert_ne!(RequestId::Number(1), RequestId::String("1".to_string()));
270    }
271
272    // ========================================================================
273    // JsonRpcRequest Tests
274    // ========================================================================
275
276    #[test]
277    fn request_serialization() {
278        let req = JsonRpcRequest::new("tools/list", None, 1i64);
279        let json = serde_json::to_string(&req).unwrap();
280        assert!(json.contains("\"jsonrpc\":\"2.0\""));
281        assert!(json.contains("\"method\":\"tools/list\""));
282        assert!(json.contains("\"id\":1"));
283    }
284
285    #[test]
286    fn request_with_params() {
287        let params = json!({"name": "greet", "arguments": {"name": "World"}});
288        let req = JsonRpcRequest::new("tools/call", Some(params.clone()), 2i64);
289        let value = serde_json::to_value(&req).expect("serialize");
290        assert_eq!(value["jsonrpc"], "2.0");
291        assert_eq!(value["method"], "tools/call");
292        assert_eq!(value["params"]["name"], "greet");
293        assert_eq!(value["id"], 2);
294    }
295
296    #[test]
297    fn request_without_params_omits_field() {
298        let req = JsonRpcRequest::new("tools/list", None, 1i64);
299        let value = serde_json::to_value(&req).expect("serialize");
300        assert!(value.get("params").is_none());
301    }
302
303    #[test]
304    fn notification_has_no_id() {
305        let notif = JsonRpcRequest::notification("notifications/progress", None);
306        assert!(notif.is_notification());
307        assert!(notif.id.is_none());
308        let value = serde_json::to_value(&notif).expect("serialize");
309        assert!(value.get("id").is_none());
310    }
311
312    #[test]
313    fn notification_with_params() {
314        let params = json!({"uri": "file://changed.txt"});
315        let notif = JsonRpcRequest::notification("notifications/resources/updated", Some(params));
316        assert!(notif.is_notification());
317        let value = serde_json::to_value(&notif).expect("serialize");
318        assert_eq!(value["params"]["uri"], "file://changed.txt");
319    }
320
321    #[test]
322    fn request_is_not_notification() {
323        let req = JsonRpcRequest::new("tools/list", None, 1i64);
324        assert!(!req.is_notification());
325    }
326
327    #[test]
328    fn request_with_string_id() {
329        let req = JsonRpcRequest::new("tools/list", None, "req-abc");
330        let value = serde_json::to_value(&req).expect("serialize");
331        assert_eq!(value["id"], "req-abc");
332    }
333
334    #[test]
335    fn request_round_trip() {
336        let original = JsonRpcRequest::new(
337            "tools/call",
338            Some(json!({"name": "add", "arguments": {"a": 1, "b": 2}})),
339            42i64,
340        );
341        let json_str = serde_json::to_string(&original).expect("serialize");
342        let deserialized: JsonRpcRequest = serde_json::from_str(&json_str).expect("deserialize");
343        assert_eq!(deserialized.method, "tools/call");
344        assert_eq!(deserialized.id, Some(RequestId::Number(42)));
345        assert!(deserialized.params.is_some());
346    }
347
348    // ========================================================================
349    // JsonRpcError Tests
350    // ========================================================================
351
352    #[test]
353    fn jsonrpc_error_serialization() {
354        let error = JsonRpcError {
355            code: -32600,
356            message: "Invalid Request".to_string(),
357            data: None,
358        };
359        let value = serde_json::to_value(&error).expect("serialize");
360        assert_eq!(value["code"], -32600);
361        assert_eq!(value["message"], "Invalid Request");
362        assert!(value.get("data").is_none());
363    }
364
365    #[test]
366    fn jsonrpc_error_with_data() {
367        let error = JsonRpcError {
368            code: -32602,
369            message: "Invalid params".to_string(),
370            data: Some(json!({"field": "name", "reason": "required"})),
371        };
372        let value = serde_json::to_value(&error).expect("serialize");
373        assert_eq!(value["code"], -32602);
374        assert_eq!(value["data"]["field"], "name");
375    }
376
377    #[test]
378    fn jsonrpc_error_standard_codes() {
379        // Parse error
380        let err = JsonRpcError {
381            code: -32700,
382            message: "Parse error".to_string(),
383            data: None,
384        };
385        assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32700);
386
387        // Method not found
388        let err = JsonRpcError {
389            code: -32601,
390            message: "Method not found".to_string(),
391            data: None,
392        };
393        assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32601);
394
395        // Internal error
396        let err = JsonRpcError {
397            code: -32603,
398            message: "Internal error".to_string(),
399            data: None,
400        };
401        assert_eq!(serde_json::to_value(&err).unwrap()["code"], -32603);
402    }
403
404    // ========================================================================
405    // JsonRpcResponse Tests
406    // ========================================================================
407
408    #[test]
409    fn response_success() {
410        let resp = JsonRpcResponse::success(RequestId::Number(1), json!({"result": "ok"}));
411        let value = serde_json::to_value(&resp).expect("serialize");
412        assert_eq!(value["jsonrpc"], "2.0");
413        assert_eq!(value["result"]["result"], "ok");
414        assert_eq!(value["id"], 1);
415        assert!(value.get("error").is_none());
416        assert!(!resp.is_error());
417    }
418
419    #[test]
420    fn response_error() {
421        let error = JsonRpcError {
422            code: -32601,
423            message: "Method not found".to_string(),
424            data: None,
425        };
426        let resp = JsonRpcResponse::error(Some(RequestId::Number(1)), error);
427        let value = serde_json::to_value(&resp).expect("serialize");
428        assert_eq!(value["jsonrpc"], "2.0");
429        assert!(value.get("result").is_none());
430        assert_eq!(value["error"]["code"], -32601);
431        assert_eq!(value["error"]["message"], "Method not found");
432        assert_eq!(value["id"], 1);
433        assert!(resp.is_error());
434    }
435
436    #[test]
437    fn response_error_null_id() {
438        let error = JsonRpcError {
439            code: -32700,
440            message: "Parse error".to_string(),
441            data: None,
442        };
443        let resp = JsonRpcResponse::error(None, error);
444        let value = serde_json::to_value(&resp).expect("serialize");
445        assert!(value["id"].is_null());
446    }
447
448    #[test]
449    fn response_round_trip() {
450        let original =
451            JsonRpcResponse::success(RequestId::String("abc".to_string()), json!({"tools": []}));
452        let json_str = serde_json::to_string(&original).expect("serialize");
453        let deserialized: JsonRpcResponse = serde_json::from_str(&json_str).expect("deserialize");
454        assert!(!deserialized.is_error());
455        assert!(deserialized.result.is_some());
456        assert_eq!(deserialized.id, Some(RequestId::String("abc".to_string())));
457    }
458
459    // ========================================================================
460    // JsonRpcMessage Tests
461    // ========================================================================
462
463    #[test]
464    fn message_request_variant() {
465        let req = JsonRpcRequest::new("tools/list", None, 1i64);
466        let msg = JsonRpcMessage::Request(req);
467        let value = serde_json::to_value(&msg).expect("serialize");
468        assert_eq!(value["method"], "tools/list");
469    }
470
471    #[test]
472    fn message_response_variant() {
473        let resp = JsonRpcResponse::success(RequestId::Number(1), json!("ok"));
474        let msg = JsonRpcMessage::Response(resp);
475        let value = serde_json::to_value(&msg).expect("serialize");
476        assert_eq!(value["result"], "ok");
477    }
478
479    #[test]
480    fn message_deserialize_as_request() {
481        let json_str = r#"{"jsonrpc":"2.0","method":"tools/list","id":1}"#;
482        let msg: JsonRpcMessage = serde_json::from_str(json_str).expect("deserialize");
483        match msg {
484            JsonRpcMessage::Request(req) => {
485                assert_eq!(req.method, "tools/list");
486                assert_eq!(req.id, Some(RequestId::Number(1)));
487            }
488            _ => panic!("Expected request variant"),
489        }
490    }
491
492    #[test]
493    fn message_deserialize_as_response() {
494        let json_str = r#"{"jsonrpc":"2.0","result":{"tools":[]},"id":1}"#;
495        let msg: JsonRpcMessage = serde_json::from_str(json_str).expect("deserialize");
496        match msg {
497            JsonRpcMessage::Response(resp) => {
498                assert!(!resp.is_error());
499                assert_eq!(resp.id, Some(RequestId::Number(1)));
500            }
501            // The untagged enum may also parse as Request depending on field overlap
502            JsonRpcMessage::Request(_) => {
503                // This is acceptable for untagged deserialization
504            }
505        }
506    }
507
508    // ========================================================================
509    // JSONRPC_VERSION constant test
510    // ========================================================================
511
512    #[test]
513    fn jsonrpc_version_constant() {
514        assert_eq!(JSONRPC_VERSION, "2.0");
515    }
516}