Skip to main content

riglr_evm_common/
error.rs

1//! Error types for EVM operations shared across riglr crates
2//!
3//! This module provides standardized error types that can be used by both
4//! riglr-evm-tools and riglr-cross-chain-tools for consistent error handling.
5
6use thiserror::Error;
7
8/// Error types for EVM operations shared across crates
9#[derive(Debug, Error)]
10pub enum EvmCommonError {
11    /// Invalid EVM address format
12    #[error("Invalid EVM address: {0}")]
13    InvalidAddress(String),
14
15    /// Unsupported or unconfigured chain
16    #[error("Unsupported chain ID: {0}. Configure RPC_URL_{0} environment variable")]
17    UnsupportedChain(u64),
18
19    /// Invalid chain name
20    #[error("Invalid chain name: {0}")]
21    InvalidChainName(String),
22
23    /// RPC provider error
24    #[error("RPC provider error: {0}")]
25    ProviderError(String),
26
27    /// Configuration validation error
28    #[error("Configuration error: {0}")]
29    InvalidConfig(String),
30
31    /// Invalid transaction data
32    #[error("Invalid transaction data: {0}")]
33    InvalidData(String),
34
35    /// Network connection error
36    #[error("Network error: {0}")]
37    NetworkError(String),
38
39    /// Parsing error
40    #[error("Parse error: {0}")]
41    ParseError(String),
42}
43
44impl EvmCommonError {
45    /// Check if this error is retriable (network/temporary issues)
46    pub fn is_retriable(&self) -> bool {
47        matches!(
48            self,
49            EvmCommonError::ProviderError(_) | EvmCommonError::NetworkError(_)
50        )
51    }
52
53    /// Check if this error is permanent (configuration/validation issues)
54    pub fn is_permanent(&self) -> bool {
55        !self.is_retriable()
56    }
57}
58
59/// Result type alias for EVM operations
60pub type EvmResult<T> = Result<T, EvmCommonError>;
61
62#[cfg(test)]
63mod tests {
64    use super::*;
65
66    #[test]
67    fn test_error_classification() {
68        let network_err = EvmCommonError::NetworkError("timeout".to_string());
69        assert!(network_err.is_retriable());
70        assert!(!network_err.is_permanent());
71
72        let config_err = EvmCommonError::InvalidConfig("bad config".to_string());
73        assert!(!config_err.is_retriable());
74        assert!(config_err.is_permanent());
75
76        let address_err = EvmCommonError::InvalidAddress("bad address".to_string());
77        assert!(address_err.is_permanent());
78    }
79
80    #[test]
81    fn test_error_display() {
82        let err = EvmCommonError::UnsupportedChain(999);
83        let msg = err.to_string();
84        assert!(msg.contains("999"));
85        assert!(msg.contains("RPC_URL_999"));
86    }
87
88    #[test]
89    fn test_invalid_address_error_display() {
90        let err = EvmCommonError::InvalidAddress("0xinvalid".to_string());
91        let msg = err.to_string();
92        assert_eq!(msg, "Invalid EVM address: 0xinvalid");
93    }
94
95    #[test]
96    fn test_invalid_address_error_empty_string() {
97        let err = EvmCommonError::InvalidAddress("".to_string());
98        let msg = err.to_string();
99        assert_eq!(msg, "Invalid EVM address: ");
100    }
101
102    #[test]
103    fn test_unsupported_chain_error_display() {
104        let err = EvmCommonError::UnsupportedChain(1);
105        let msg = err.to_string();
106        assert_eq!(
107            msg,
108            "Unsupported chain ID: 1. Configure RPC_URL_1 environment variable"
109        );
110    }
111
112    #[test]
113    fn test_unsupported_chain_error_large_chain_id() {
114        let err = EvmCommonError::UnsupportedChain(u64::MAX);
115        let msg = err.to_string();
116        assert!(msg.contains(&u64::MAX.to_string()));
117        assert!(msg.contains(&format!("RPC_URL_{}", u64::MAX)));
118    }
119
120    #[test]
121    fn test_invalid_chain_name_error_display() {
122        let err = EvmCommonError::InvalidChainName("unknown_chain".to_string());
123        let msg = err.to_string();
124        assert_eq!(msg, "Invalid chain name: unknown_chain");
125    }
126
127    #[test]
128    fn test_invalid_chain_name_error_empty_string() {
129        let err = EvmCommonError::InvalidChainName("".to_string());
130        let msg = err.to_string();
131        assert_eq!(msg, "Invalid chain name: ");
132    }
133
134    #[test]
135    fn test_provider_error_display() {
136        let err = EvmCommonError::ProviderError("connection failed".to_string());
137        let msg = err.to_string();
138        assert_eq!(msg, "RPC provider error: connection failed");
139    }
140
141    #[test]
142    fn test_provider_error_empty_string() {
143        let err = EvmCommonError::ProviderError("".to_string());
144        let msg = err.to_string();
145        assert_eq!(msg, "RPC provider error: ");
146    }
147
148    #[test]
149    fn test_invalid_config_error_display() {
150        let err = EvmCommonError::InvalidConfig("missing key".to_string());
151        let msg = err.to_string();
152        assert_eq!(msg, "Configuration error: missing key");
153    }
154
155    #[test]
156    fn test_invalid_config_error_empty_string() {
157        let err = EvmCommonError::InvalidConfig("".to_string());
158        let msg = err.to_string();
159        assert_eq!(msg, "Configuration error: ");
160    }
161
162    #[test]
163    fn test_invalid_data_error_display() {
164        let err = EvmCommonError::InvalidData("malformed hex".to_string());
165        let msg = err.to_string();
166        assert_eq!(msg, "Invalid transaction data: malformed hex");
167    }
168
169    #[test]
170    fn test_invalid_data_error_empty_string() {
171        let err = EvmCommonError::InvalidData("".to_string());
172        let msg = err.to_string();
173        assert_eq!(msg, "Invalid transaction data: ");
174    }
175
176    #[test]
177    fn test_network_error_display() {
178        let err = EvmCommonError::NetworkError("timeout".to_string());
179        let msg = err.to_string();
180        assert_eq!(msg, "Network error: timeout");
181    }
182
183    #[test]
184    fn test_network_error_empty_string() {
185        let err = EvmCommonError::NetworkError("".to_string());
186        let msg = err.to_string();
187        assert_eq!(msg, "Network error: ");
188    }
189
190    #[test]
191    fn test_parse_error_display() {
192        let err = EvmCommonError::ParseError("invalid format".to_string());
193        let msg = err.to_string();
194        assert_eq!(msg, "Parse error: invalid format");
195    }
196
197    #[test]
198    fn test_parse_error_empty_string() {
199        let err = EvmCommonError::ParseError("".to_string());
200        let msg = err.to_string();
201        assert_eq!(msg, "Parse error: ");
202    }
203
204    #[test]
205    fn test_provider_error_is_retriable() {
206        let err = EvmCommonError::ProviderError("connection timeout".to_string());
207        assert!(err.is_retriable());
208        assert!(!err.is_permanent());
209    }
210
211    #[test]
212    fn test_network_error_is_retriable() {
213        let err = EvmCommonError::NetworkError("dns resolution failed".to_string());
214        assert!(err.is_retriable());
215        assert!(!err.is_permanent());
216    }
217
218    #[test]
219    fn test_all_permanent_errors_are_not_retriable() {
220        let errors = vec![
221            EvmCommonError::InvalidAddress("0xinvalid".to_string()),
222            EvmCommonError::UnsupportedChain(999),
223            EvmCommonError::InvalidChainName("unknown".to_string()),
224            EvmCommonError::InvalidConfig("bad config".to_string()),
225            EvmCommonError::InvalidData("bad data".to_string()),
226            EvmCommonError::ParseError("parse failed".to_string()),
227        ];
228
229        for err in errors {
230            assert!(!err.is_retriable());
231            assert!(err.is_permanent());
232        }
233    }
234
235    #[test]
236    fn test_evm_result_type_alias_ok() {
237        let result: EvmResult<String> = Ok("success".to_string());
238        assert!(result.is_ok());
239        assert_eq!(result.unwrap(), "success");
240    }
241
242    #[test]
243    fn test_evm_result_type_alias_err() {
244        let result: EvmResult<String> =
245            Err(EvmCommonError::InvalidAddress("0xinvalid".to_string()));
246        assert!(result.is_err());
247        match result.unwrap_err() {
248            EvmCommonError::InvalidAddress(addr) => assert_eq!(addr, "0xinvalid"),
249            _ => panic!("Expected InvalidAddress error"),
250        }
251    }
252
253    #[test]
254    fn test_error_debug_trait() {
255        let err = EvmCommonError::InvalidAddress("0xinvalid".to_string());
256        let debug_str = format!("{:?}", err);
257        assert!(debug_str.contains("InvalidAddress"));
258        assert!(debug_str.contains("0xinvalid"));
259    }
260}