1use crate::*;
2use std::error::Error;
3use std::fmt::Display;
4use std::path::PathBuf;
5
6#[derive(Debug)]
8pub enum ConfigError {
9 ConfigNotFound(String),
11 ConfigRecursiveNotFound(String),
13 ConfigTypeMismatch(String, &'static str, &'static str),
15 ConfigParseError(String, String),
17 ConfigRecursiveError(String),
19 ConfigFileNotExists(PathBuf),
21 ConfigFileNotSupported(PathBuf),
23 RefValueRecursiveError,
25 TooManyInstances(usize),
27 LockPoisoned,
29 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 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 assert!(ConfigError::try_lock_err(TryLockError::WouldBlock::<()>).is_none());
229
230 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 let _ = h.join();
239
240 {
242 let try_result = m.try_lock();
243 match try_result {
244 Err(e) => {
245 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 let m_ok = Mutex::new(1);
264 assert!(m_ok.lock_c().is_ok());
265
266 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 });
274 thread::sleep(Duration::from_millis(10));
276 match m_block.try_lock_c().unwrap() {
277 None => {} Some(_) => panic!("Expected None when mutex is held by another thread"),
279 }
280 handle.join().unwrap();
281
282 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 assert!(matches!(
293 m_poison.try_lock_c(),
294 Err(ConfigError::LockPoisoned)
295 ));
296
297 assert!(matches!(m_poison.lock_c(), Err(ConfigError::LockPoisoned)));
299 }
300}