openzeppelin_relayer/domain/relayer/evm/
rpc_utils.rs

1//! Utilities for EVM JSON-RPC response handling and error mapping.
2//!
3//! This module provides helper functions for creating standardized JSON-RPC 2.0 responses
4//! and mapping internal provider errors to appropriate error codes and messages.
5//!
6//! # Main Features
7//!
8//! - Create success and error responses following JSON-RPC 2.0 specification
9//! - Map provider errors to standardized JSON-RPC error codes
10//! - Handle EVM-specific result types and error formatting
11
12use crate::{
13    models::{EvmRpcResult, NetworkRpcResult},
14    models::{JsonRpcError, JsonRpcId, JsonRpcResponse},
15};
16use serde_json;
17
18/// Creates an error response following the JSON-RPC 2.0 specification.
19///
20/// # Arguments
21///
22/// * `id` - The request ID from the original JSON-RPC request
23/// * `code` - The error code (should follow JSON-RPC error code conventions)
24/// * `message` - A short, human-readable error message
25/// * `description` - A more detailed description of the error
26///
27/// # Returns
28///
29/// Returns a `JsonRpcResponse<NetworkRpcResult>` containing the error details
30/// and no result data.
31pub fn create_error_response(
32    id: Option<JsonRpcId>,
33    code: i32,
34    message: &str,
35    description: &str,
36) -> JsonRpcResponse<NetworkRpcResult> {
37    JsonRpcResponse {
38        id,
39        jsonrpc: "2.0".to_string(),
40        result: None,
41        error: Some(JsonRpcError {
42            code,
43            message: message.to_string(),
44            description: description.to_string(),
45        }),
46    }
47}
48
49/// Creates a success response following the JSON-RPC 2.0 specification.
50///
51/// # Arguments
52///
53/// * `id` - The request ID from the original JSON-RPC request
54/// * `result` - The result data to include in the response as a JSON value
55///
56/// # Returns
57///
58/// Returns a `JsonRpcResponse<NetworkRpcResult>` containing the result data
59/// wrapped in an EVM-specific result type, with no error information.
60pub fn create_success_response(
61    id: Option<JsonRpcId>,
62    result: serde_json::Value,
63) -> JsonRpcResponse<NetworkRpcResult> {
64    JsonRpcResponse {
65        id,
66        jsonrpc: "2.0".to_string(),
67        result: Some(NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(result))),
68        error: None,
69    }
70}
71
72// Re-export error sanitization functions for backward compatibility
73// These functions have been moved to src/utils/error_sanitization.rs
74pub use crate::utils::{map_provider_error, sanitize_error_description};
75
76#[cfg(test)]
77mod tests {
78    use super::*;
79    use crate::models::{OpenZeppelinErrorCodes, RpcErrorCodes};
80    use serde_json::json;
81
82    #[test]
83    fn test_create_error_response_basic() {
84        let response = create_error_response(
85            Some(JsonRpcId::Number(123)),
86            -32602,
87            "Invalid params",
88            "The provided parameters are invalid",
89        );
90
91        assert_eq!(response.id, Some(JsonRpcId::Number(123)));
92        assert_eq!(response.jsonrpc, "2.0");
93        assert!(response.result.is_none());
94        assert!(response.error.is_some());
95
96        let error = response.error.unwrap();
97        assert_eq!(error.code, -32602);
98        assert!(!error.message.is_empty());
99        assert!(!error.description.is_empty());
100    }
101
102    #[test]
103    fn test_create_error_response_zero_id() {
104        let response = create_error_response(
105            Some(JsonRpcId::Number(0)),
106            -32603,
107            "Internal error",
108            "Something went wrong",
109        );
110
111        assert_eq!(response.id, Some(JsonRpcId::Number(0)));
112        assert_eq!(response.jsonrpc, "2.0");
113        assert!(response.result.is_none());
114        assert!(response.error.is_some());
115    }
116
117    #[test]
118    fn test_create_error_response_max_id() {
119        let response = create_error_response(
120            Some(JsonRpcId::Number(u64::MAX as i64)),
121            -32700,
122            "Parse error",
123            "JSON parsing failed",
124        );
125
126        assert_eq!(response.id, Some(JsonRpcId::Number(u64::MAX as i64)));
127        assert_eq!(response.jsonrpc, "2.0");
128        assert!(response.result.is_none());
129        assert!(response.error.is_some());
130    }
131
132    #[test]
133    fn test_create_error_response_empty_message() {
134        let response = create_error_response(
135            Some(JsonRpcId::Number(42)),
136            -32601,
137            "",
138            "Method not found error",
139        );
140
141        assert_eq!(response.id, Some(JsonRpcId::Number(42)));
142        let error = response.error.unwrap();
143        assert!(error.message.is_empty());
144        assert!(!error.description.is_empty());
145    }
146
147    #[test]
148    fn test_create_error_response_empty_description() {
149        let response =
150            create_error_response(Some(JsonRpcId::Number(99)), -32600, "Invalid Request", "");
151
152        assert_eq!(response.id, Some(JsonRpcId::Number(99)));
153        let error = response.error.unwrap();
154        assert!(!error.message.is_empty());
155        assert!(error.description.is_empty());
156    }
157
158    #[test]
159    fn test_create_error_response_preserves_input() {
160        let message = "Error with unicode: 🚨 ñáéíóú";
161        let description = "Description with symbols: @#$%^&*()";
162        let response =
163            create_error_response(Some(JsonRpcId::Number(500)), -33000, message, description);
164
165        let error = response.error.unwrap();
166        assert!(!error.message.is_empty());
167        assert!(!error.description.is_empty());
168        assert_eq!(error.code, -33000);
169    }
170
171    #[test]
172    fn test_create_error_response_long_strings() {
173        let long_message = "a".repeat(1000);
174        let long_description = "b".repeat(2000);
175        let response = create_error_response(
176            Some(JsonRpcId::Number(777)),
177            -33001,
178            &long_message,
179            &long_description,
180        );
181
182        let error = response.error.unwrap();
183        assert_eq!(error.message.len(), 1000);
184        assert_eq!(error.description.len(), 2000);
185    }
186
187    #[test]
188    fn test_create_error_response_custom_openzeppelin_codes() {
189        let test_cases = vec![
190            OpenZeppelinErrorCodes::TIMEOUT,
191            OpenZeppelinErrorCodes::RATE_LIMITED,
192            OpenZeppelinErrorCodes::BAD_GATEWAY,
193            OpenZeppelinErrorCodes::REQUEST_ERROR,
194            OpenZeppelinErrorCodes::NETWORK_CONFIGURATION,
195        ];
196
197        for code in test_cases {
198            let response = create_error_response(
199                Some(JsonRpcId::Number(1)),
200                code,
201                "Test message",
202                "Test description",
203            );
204            let error = response.error.unwrap();
205            assert_eq!(error.code, code);
206            assert!(!error.message.is_empty());
207            assert!(!error.description.is_empty());
208        }
209    }
210
211    #[test]
212    fn test_create_error_response_standard_json_rpc_codes() {
213        let test_cases = vec![
214            RpcErrorCodes::PARSE,
215            RpcErrorCodes::INVALID_REQUEST,
216            RpcErrorCodes::METHOD_NOT_FOUND,
217            RpcErrorCodes::INVALID_PARAMS,
218            RpcErrorCodes::INTERNAL_ERROR,
219        ];
220
221        for code in test_cases {
222            let response = create_error_response(
223                Some(JsonRpcId::Number(1)),
224                code,
225                "Test message",
226                "Test description",
227            );
228            let error = response.error.unwrap();
229            assert_eq!(error.code, code);
230            assert!(!error.message.is_empty());
231            assert!(!error.description.is_empty());
232        }
233    }
234
235    #[test]
236    fn test_create_success_response_basic() {
237        let result_data = json!({
238            "blockNumber": "0x1234",
239            "hash": "0xabcd"
240        });
241
242        let response = create_success_response(Some(JsonRpcId::Number(456)), result_data.clone());
243
244        assert_eq!(response.id, Some(JsonRpcId::Number(456)));
245        assert_eq!(response.jsonrpc, "2.0");
246        assert!(response.error.is_none());
247        assert!(response.result.is_some());
248
249        match response.result.unwrap() {
250            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
251                assert_eq!(value, result_data);
252            }
253            _ => unreachable!("Expected EVM RawRpcResult"),
254        }
255    }
256
257    #[test]
258    fn test_create_success_response_zero_id() {
259        let result_data = json!(null);
260        let response = create_success_response(Some(JsonRpcId::Number(0)), result_data);
261
262        assert_eq!(response.id, Some(JsonRpcId::Number(0)));
263        assert_eq!(response.jsonrpc, "2.0");
264        assert!(response.error.is_none());
265    }
266
267    #[test]
268    fn test_create_success_response_max_id() {
269        let result_data = json!(42);
270        let response =
271            create_success_response(Some(JsonRpcId::Number(u64::MAX as i64)), result_data);
272
273        assert_eq!(response.id, Some(JsonRpcId::Number(u64::MAX as i64)));
274        assert_eq!(response.jsonrpc, "2.0");
275        assert!(response.error.is_none());
276    }
277
278    #[test]
279    fn test_create_success_response_null_result() {
280        let result_data = json!(null);
281        let response = create_success_response(Some(JsonRpcId::Number(100)), result_data.clone());
282
283        match response.result.unwrap() {
284            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
285                assert_eq!(value, result_data);
286                assert!(value.is_null());
287            }
288            _ => unreachable!("Expected EVM RawRpcResult"),
289        }
290    }
291
292    #[test]
293    fn test_create_success_response_boolean_result() {
294        let result_data = json!(true);
295        let response = create_success_response(Some(JsonRpcId::Number(200)), result_data.clone());
296
297        match response.result.unwrap() {
298            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
299                assert_eq!(value, result_data);
300                assert!(value.is_boolean());
301            }
302            _ => unreachable!("Expected EVM RawRpcResult"),
303        }
304    }
305
306    #[test]
307    fn test_create_success_response_number_result() {
308        let result_data = json!(12345);
309        let response = create_success_response(Some(JsonRpcId::Number(300)), result_data.clone());
310
311        match response.result.unwrap() {
312            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
313                assert_eq!(value, result_data);
314                assert!(value.is_number());
315            }
316            _ => unreachable!("Expected EVM RawRpcResult"),
317        }
318    }
319
320    #[test]
321    fn test_create_success_response_string_result() {
322        let result_data = json!("test string");
323        let response = create_success_response(Some(JsonRpcId::Number(400)), result_data.clone());
324
325        match response.result.unwrap() {
326            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
327                assert_eq!(value, result_data);
328                assert!(value.is_string());
329            }
330            _ => unreachable!("Expected EVM RawRpcResult"),
331        }
332    }
333
334    #[test]
335    fn test_create_success_response_array_result() {
336        let result_data = json!([1, 2, 3, "test", true, null]);
337        let response = create_success_response(Some(JsonRpcId::Number(500)), result_data.clone());
338
339        match response.result.unwrap() {
340            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
341                assert_eq!(value, result_data);
342                assert!(value.is_array());
343                assert_eq!(value.as_array().unwrap().len(), 6);
344            }
345            _ => unreachable!("Expected EVM RawRpcResult"),
346        }
347    }
348
349    #[test]
350    fn test_create_success_response_complex_object() {
351        let result_data = json!({
352            "transactions": [
353                {"hash": "0x123", "value": "1000000000000000000"},
354                {"hash": "0x456", "value": "2000000000000000000"}
355            ],
356            "blockNumber": "0x1a2b3c",
357            "timestamp": 1234567890,
358            "gasUsed": "0x5208",
359            "metadata": {
360                "network": "mainnet",
361                "version": "1.0",
362                "features": ["eip1559", "eip2930"]
363            },
364            "isEmpty": false,
365            "nullField": null
366        });
367
368        let response = create_success_response(Some(JsonRpcId::Number(600)), result_data.clone());
369
370        match response.result.unwrap() {
371            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
372                assert_eq!(value, result_data);
373                assert!(value.is_object());
374                assert_eq!(value["blockNumber"], "0x1a2b3c");
375                assert_eq!(value["transactions"].as_array().unwrap().len(), 2);
376                assert!(value["nullField"].is_null());
377            }
378            _ => unreachable!("Expected EVM RawRpcResult"),
379        }
380    }
381
382    #[test]
383    fn test_create_success_response_large_object() {
384        let mut large_object = json!({});
385        let object = large_object.as_object_mut().unwrap();
386
387        // Create a large object with many fields
388        for i in 0..100 {
389            object.insert(format!("field_{i}"), json!(format!("value_{}", i)));
390        }
391
392        let response = create_success_response(Some(JsonRpcId::Number(700)), large_object.clone());
393
394        match response.result.unwrap() {
395            NetworkRpcResult::Evm(EvmRpcResult::RawRpcResult(value)) => {
396                assert_eq!(value, large_object);
397                assert_eq!(value.as_object().unwrap().len(), 100);
398            }
399            _ => unreachable!("Expected EVM RawRpcResult"),
400        }
401    }
402
403    #[test]
404    fn test_integration_error_response_with_mapped_provider_error() {
405        use crate::models::RpcErrorCodes;
406        use crate::services::provider::ProviderError;
407        use crate::utils::map_provider_error;
408
409        let provider_error = ProviderError::InvalidAddress("0xinvalid".to_string());
410        let (error_code, error_message) = map_provider_error(&provider_error);
411
412        let response = create_error_response(
413            Some(JsonRpcId::Number(999)),
414            error_code,
415            error_message,
416            "Invalid address provided",
417        );
418
419        assert_eq!(response.id, Some(JsonRpcId::Number(999)));
420        assert_eq!(response.jsonrpc, "2.0");
421        assert!(response.result.is_none());
422
423        let error = response.error.unwrap();
424        assert_eq!(error.code, RpcErrorCodes::INVALID_PARAMS);
425        assert!(!error.message.is_empty());
426        assert!(!error.description.is_empty());
427    }
428
429    #[test]
430    fn test_integration_all_provider_errors_to_responses() {
431        use crate::models::{OpenZeppelinErrorCodes, RpcErrorCodes};
432        use crate::services::provider::ProviderError;
433        use crate::utils::map_provider_error;
434
435        let test_cases = vec![
436            (
437                ProviderError::InvalidAddress("test".to_string()),
438                RpcErrorCodes::INVALID_PARAMS,
439            ),
440            (
441                ProviderError::NetworkConfiguration("test".to_string()),
442                OpenZeppelinErrorCodes::NETWORK_CONFIGURATION,
443            ),
444            (ProviderError::Timeout, OpenZeppelinErrorCodes::TIMEOUT),
445            (
446                ProviderError::RateLimited,
447                OpenZeppelinErrorCodes::RATE_LIMITED,
448            ),
449            (
450                ProviderError::BadGateway,
451                OpenZeppelinErrorCodes::BAD_GATEWAY,
452            ),
453            (
454                ProviderError::RequestError {
455                    error: "test".to_string(),
456                    status_code: 400,
457                },
458                OpenZeppelinErrorCodes::REQUEST_ERROR,
459            ),
460            (
461                ProviderError::Other("test".to_string()),
462                RpcErrorCodes::INTERNAL_ERROR,
463            ),
464        ];
465
466        for (provider_error, expected_code) in test_cases {
467            let (error_code, error_message) = map_provider_error(&provider_error);
468            let response = create_error_response(
469                Some(JsonRpcId::Number(1)),
470                error_code,
471                error_message,
472                "Test integration",
473            );
474
475            assert_eq!(response.id, Some(JsonRpcId::Number(1)));
476            assert_eq!(response.jsonrpc, "2.0");
477            assert!(response.result.is_none());
478
479            let error = response.error.unwrap();
480            assert_eq!(error.code, expected_code);
481            assert!(!error.message.is_empty());
482        }
483    }
484
485    #[test]
486    fn test_response_structure_consistency() {
487        // Test that both success and error responses have consistent structure
488        let success_response =
489            create_success_response(Some(JsonRpcId::Number(100)), json!({"status": "ok"}));
490        let error_response = create_error_response(
491            Some(JsonRpcId::Number(100)),
492            -32603,
493            "Internal error",
494            "Test error",
495        );
496
497        // Both should have same basic structure
498        assert_eq!(success_response.id, error_response.id);
499        assert_eq!(success_response.jsonrpc, error_response.jsonrpc);
500
501        // Success response should have result, not error
502        assert!(success_response.result.is_some());
503        assert!(success_response.error.is_none());
504
505        // Error response should have error, not result
506        assert!(error_response.result.is_none());
507        assert!(error_response.error.is_some());
508    }
509}