Skip to main content

act_types/
jsonrpc.rs

1//! JSON-RPC 2.0 wire-format types.
2//!
3//! Protocol-agnostic JSON-RPC envelope types used by MCP and potentially
4//! other JSON-RPC-based transports.
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use serde_with::skip_serializing_none;
9
10/// JSON-RPC protocol version. Always `"2.0"`.
11///
12/// Single-variant enum: zero-size, always serializes as `"2.0"`,
13/// rejects other values on deserialization.
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
15pub enum Version {
16    #[default]
17    #[serde(rename = "2.0")]
18    V2,
19}
20
21/// A JSON-RPC 2.0 request.
22#[skip_serializing_none]
23#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct Request {
25    #[serde(default)]
26    pub jsonrpc: Version,
27    #[serde(default)]
28    pub id: Option<Value>,
29    pub method: String,
30    #[serde(default)]
31    pub params: Option<Value>,
32}
33
34/// A JSON-RPC 2.0 response.
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct Response {
37    #[serde(default)]
38    pub jsonrpc: Version,
39    pub id: Value,
40    #[serde(flatten)]
41    pub body: Body,
42}
43
44/// The result or error payload of a JSON-RPC response.
45///
46/// Uses `#[serde(flatten)]` with the response so exactly one of
47/// `"result"` or `"error"` appears in the JSON.
48#[derive(Debug, Clone, Serialize, Deserialize)]
49#[serde(rename_all = "lowercase")]
50pub enum Body {
51    Result(Value),
52    Error(Error),
53}
54
55/// A JSON-RPC error object.
56#[skip_serializing_none]
57#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct Error {
59    pub code: i32,
60    pub message: String,
61    #[serde(default)]
62    pub data: Option<Value>,
63}
64
65impl Response {
66    /// Create a successful response.
67    pub fn success(id: Value, result: Value) -> Self {
68        Self {
69            jsonrpc: Version::V2,
70            id,
71            body: Body::Result(result),
72        }
73    }
74
75    /// Create an error response.
76    pub fn error(id: Value, code: i32, message: impl Into<String>) -> Self {
77        Self {
78            jsonrpc: Version::V2,
79            id,
80            body: Body::Error(Error {
81                code,
82                message: message.into(),
83                data: None,
84            }),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::*;
92    use serde_json::json;
93
94    #[test]
95    fn response_success_roundtrip() {
96        let resp = Response::success(json!(1), json!({"tools": []}));
97        let json = serde_json::to_value(&resp).unwrap();
98        assert_eq!(json["jsonrpc"], "2.0");
99        assert_eq!(json["id"], 1);
100        assert!(json.get("result").is_some());
101        assert!(json.get("error").is_none());
102    }
103
104    #[test]
105    fn response_error_roundtrip() {
106        let resp = Response::error(json!(1), -32601, "not found");
107        let json = serde_json::to_value(&resp).unwrap();
108        assert_eq!(json["error"]["code"], -32601);
109        assert_eq!(json["error"]["message"], "not found");
110        assert!(json.get("result").is_none());
111    }
112
113    #[test]
114    fn error_omits_null_data() {
115        let resp = Response::error(json!(1), -32600, "bad");
116        let json = serde_json::to_string(&resp).unwrap();
117        assert!(!json.contains("\"data\""));
118    }
119
120    #[test]
121    fn request_omits_null_fields() {
122        let req = Request {
123            jsonrpc: Version::V2,
124            id: None,
125            method: "ping".to_string(),
126            params: None,
127        };
128        let json = serde_json::to_string(&req).unwrap();
129        assert!(!json.contains("\"id\""));
130        assert!(!json.contains("\"params\""));
131    }
132
133    #[test]
134    fn version_rejects_invalid() {
135        let result: Result<Version, _> = serde_json::from_value(json!("1.0"));
136        assert!(result.is_err());
137    }
138
139    #[test]
140    fn version_default() {
141        assert_eq!(Version::default(), Version::V2);
142    }
143}