Skip to main content

cfg_rs/
err.rs

1use crate::*;
2use std::error::Error;
3use std::fmt::Display;
4use std::path::PathBuf;
5
6/// Configuration Error.
7#[derive(Debug)]
8pub enum ConfigError {
9    /// Config not found.
10    ConfigNotFound(String),
11    /// Config not found when parsing placeholder.
12    ConfigRecursiveNotFound(String),
13    /// Config type mismatch.
14    ConfigTypeMismatch(String, &'static str, &'static str),
15    /// Config parse error.
16    ConfigParseError(String, String),
17    /// Config recursively parsed.
18    ConfigRecursiveError(String),
19    /// Config file not exists.
20    ConfigFileNotExists(PathBuf),
21    /// Config file not supported.
22    ConfigFileNotSupported(PathBuf),
23    /// Ref value recursive error.
24    RefValueRecursiveError,
25    /// Too many instances.
26    TooManyInstances(usize),
27    /// Lock failed.
28    LockPoisoned,
29    /// Config parse error with other error.
30    ConfigCause(Box<dyn Error + 'static>),
31}
32
33impl ConfigError {
34    #[inline]
35    /// Creates a `ConfigError` from another error type.
36    pub fn from_cause<E: Error + 'static>(e: E) -> Self {
37        ConfigError::ConfigCause(Box::new(e))
38    }
39}
40
41impl Error for ConfigError {}
42
43impl Display for ConfigError {
44    #[inline]
45    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
46        match self {
47            ConfigError::ConfigNotFound(key) => {
48                write!(f, "Configuration not found: {}", key)
49            }
50            ConfigError::ConfigRecursiveNotFound(key) => {
51                write!(f, "Configuration recursive not found: {}", key)
52            }
53            ConfigError::ConfigTypeMismatch(key, expected, found) => {
54                write!(
55                    f,
56                    "Configuration type mismatch for key '{}': expected {}, found {}",
57                    key, expected, found
58                )
59            }
60            ConfigError::ConfigParseError(key, msg) => {
61                write!(f, "Configuration parse error for key '{}': {}", key, msg)
62            }
63            ConfigError::ConfigRecursiveError(key) => {
64                write!(f, "Configuration recursive error for key '{}'", key)
65            }
66            ConfigError::ConfigFileNotExists(path) => {
67                write!(f, "Configuration file does not exist: {:?}", path)
68            }
69            ConfigError::ConfigFileNotSupported(path) => {
70                write!(f, "Configuration file not supported: {:?}", path)
71            }
72            ConfigError::RefValueRecursiveError => {
73                write!(f, "Reference value recursive error")
74            }
75            ConfigError::TooManyInstances(count) => {
76                write!(f, "Too many instances: {}", count)
77            }
78            ConfigError::LockPoisoned => {
79                write!(f, "Lock poisoned")
80            }
81            ConfigError::ConfigCause(e) => {
82                write!(f, "Configuration error caused by: {}", e)
83            }
84        }
85    }
86}
87
88impl ConfigError {
89    #[inline]
90    pub(crate) fn try_lock_err<T>(v: TryLockError<T>) -> Option<Self> {
91        match v {
92            TryLockError::WouldBlock => None,
93            TryLockError::Poisoned(e) => Some(Self::lock_err(e)),
94        }
95    }
96
97    #[inline]
98    pub(crate) fn lock_err<T>(_e: PoisonError<T>) -> Self {
99        ConfigError::LockPoisoned
100    }
101}
102
103pub(crate) trait ConfigLock<'a, T> {
104    fn lock_c(&'a self) -> Result<MutexGuard<'a, T>, ConfigError>;
105
106    fn try_lock_c(&'a self) -> Result<Option<MutexGuard<'a, T>>, ConfigError>;
107}
108
109impl<'a, T> ConfigLock<'a, T> for Mutex<T> {
110    #[inline]
111    fn lock_c(&'a self) -> Result<MutexGuard<'a, T>, ConfigError> {
112        self.lock().map_err(ConfigError::lock_err)
113    }
114
115    #[inline]
116    fn try_lock_c(&'a self) -> Result<Option<MutexGuard<'a, T>>, ConfigError> {
117        let v = self.try_lock().map_err(ConfigError::try_lock_err);
118        match v {
119            Ok(ok) => Ok(Some(ok)),
120            Err(Some(e)) => Err(e),
121            _ => Ok(None),
122        }
123    }
124}
125
126#[cfg_attr(coverage_nightly, coverage(off))]
127#[cfg(test)]
128mod tests {
129    use super::*;
130    use std::sync::{Arc, Mutex};
131    use std::thread;
132    use std::time::Duration;
133
134    #[test]
135    fn display_config_not_found() {
136        let e = ConfigError::ConfigNotFound("db.url".into());
137        assert_eq!(format!("{}", e), "Configuration not found: db.url");
138    }
139
140    #[test]
141    fn display_config_recursive_not_found() {
142        let e = ConfigError::ConfigRecursiveNotFound("${missing}".into());
143        assert_eq!(
144            format!("{}", e),
145            "Configuration recursive not found: ${missing}"
146        );
147    }
148
149    #[test]
150    fn display_config_type_mismatch() {
151        let e = ConfigError::ConfigTypeMismatch("app.port".into(), "u16", "String");
152        assert_eq!(
153            format!("{}", e),
154            "Configuration type mismatch for key 'app.port': expected u16, found String"
155        );
156    }
157
158    #[test]
159    fn display_config_parse_error() {
160        let e = ConfigError::ConfigParseError("app.timeout".into(), "invalid number".into());
161        assert_eq!(
162            format!("{}", e),
163            "Configuration parse error for key 'app.timeout': invalid number"
164        );
165    }
166
167    #[test]
168    fn display_config_recursive_error() {
169        let e = ConfigError::ConfigRecursiveError("a -> b -> a".into());
170        assert_eq!(
171            format!("{}", e),
172            "Configuration recursive error for key 'a -> b -> a'"
173        );
174    }
175
176    #[test]
177    fn display_config_file_not_exists_contains_path() {
178        let p = PathBuf::from("/tmp/file.toml");
179        let s = format!("{}", ConfigError::ConfigFileNotExists(p.clone()));
180        assert!(s.starts_with("Configuration file does not exist: "));
181        // Be tolerant to potential platform/debug formatting differences by checking containment.
182        assert!(s.contains("/tmp/file.toml"), "{}", s);
183    }
184
185    #[test]
186    fn display_config_file_not_supported_contains_path() {
187        let p = PathBuf::from("/tmp/file.unsupported");
188        let s = format!("{}", ConfigError::ConfigFileNotSupported(p.clone()));
189        assert!(s.starts_with("Configuration file not supported: "));
190        assert!(s.contains("/tmp/file.unsupported"), "{}", s);
191    }
192
193    #[test]
194    fn display_ref_value_recursive_error() {
195        let e = ConfigError::RefValueRecursiveError;
196        assert_eq!(format!("{}", e), "Reference value recursive error");
197    }
198
199    #[test]
200    fn display_too_many_instances() {
201        let e = ConfigError::TooManyInstances(42);
202        assert_eq!(format!("{}", e), "Too many instances: 42");
203    }
204
205    #[test]
206    fn display_lock_poisoned() {
207        let e = ConfigError::LockPoisoned;
208        assert_eq!(format!("{}", e), "Lock poisoned");
209    }
210
211    #[test]
212    fn display_config_cause() {
213        let io_err = std::io::Error::other("io");
214        let e = ConfigError::from_cause(io_err);
215        assert_eq!(format!("{}", e), "Configuration error caused by: io");
216    }
217
218    #[test]
219    fn config_error_from_converts_to_configcause() {
220        let io_err = std::io::Error::other("io");
221        let ce = ConfigError::from_cause(io_err);
222        match ce {
223            ConfigError::ConfigCause(_) => {}
224            _ => panic!("Expected ConfigCause variant"),
225        }
226    }
227
228    #[test]
229    fn try_lock_err_variants_and_poison_detection() {
230        // WouldBlock -> None
231        assert!(ConfigError::try_lock_err(TryLockError::WouldBlock::<()>).is_none());
232
233        // Create a poisoned mutex by panicking while holding the lock in another thread.
234        let m = Arc::new(Mutex::new(()));
235        let mm = m.clone();
236        let h = thread::spawn(move || {
237            let _g = mm.lock().unwrap();
238            panic!("poison");
239        });
240        // join to ensure the panic happened and the mutex is poisoned
241        let _ = h.join();
242
243        // 用作用域包裹,确保 borrow 生命周期不会超出 m 的作用域
244        {
245            let try_result = m.try_lock();
246            match try_result {
247                Err(e) => {
248                    // Ensure ConfigError::try_lock_err maps Poisoned -> Some(LockPoisoned)
249                    let opt = ConfigError::try_lock_err(e);
250                    assert!(opt.is_some());
251                    if let Some(err) = opt {
252                        match err {
253                            ConfigError::LockPoisoned => {}
254                            _ => panic!("Expected LockPoisoned"),
255                        }
256                    }
257                }
258                Ok(_) => panic!("Expected poisoned mutex"),
259            }
260        }
261    }
262
263    #[test]
264    fn configlock_mutex_lock_c_and_try_lock_c_behaviour() {
265        // lock_c on fresh mutex should succeed
266        let m_ok = Mutex::new(1);
267        assert!(m_ok.lock_c().is_ok());
268
269        // try_lock_c returns None when another thread holds the lock (WouldBlock)
270        let m_block = Arc::new(Mutex::new(0));
271        let m_block_c = m_block.clone();
272        let handle = thread::spawn(move || {
273            let _g = m_block_c.lock().unwrap();
274            thread::sleep(Duration::from_millis(200));
275            // guard drops here
276        });
277        // give spawned thread time to acquire the lock
278        thread::sleep(Duration::from_millis(10));
279        match m_block.try_lock_c().unwrap() {
280            None => {} // expected
281            Some(_) => panic!("Expected None when mutex is held by another thread"),
282        }
283        handle.join().unwrap();
284
285        // Now create a poisoned mutex and ensure lock_c / try_lock_c return LockPoisoned
286        let m_poison = Arc::new(Mutex::new(()));
287        let mm = m_poison.clone();
288        let h2 = thread::spawn(move || {
289            let _g = mm.lock().unwrap();
290            panic!("poison");
291        });
292        let _ = h2.join();
293
294        // try_lock_c should return Err(ConfigError::LockPoisoned)
295        assert!(matches!(
296            m_poison.try_lock_c(),
297            Err(ConfigError::LockPoisoned)
298        ));
299
300        // lock_c should return Err(ConfigError::LockPoisoned)
301        assert!(matches!(m_poison.lock_c(), Err(ConfigError::LockPoisoned)));
302    }
303}