Skip to main content

chainrpc_core/
request.rs

1//! JSON-RPC 2.0 wire types.
2
3use std::sync::atomic::{AtomicU64, Ordering};
4
5use serde::{Deserialize, Serialize};
6use serde_json::Value;
7
8/// Global auto-incrementing request ID counter.
9static NEXT_ID: AtomicU64 = AtomicU64::new(1);
10
11/// JSON-RPC request ID — string, number, or null.
12#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13#[serde(untagged)]
14pub enum RpcId {
15    Number(u64),
16    String(String),
17    Null,
18}
19
20impl RpcId {
21    pub fn number(n: u64) -> Self {
22        Self::Number(n)
23    }
24}
25
26impl std::fmt::Display for RpcId {
27    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28        match self {
29            Self::Number(n) => write!(f, "{n}"),
30            Self::String(s) => write!(f, "{s}"),
31            Self::Null => write!(f, "null"),
32        }
33    }
34}
35
36/// A single JSON-RPC parameter value.
37pub type RpcParam = Value;
38
39/// A JSON-RPC 2.0 request.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct JsonRpcRequest {
42    pub jsonrpc: String,
43    pub method: String,
44    pub params: Vec<RpcParam>,
45    pub id: RpcId,
46}
47
48impl JsonRpcRequest {
49    /// Create a new JSON-RPC 2.0 request with an explicit ID.
50    pub fn new(id: u64, method: impl Into<String>, params: Vec<RpcParam>) -> Self {
51        Self {
52            jsonrpc: "2.0".into(),
53            method: method.into(),
54            params,
55            id: RpcId::Number(id),
56        }
57    }
58
59    /// Create a request with an auto-incrementing ID.
60    pub fn auto(method: impl Into<String>, params: Vec<RpcParam>) -> Self {
61        let id = NEXT_ID.fetch_add(1, Ordering::Relaxed);
62        Self::new(id, method, params)
63    }
64}
65
66/// A JSON-RPC 2.0 error object.
67#[derive(Debug, Clone, Serialize, Deserialize)]
68pub struct JsonRpcError {
69    pub code: i64,
70    pub message: String,
71    #[serde(skip_serializing_if = "Option::is_none")]
72    pub data: Option<Value>,
73}
74
75impl std::fmt::Display for JsonRpcError {
76    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
77        write!(f, "JSON-RPC error {}: {}", self.code, self.message)
78    }
79}
80
81/// A JSON-RPC 2.0 response.
82#[derive(Debug, Clone, Serialize, Deserialize)]
83pub struct JsonRpcResponse {
84    pub jsonrpc: String,
85    pub id: RpcId,
86    #[serde(skip_serializing_if = "Option::is_none")]
87    pub result: Option<Value>,
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub error: Option<JsonRpcError>,
90}
91
92impl JsonRpcResponse {
93    /// Returns `true` if this is a successful response (has result, no error).
94    pub fn is_ok(&self) -> bool {
95        self.error.is_none() && self.result.is_some()
96    }
97
98    /// Unwrap the result value or return an error.
99    pub fn into_result(self) -> Result<Value, JsonRpcError> {
100        if let Some(err) = self.error {
101            Err(err)
102        } else {
103            Ok(self.result.unwrap_or(Value::Null))
104        }
105    }
106}
107
108#[cfg(test)]
109mod tests {
110    use super::*;
111
112    #[test]
113    fn request_serialization() {
114        let req = JsonRpcRequest::new(1, "eth_blockNumber", vec![]);
115        let json = serde_json::to_string(&req).unwrap();
116        assert!(json.contains("\"jsonrpc\":\"2.0\""));
117        assert!(json.contains("\"method\":\"eth_blockNumber\""));
118    }
119
120    #[test]
121    fn response_into_result_ok() {
122        let resp = JsonRpcResponse {
123            jsonrpc: "2.0".into(),
124            id: RpcId::Number(1),
125            result: Some(Value::String("0x12345".into())),
126            error: None,
127        };
128        assert!(resp.is_ok());
129        let val = resp.into_result().unwrap();
130        assert_eq!(val, Value::String("0x12345".into()));
131    }
132
133    #[test]
134    fn response_into_result_error() {
135        let resp = JsonRpcResponse {
136            jsonrpc: "2.0".into(),
137            id: RpcId::Number(1),
138            result: None,
139            error: Some(JsonRpcError {
140                code: -32000,
141                message: "execution reverted".into(),
142                data: None,
143            }),
144        };
145        assert!(!resp.is_ok());
146        let err = resp.into_result().unwrap_err();
147        assert_eq!(err.code, -32000);
148    }
149}