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 ConfigError {
34 #[inline]
35 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 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 assert!(ConfigError::try_lock_err(TryLockError::WouldBlock::<()>).is_none());
232
233 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 let _ = h.join();
242
243 {
245 let try_result = m.try_lock();
246 match try_result {
247 Err(e) => {
248 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 let m_ok = Mutex::new(1);
267 assert!(m_ok.lock_c().is_ok());
268
269 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 });
277 thread::sleep(Duration::from_millis(10));
279 match m_block.try_lock_c().unwrap() {
280 None => {} Some(_) => panic!("Expected None when mutex is held by another thread"),
282 }
283 handle.join().unwrap();
284
285 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 assert!(matches!(
296 m_poison.try_lock_c(),
297 Err(ConfigError::LockPoisoned)
298 ));
299
300 assert!(matches!(m_poison.lock_c(), Err(ConfigError::LockPoisoned)));
302 }
303}