Skip to main content

ethrpc_rs/
jsonrpc.rs

1//! JSON-RPC 2.0 request, response, and error types.
2
3use std::fmt;
4use std::sync::atomic::{AtomicU64, Ordering};
5
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8
9/// Monotonic request id counter, shared across all requests.
10static RPC_ID: AtomicU64 = AtomicU64::new(0);
11
12/// Returns the next request id. The first id is 1, matching the Go
13/// implementation's `atomic.AddUint64(&rpcId, 1)`.
14fn next_id() -> u64 {
15    RPC_ID.fetch_add(1, Ordering::SeqCst) + 1
16}
17
18/// A JSON-RPC 2.0 request object.
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct Request {
21    /// Always `"2.0"`.
22    pub jsonrpc: String,
23    /// The method name.
24    pub method: String,
25    /// Either a positional array or a named-parameter object.
26    pub params: Value,
27    /// The request id.
28    pub id: Value,
29}
30
31impl Request {
32    /// Builds a new request with positional parameters, fit to use with
33    /// [`Rpc::send`](crate::Rpc::send). An empty `params` is encoded as `[]`,
34    /// never `null`.
35    pub fn new(method: impl Into<String>, params: Vec<Value>) -> Request {
36        Request {
37            jsonrpc: "2.0".to_string(),
38            method: method.into(),
39            params: Value::Array(params),
40            id: Value::from(next_id()),
41        }
42    }
43
44    /// Builds a new request with named parameters.
45    pub fn with_map(method: impl Into<String>, params: serde_json::Map<String, Value>) -> Request {
46        Request {
47            jsonrpc: "2.0".to_string(),
48            method: method.into(),
49            params: Value::Object(params),
50            id: Value::from(next_id()),
51        }
52    }
53
54    /// Wraps an error into a JSON-RPC response carrying this request's id. If
55    /// `e` is itself a JSON-RPC error it is preserved verbatim, otherwise it is
56    /// wrapped with the internal-error code `-32603`.
57    pub(crate) fn make_error(&self, e: &crate::Error) -> ResponseIntf {
58        let error = match e {
59            crate::Error::Rpc(eo) => eo.clone(),
60            other => ErrorObject {
61                code: -32603,
62                message: other.to_string(),
63                data: None,
64            },
65        };
66        ResponseIntf {
67            jsonrpc: "2.0".to_string(),
68            result: None,
69            error: Some(error),
70            id: self.id.clone(),
71        }
72    }
73}
74
75/// A JSON-RPC 2.0 response with a raw JSON result.
76#[derive(Debug, Clone, Deserialize)]
77pub struct Response {
78    /// Always `"2.0"`.
79    #[allow(dead_code)]
80    pub jsonrpc: String,
81    /// The result value, if any.
82    #[serde(default)]
83    pub result: Value,
84    /// The error object, if the call failed.
85    #[serde(default)]
86    pub error: Option<ErrorObject>,
87    /// The request id echoed back.
88    #[serde(default)]
89    pub id: Value,
90}
91
92/// A JSON-RPC 2.0 response where `result` is an arbitrary serializable value,
93/// used when encoding a locally-produced (overridden) response.
94#[derive(Debug, Clone, Serialize)]
95pub struct ResponseIntf {
96    /// Always `"2.0"`.
97    pub jsonrpc: String,
98    /// The result value, omitted when `None`.
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub result: Option<Value>,
101    /// The error object, omitted when `None`.
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub error: Option<ErrorObject>,
104    /// The request id.
105    pub id: Value,
106}
107
108/// A JSON-RPC 2.0 error object returned by a server.
109#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct ErrorObject {
111    /// The numeric error code.
112    pub code: i64,
113    /// A human-readable error message.
114    pub message: String,
115    /// Optional structured error data.
116    #[serde(default, skip_serializing_if = "Option::is_none")]
117    pub data: Option<Value>,
118}
119
120impl fmt::Display for ErrorObject {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(f, "jsonrpc error {}: {}", self.code, self.message)
123    }
124}
125
126impl std::error::Error for ErrorObject {}
127
128#[cfg(test)]
129mod tests {
130    use super::*;
131    use serde_json::json;
132
133    #[test]
134    fn new_request_empty_params() {
135        let req = Request::new("eth_blockNumber", vec![]);
136        assert_eq!(req.jsonrpc, "2.0");
137        assert_eq!(req.method, "eth_blockNumber");
138        assert_eq!(req.params, json!([]));
139        // Id should be a non-zero number.
140        assert!(req.id.as_u64().unwrap() > 0);
141    }
142
143    #[test]
144    fn new_request_with_params() {
145        let req = Request::new("eth_getBalance", vec![json!("0xdead"), json!("latest")]);
146        assert_eq!(req.params, json!(["0xdead", "latest"]));
147    }
148
149    #[test]
150    fn new_request_map() {
151        let mut m = serde_json::Map::new();
152        m.insert("to".to_string(), json!("0xdead"));
153        let req = Request::with_map("eth_call", m);
154        assert_eq!(req.method, "eth_call");
155        assert_eq!(req.params["to"], json!("0xdead"));
156    }
157
158    #[test]
159    fn ids_are_monotonic() {
160        let a = Request::new("a", vec![]).id.as_u64().unwrap();
161        let b = Request::new("b", vec![]).id.as_u64().unwrap();
162        assert!(b > a);
163    }
164
165    #[test]
166    fn error_object_display() {
167        let e = ErrorObject {
168            code: -32601,
169            message: "Method not found".to_string(),
170            data: None,
171        };
172        assert_eq!(e.to_string(), "jsonrpc error -32601: Method not found");
173    }
174
175    #[test]
176    fn make_error_generic_and_passthrough() {
177        let req = Request::new("eth_test", vec![]);
178        let resp = req.make_error(&crate::Error::Other("something broke".to_string()));
179        let eo = resp.error.unwrap();
180        assert_eq!(eo.code, -32603);
181        assert_eq!(eo.message, "something broke");
182
183        let original = ErrorObject {
184            code: -32601,
185            message: "Method not found".to_string(),
186            data: None,
187        };
188        let resp = req.make_error(&crate::Error::Rpc(original));
189        assert_eq!(resp.error.unwrap().code, -32601);
190    }
191}