cfg_rs/
err.rs

1use crate::*;
2use std::error::Error;
3use std::path::PathBuf;
4
5/// Configuration Error.
6#[derive(Debug)]
7pub enum ConfigError {
8    /// Config not found.
9    ConfigNotFound(String),
10    /// Config not found when parsing placeholder.
11    ConfigRecursiveNotFound(String),
12    /// Config type mismatch.
13    ConfigTypeMismatch(String, &'static str, &'static str),
14    /// Config parse error.
15    ConfigParseError(String, String),
16    /// Config recursively parsed.
17    ConfigRecursiveError(String),
18    /// Config file not exists.
19    ConfigFileNotExists(PathBuf),
20    /// Config file not supported.
21    ConfigFileNotSupported(PathBuf),
22    /// Ref value recursive error.
23    RefValueRecursiveError,
24    /// Too many instances.
25    TooManyInstances(usize),
26    /// Lock failed.
27    LockPoisoned,
28    /// Config parse error with other error.
29    ConfigCause(Box<dyn Error + 'static>),
30}
31
32impl<E: Error + 'static> From<E> for ConfigError {
33    #[inline]
34    fn from(e: E) -> Self {
35        ConfigError::ConfigCause(Box::new(e))
36    }
37}
38
39impl ConfigError {
40    #[inline]
41    pub(crate) fn try_lock_err<T>(v: TryLockError<T>) -> Option<Self> {
42        match v {
43            TryLockError::WouldBlock => None,
44            TryLockError::Poisoned(e) => Some(Self::lock_err(e)),
45        }
46    }
47
48    #[inline]
49    pub(crate) fn lock_err<T>(_e: PoisonError<T>) -> Self {
50        ConfigError::LockPoisoned
51    }
52}
53
54pub(crate) trait ConfigLock<'a, T> {
55    fn lock_c(&'a self) -> Result<MutexGuard<'a, T>, ConfigError>;
56
57    fn try_lock_c(&'a self) -> Result<Option<MutexGuard<'a, T>>, ConfigError>;
58}
59
60impl<'a, T> ConfigLock<'a, T> for Mutex<T> {
61    #[inline]
62    fn lock_c(&'a self) -> Result<MutexGuard<'a, T>, ConfigError> {
63        self.lock().map_err(ConfigError::lock_err)
64    }
65
66    #[inline]
67    fn try_lock_c(&'a self) -> Result<Option<MutexGuard<'a, T>>, ConfigError> {
68        let v = self.try_lock().map_err(ConfigError::try_lock_err);
69        match v {
70            Ok(ok) => Ok(Some(ok)),
71            Err(Some(e)) => Err(e),
72            _ => Ok(None),
73        }
74    }
75}
76
77#[cfg(test)]
78mod tests {
79    use super::*;
80    use std::sync::{Arc, Mutex};
81    use std::thread;
82    use std::time::Duration;
83
84    #[test]
85    fn config_error_from_converts_to_configcause() {
86        let io_err = std::io::Error::new(std::io::ErrorKind::Other, "io");
87        let ce: ConfigError = io_err.into();
88        match ce {
89            ConfigError::ConfigCause(_) => {}
90            _ => panic!("Expected ConfigCause variant"),
91        }
92    }
93
94    #[test]
95    fn try_lock_err_variants_and_poison_detection() {
96        // WouldBlock -> None
97        assert!(ConfigError::try_lock_err(TryLockError::WouldBlock::<()>).is_none());
98
99        // Create a poisoned mutex by panicking while holding the lock in another thread.
100        let m = Arc::new(Mutex::new(()));
101        let mm = m.clone();
102        let h = thread::spawn(move || {
103            let _g = mm.lock().unwrap();
104            panic!("poison");
105        });
106        // join to ensure the panic happened and the mutex is poisoned
107        let _ = h.join();
108
109        // 用作用域包裹,确保 borrow 生命周期不会超出 m 的作用域
110        {
111            let try_result = m.try_lock();
112            match try_result {
113                Err(e) => {
114                    // Ensure ConfigError::try_lock_err maps Poisoned -> Some(LockPoisoned)
115                    let opt = ConfigError::try_lock_err(e);
116                    assert!(opt.is_some());
117                    if let Some(err) = opt {
118                        match err {
119                            ConfigError::LockPoisoned => {}
120                            _ => panic!("Expected LockPoisoned"),
121                        }
122                    }
123                }
124                Ok(_) => panic!("Expected poisoned mutex"),
125            }
126        }
127    }
128
129    #[test]
130    fn configlock_mutex_lock_c_and_try_lock_c_behaviour() {
131        // lock_c on fresh mutex should succeed
132        let m_ok = Mutex::new(1);
133        assert!(m_ok.lock_c().is_ok());
134
135        // try_lock_c returns None when another thread holds the lock (WouldBlock)
136        let m_block = Arc::new(Mutex::new(0));
137        let m_block_c = m_block.clone();
138        let handle = thread::spawn(move || {
139            let _g = m_block_c.lock().unwrap();
140            thread::sleep(Duration::from_millis(200));
141            // guard drops here
142        });
143        // give spawned thread time to acquire the lock
144        thread::sleep(Duration::from_millis(10));
145        match m_block.try_lock_c().unwrap() {
146            None => {} // expected
147            Some(_) => panic!("Expected None when mutex is held by another thread"),
148        }
149        handle.join().unwrap();
150
151        // Now create a poisoned mutex and ensure lock_c / try_lock_c return LockPoisoned
152        let m_poison = Arc::new(Mutex::new(()));
153        let mm = m_poison.clone();
154        let h2 = thread::spawn(move || {
155            let _g = mm.lock().unwrap();
156            panic!("poison");
157        });
158        let _ = h2.join();
159
160        // try_lock_c should return Err(ConfigError::LockPoisoned)
161        assert!(matches!(
162            m_poison.try_lock_c(),
163            Err(ConfigError::LockPoisoned)
164        ));
165
166        // lock_c should return Err(ConfigError::LockPoisoned)
167        assert!(matches!(m_poison.lock_c(), Err(ConfigError::LockPoisoned)));
168    }
169}