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