riglr_config/
error.rs

1//! Configuration error types
2
3use thiserror::Error;
4
5/// Configuration result type
6pub type ConfigResult<T> = Result<T, ConfigError>;
7
8/// Configuration errors
9#[derive(Debug, Error)]
10pub enum ConfigError {
11    /// Environment variable not found
12    #[error("Missing environment variable: {0}")]
13    MissingEnvVar(String),
14
15    /// Failed to parse environment variables
16    #[error("Environment parsing error: {0}")]
17    EnvParse(String),
18
19    /// Invalid configuration value
20    #[error("Invalid configuration: {0}")]
21    ValidationError(String),
22
23    /// Failed to parse configuration
24    #[error("Failed to parse configuration: {0}")]
25    ParseError(String),
26
27    /// IO error
28    #[error("IO error: {0}")]
29    IoError(String),
30
31    /// Chain not supported
32    #[error("Chain {0} is not supported")]
33    ChainNotSupported(u64),
34
35    /// Provider not configured
36    #[error("Provider {0} is not configured")]
37    ProviderNotConfigured(String),
38
39    /// Configuration already locked
40    #[error("Configuration locked: {0}")]
41    ConfigLocked(String),
42
43    /// Generic error
44    #[error("{0}")]
45    Generic(String),
46}
47
48impl ConfigError {
49    /// Create a validation error
50    pub fn validation<S: Into<String>>(msg: S) -> Self {
51        Self::ValidationError(msg.into())
52    }
53
54    /// Create a parse error
55    pub fn parse<S: Into<String>>(msg: S) -> Self {
56        Self::ParseError(msg.into())
57    }
58
59    /// Create an IO error
60    pub fn io<S: Into<String>>(msg: S) -> Self {
61        Self::IoError(msg.into())
62    }
63
64    /// Create a generic error
65    pub fn generic<S: Into<String>>(msg: S) -> Self {
66        Self::Generic(msg.into())
67    }
68}
69
70impl From<std::io::Error> for ConfigError {
71    fn from(err: std::io::Error) -> Self {
72        Self::IoError(err.to_string())
73    }
74}
75
76impl From<toml::de::Error> for ConfigError {
77    fn from(err: toml::de::Error) -> Self {
78        Self::ParseError(err.to_string())
79    }
80}
81
82impl From<envy::Error> for ConfigError {
83    fn from(err: envy::Error) -> Self {
84        Self::ParseError(err.to_string())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use super::*;
91    use std::io;
92
93    #[test]
94    fn test_config_error_missing_env_var_display() {
95        let error = ConfigError::MissingEnvVar("DATABASE_URL".to_string());
96        assert_eq!(
97            error.to_string(),
98            "Missing environment variable: DATABASE_URL"
99        );
100    }
101
102    #[test]
103    fn test_config_error_validation_error_display() {
104        let error = ConfigError::ValidationError("Invalid port number".to_string());
105        assert_eq!(
106            error.to_string(),
107            "Invalid configuration: Invalid port number"
108        );
109    }
110
111    #[test]
112    fn test_config_error_parse_error_display() {
113        let error = ConfigError::ParseError("Invalid TOML format".to_string());
114        assert_eq!(
115            error.to_string(),
116            "Failed to parse configuration: Invalid TOML format"
117        );
118    }
119
120    #[test]
121    fn test_config_error_io_error_display() {
122        let error = ConfigError::IoError("File not found".to_string());
123        assert_eq!(error.to_string(), "IO error: File not found");
124    }
125
126    #[test]
127    fn test_config_error_chain_not_supported_display() {
128        let error = ConfigError::ChainNotSupported(1337);
129        assert_eq!(error.to_string(), "Chain 1337 is not supported");
130    }
131
132    #[test]
133    fn test_config_error_provider_not_configured_display() {
134        let error = ConfigError::ProviderNotConfigured("RPC".to_string());
135        assert_eq!(error.to_string(), "Provider RPC is not configured");
136    }
137
138    #[test]
139    fn test_config_error_generic_display() {
140        let error = ConfigError::Generic("Something went wrong".to_string());
141        assert_eq!(error.to_string(), "Something went wrong");
142    }
143
144    #[test]
145    fn test_validation_constructor_with_string() {
146        let error = ConfigError::validation("Invalid value".to_string());
147        match error {
148            ConfigError::ValidationError(msg) => assert_eq!(msg, "Invalid value"),
149            _ => panic!("Expected ValidationError"),
150        }
151    }
152
153    #[test]
154    fn test_validation_constructor_with_str() {
155        let error = ConfigError::validation("Invalid value");
156        match error {
157            ConfigError::ValidationError(msg) => assert_eq!(msg, "Invalid value"),
158            _ => panic!("Expected ValidationError"),
159        }
160    }
161
162    #[test]
163    fn test_parse_constructor_with_string() {
164        let error = ConfigError::parse("Parse failed".to_string());
165        match error {
166            ConfigError::ParseError(msg) => assert_eq!(msg, "Parse failed"),
167            _ => panic!("Expected ParseError"),
168        }
169    }
170
171    #[test]
172    fn test_parse_constructor_with_str() {
173        let error = ConfigError::parse("Parse failed");
174        match error {
175            ConfigError::ParseError(msg) => assert_eq!(msg, "Parse failed"),
176            _ => panic!("Expected ParseError"),
177        }
178    }
179
180    #[test]
181    fn test_io_constructor_with_string() {
182        let error = ConfigError::io("IO failed".to_string());
183        match error {
184            ConfigError::IoError(msg) => assert_eq!(msg, "IO failed"),
185            _ => panic!("Expected IoError"),
186        }
187    }
188
189    #[test]
190    fn test_io_constructor_with_str() {
191        let error = ConfigError::io("IO failed");
192        match error {
193            ConfigError::IoError(msg) => assert_eq!(msg, "IO failed"),
194            _ => panic!("Expected IoError"),
195        }
196    }
197
198    #[test]
199    fn test_generic_constructor_with_string() {
200        let error = ConfigError::generic("Generic error".to_string());
201        match error {
202            ConfigError::Generic(msg) => assert_eq!(msg, "Generic error"),
203            _ => panic!("Expected Generic"),
204        }
205    }
206
207    #[test]
208    fn test_generic_constructor_with_str() {
209        let error = ConfigError::generic("Generic error");
210        match error {
211            ConfigError::Generic(msg) => assert_eq!(msg, "Generic error"),
212            _ => panic!("Expected Generic"),
213        }
214    }
215
216    #[test]
217    fn test_from_std_io_error() {
218        let io_error = io::Error::new(io::ErrorKind::NotFound, "File not found");
219        let config_error: ConfigError = io_error.into();
220
221        match config_error {
222            ConfigError::IoError(msg) => assert!(msg.contains("File not found")),
223            _ => panic!("Expected IoError"),
224        }
225    }
226
227    #[test]
228    fn test_from_toml_de_error() {
229        let toml_content = "invalid = toml = content";
230        let toml_error = toml::from_str::<toml::Value>(toml_content).unwrap_err();
231        let config_error: ConfigError = toml_error.into();
232
233        match config_error {
234            ConfigError::ParseError(_) => {}
235            _ => panic!("Expected ParseError"),
236        }
237    }
238
239    #[test]
240    fn test_from_envy_error() {
241        // Create an envy error by trying to deserialize an empty environment
242        const TEST_REQUIRED_VAR: &str = "TEST_REQUIRED_VAR";
243        #[allow(clippy::disallowed_methods)]
244        std::env::remove_var(TEST_REQUIRED_VAR);
245
246        #[derive(Debug, serde::Deserialize)]
247        #[allow(dead_code)]
248        struct TestConfig {
249            test_required_var: String,
250        }
251
252        let envy_error = envy::from_env::<TestConfig>().unwrap_err();
253        let config_error: ConfigError = envy_error.into();
254
255        match config_error {
256            ConfigError::ParseError(_) => {}
257            _ => panic!("Expected ParseError"),
258        }
259    }
260
261    #[test]
262    fn test_config_error_debug() {
263        let error = ConfigError::MissingEnvVar("TEST_VAR".to_string());
264        let debug_str = format!("{:?}", error);
265        assert!(debug_str.contains("MissingEnvVar"));
266        assert!(debug_str.contains("TEST_VAR"));
267    }
268
269    #[test]
270    fn test_config_result_type_alias() {
271        let success: ConfigResult<i32> = Ok(42);
272        assert_eq!(success.unwrap(), 42);
273
274        let failure: ConfigResult<i32> = Err(ConfigError::Generic("test".to_string()));
275        assert!(failure.is_err());
276    }
277
278    #[test]
279    fn test_all_error_variants_can_be_constructed() {
280        let _missing_env = ConfigError::MissingEnvVar("VAR".to_string());
281        let _validation = ConfigError::ValidationError("msg".to_string());
282        let _parse = ConfigError::ParseError("msg".to_string());
283        let _io = ConfigError::IoError("msg".to_string());
284        let _chain = ConfigError::ChainNotSupported(1);
285        let _provider = ConfigError::ProviderNotConfigured("provider".to_string());
286        let _generic = ConfigError::Generic("msg".to_string());
287    }
288
289    #[test]
290    fn test_error_equality_and_matching() {
291        let error1 = ConfigError::ChainNotSupported(42);
292        let error2 = ConfigError::ChainNotSupported(42);
293
294        match (&error1, &error2) {
295            (ConfigError::ChainNotSupported(id1), ConfigError::ChainNotSupported(id2)) => {
296                assert_eq!(id1, id2);
297            }
298            _ => panic!("Pattern matching failed"),
299        }
300    }
301
302    #[test]
303    fn test_error_with_empty_strings() {
304        let error = ConfigError::MissingEnvVar("".to_string());
305        assert_eq!(error.to_string(), "Missing environment variable: ");
306
307        let generic_error = ConfigError::generic("");
308        assert_eq!(generic_error.to_string(), "");
309    }
310
311    #[test]
312    fn test_error_with_special_characters() {
313        let error = ConfigError::validation("Invalid: üñíçødé");
314        match error {
315            ConfigError::ValidationError(msg) => assert_eq!(msg, "Invalid: üñíçødé"),
316            _ => panic!("Expected ValidationError"),
317        }
318    }
319
320    #[test]
321    fn test_chain_id_zero() {
322        let error = ConfigError::ChainNotSupported(0);
323        assert_eq!(error.to_string(), "Chain 0 is not supported");
324    }
325
326    #[test]
327    fn test_chain_id_max_value() {
328        let error = ConfigError::ChainNotSupported(u64::MAX);
329        assert_eq!(
330            error.to_string(),
331            format!("Chain {} is not supported", u64::MAX)
332        );
333    }
334}