openzeppelin_relayer/models/rpc/
json_rpc.rs

1/// Core JSON-RPC 2.0 types and implementations.
2///
3/// This module contains the fundamental data structures for JSON-RPC 2.0 requests and responses,
4/// including proper ID handling according to the JSON-RPC specification.
5use serde::{Deserialize, Serialize};
6use utoipa::ToSchema;
7
8/// Represents a JSON-RPC 2.0 ID value.
9/// According to the spec, the ID can be a String or Number.
10/// When used in `Option<JsonRpcId>`: Some(id) = actual ID, None = explicit null.
11#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, ToSchema)]
12#[serde(untagged)]
13pub enum JsonRpcId {
14    /// String identifier
15    String(String),
16    /// Numeric identifier (should not contain fractional parts per spec)
17    Number(i64),
18}
19
20impl JsonRpcId {
21    /// Creates a JsonRpcId from a number
22    pub fn number(n: i64) -> Self {
23        JsonRpcId::Number(n)
24    }
25
26    /// Creates a JsonRpcId from a string
27    pub fn string<S: Into<String>>(s: S) -> Self {
28        JsonRpcId::String(s.into())
29    }
30
31    /// Attempts to extract a numeric value from the ID
32    pub fn as_number(&self) -> Option<i64> {
33        match self {
34            JsonRpcId::Number(n) => Some(*n),
35            _ => None,
36        }
37    }
38
39    /// Attempts to extract a string value from the ID
40    pub fn as_string(&self) -> Option<&str> {
41        match self {
42            JsonRpcId::String(s) => Some(s),
43            _ => None,
44        }
45    }
46}
47
48impl From<i64> for JsonRpcId {
49    fn from(n: i64) -> Self {
50        JsonRpcId::Number(n)
51    }
52}
53
54impl From<u64> for JsonRpcId {
55    fn from(n: u64) -> Self {
56        JsonRpcId::Number(n as i64)
57    }
58}
59
60impl From<String> for JsonRpcId {
61    fn from(s: String) -> Self {
62        JsonRpcId::String(s)
63    }
64}
65
66impl From<&str> for JsonRpcId {
67    fn from(s: &str) -> Self {
68        JsonRpcId::String(s.to_string())
69    }
70}
71
72/// JSON-RPC 2.0 Request structure.
73///
74/// Represents a JSON-RPC request with proper ID handling:
75/// - `Some(JsonRpcId)` = request with ID
76/// - `None` = explicit null ID or notification
77#[derive(Serialize, Deserialize, ToSchema)]
78pub struct JsonRpcRequest<T> {
79    pub jsonrpc: String,
80    #[serde(flatten)]
81    pub params: T,
82    #[serde(default)]
83    pub id: Option<JsonRpcId>,
84}
85
86/// JSON-RPC 2.0 Response structure.
87///
88/// Represents a JSON-RPC response that can contain either a result or an error.
89#[derive(Debug, Serialize, Deserialize, ToSchema)]
90pub struct JsonRpcResponse<T> {
91    pub jsonrpc: String,
92    #[serde(skip_serializing_if = "Option::is_none")]
93    #[schema(nullable = false)]
94    pub result: Option<T>,
95    #[serde(skip_serializing_if = "Option::is_none")]
96    #[schema(nullable = false)]
97    pub error: Option<JsonRpcError>,
98    #[serde(skip_serializing_if = "Option::is_none")]
99    #[schema(nullable = false)]
100    pub id: Option<JsonRpcId>,
101}
102
103impl<T> JsonRpcResponse<T> {
104    /// Creates a new successful JSON-RPC response with the given result and id.
105    ///
106    /// # Arguments
107    /// * `id` - The request identifier (can be None for null)
108    /// * `result` - The result value to include in the response
109    ///
110    /// # Returns
111    /// A new JsonRpcResponse with the specified result
112    pub fn result(id: Option<JsonRpcId>, result: T) -> Self {
113        Self {
114            jsonrpc: "2.0".to_string(),
115            result: Some(result),
116            error: None,
117            id,
118        }
119    }
120
121    /// Creates a new error JSON-RPC response.
122    ///
123    /// # Arguments
124    /// * `code` - The error code
125    /// * `message` - The error message
126    /// * `description` - The error description
127    ///
128    /// # Returns
129    /// A new JsonRpcResponse with the specified error
130    pub fn error(code: i32, message: &str, description: &str) -> Self {
131        Self {
132            jsonrpc: "2.0".to_string(),
133            result: None,
134            error: Some(JsonRpcError {
135                code,
136                message: message.to_string(),
137                description: description.to_string(),
138            }),
139            id: None,
140        }
141    }
142}
143
144/// JSON-RPC 2.0 Error structure.
145///
146/// Represents an error in a JSON-RPC response.
147#[derive(Debug, Serialize, Deserialize, ToSchema)]
148pub struct JsonRpcError {
149    pub code: i32,
150    pub message: String,
151    pub description: String,
152}
153
154#[cfg(test)]
155mod tests {
156    use super::*;
157    use serde_json::json;
158
159    #[test]
160    fn test_json_rpc_id_serialization() {
161        // Test Number variant
162        let id_number = JsonRpcId::Number(42);
163        let serialized = serde_json::to_value(&id_number).unwrap();
164        assert_eq!(serialized, json!(42));
165
166        // Test String variant
167        let id_string = JsonRpcId::String("test-id".to_string());
168        let serialized = serde_json::to_value(&id_string).unwrap();
169        assert_eq!(serialized, json!("test-id"));
170    }
171
172    #[test]
173    fn test_json_rpc_id_deserialization() {
174        // Test Number deserialization
175        let id: JsonRpcId = serde_json::from_value(json!(123)).unwrap();
176        assert_eq!(id, JsonRpcId::Number(123));
177
178        // Test String deserialization
179        let id: JsonRpcId = serde_json::from_value(json!("example-id")).unwrap();
180        assert_eq!(id, JsonRpcId::String("example-id".to_string()));
181    }
182
183    #[test]
184    fn test_json_rpc_id_helper_methods() {
185        let number_id = JsonRpcId::Number(100);
186        assert_eq!(number_id.as_number(), Some(100));
187        assert_eq!(number_id.as_string(), None);
188
189        let string_id = JsonRpcId::String("hello".to_string());
190        assert_eq!(string_id.as_number(), None);
191        assert_eq!(string_id.as_string(), Some("hello"));
192    }
193
194    #[test]
195    fn test_json_rpc_id_from_implementations() {
196        // Test From<i64>
197        let id: JsonRpcId = 42i64.into();
198        assert_eq!(id, JsonRpcId::Number(42));
199
200        // Test From<u64>
201        let id: JsonRpcId = 42u64.into();
202        assert_eq!(id, JsonRpcId::Number(42));
203
204        // Test From<String>
205        let id: JsonRpcId = "test".to_string().into();
206        assert_eq!(id, JsonRpcId::String("test".to_string()));
207
208        // Test From<&str>
209        let id: JsonRpcId = "test".into();
210        assert_eq!(id, JsonRpcId::String("test".to_string()));
211    }
212
213    #[test]
214    fn test_json_rpc_request_with_different_id_types() {
215        // Test with number ID
216        let request_with_number = JsonRpcRequest {
217            jsonrpc: "2.0".to_string(),
218            params: json!({"method": "test"}),
219            id: Some(JsonRpcId::Number(1)),
220        };
221        let serialized = serde_json::to_value(&request_with_number).unwrap();
222        assert_eq!(serialized["id"], json!(1));
223
224        // Test with string ID
225        let request_with_string = JsonRpcRequest {
226            jsonrpc: "2.0".to_string(),
227            params: json!({"method": "test"}),
228            id: Some(JsonRpcId::String("abc123".to_string())),
229        };
230        let serialized = serde_json::to_value(&request_with_string).unwrap();
231        assert_eq!(serialized["id"], json!("abc123"));
232
233        // Test with None (explicit null ID)
234        let request_with_null = JsonRpcRequest {
235            jsonrpc: "2.0".to_string(),
236            params: json!({"method": "test"}),
237            id: None,
238        };
239        let serialized = serde_json::to_value(&request_with_null).unwrap();
240        assert_eq!(serialized["id"], json!(null));
241        assert!(serialized.as_object().unwrap().contains_key("id")); // Field is present with null value
242    }
243
244    #[test]
245    fn test_json_rpc_request_deserialization_with_option() {
246        // Test deserializing with number ID
247        let json_with_number = json!({
248            "jsonrpc": "2.0",
249            "method": "test",
250            "id": 42
251        });
252        let request: JsonRpcRequest<serde_json::Value> =
253            serde_json::from_value(json_with_number).unwrap();
254        assert_eq!(request.id, Some(JsonRpcId::Number(42)));
255
256        // Test deserializing with string ID
257        let json_with_string = json!({
258            "jsonrpc": "2.0",
259            "method": "test",
260            "id": "req-123"
261        });
262        let request: JsonRpcRequest<serde_json::Value> =
263            serde_json::from_value(json_with_string).unwrap();
264        assert_eq!(request.id, Some(JsonRpcId::String("req-123".to_string())));
265
266        // Test deserializing with explicit null ID
267        let json_with_null = json!({
268            "jsonrpc": "2.0",
269            "method": "test",
270            "id": null
271        });
272        let request: JsonRpcRequest<serde_json::Value> =
273            serde_json::from_value(json_with_null).unwrap();
274        assert_eq!(request.id, None);
275
276        // Test deserializing without ID field (notification)
277        let json_notification = json!({
278            "jsonrpc": "2.0",
279            "method": "test"
280        });
281        let request: JsonRpcRequest<serde_json::Value> =
282            serde_json::from_value(json_notification).unwrap();
283        assert_eq!(request.id, None);
284    }
285
286    #[test]
287    fn test_option_json_rpc_id_serialization() {
288        // Test Some(Number)
289        let id_some_number = Some(JsonRpcId::Number(100));
290        let serialized = serde_json::to_value(&id_some_number).unwrap();
291        assert_eq!(serialized, json!(100));
292
293        // Test Some(String)
294        let id_some_string = Some(JsonRpcId::String("test".to_string()));
295        let serialized = serde_json::to_value(&id_some_string).unwrap();
296        assert_eq!(serialized, json!("test"));
297
298        // Test None
299        let id_none: Option<JsonRpcId> = None;
300        let serialized = serde_json::to_value(&id_none).unwrap();
301        assert_eq!(serialized, json!(null));
302    }
303
304    #[test]
305    fn test_json_rpc_response_with_option_id() {
306        // Test with Some(Number) ID
307        let response_with_number =
308            JsonRpcResponse::result(Some(JsonRpcId::Number(42)), json!("success"));
309        assert_eq!(response_with_number.id, Some(JsonRpcId::Number(42)));
310
311        // Test with Some(String) ID
312        let response_with_string = JsonRpcResponse::result(
313            Some(JsonRpcId::String("req-123".to_string())),
314            json!("success"),
315        );
316        assert_eq!(
317            response_with_string.id,
318            Some(JsonRpcId::String("req-123".to_string()))
319        );
320
321        // Test with None ID (null)
322        let response_with_null = JsonRpcResponse::result(None, json!("success"));
323        assert_eq!(response_with_null.id, None);
324    }
325}