1use thiserror::Error;
4
5pub type ConfigResult<T> = Result<T, ConfigError>;
7
8#[derive(Debug, Error)]
10pub enum ConfigError {
11 #[error("Missing environment variable: {0}")]
13 MissingEnvVar(String),
14
15 #[error("Environment parsing error: {0}")]
17 EnvParse(String),
18
19 #[error("Invalid configuration: {0}")]
21 ValidationError(String),
22
23 #[error("Failed to parse configuration: {0}")]
25 ParseError(String),
26
27 #[error("IO error: {0}")]
29 IoError(String),
30
31 #[error("Chain {0} is not supported")]
33 ChainNotSupported(u64),
34
35 #[error("Provider {0} is not configured")]
37 ProviderNotConfigured(String),
38
39 #[error("Configuration locked: {0}")]
41 ConfigLocked(String),
42
43 #[error("{0}")]
45 Generic(String),
46}
47
48impl ConfigError {
49 pub fn validation<S: Into<String>>(msg: S) -> Self {
51 Self::ValidationError(msg.into())
52 }
53
54 pub fn parse<S: Into<String>>(msg: S) -> Self {
56 Self::ParseError(msg.into())
57 }
58
59 pub fn io<S: Into<String>>(msg: S) -> Self {
61 Self::IoError(msg.into())
62 }
63
64 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 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}