1use thiserror::Error;
30
31pub type BoxError = Box<dyn std::error::Error + Send + Sync>;
40
41#[derive(Debug, Clone)]
53pub struct StringError(pub String);
54
55impl std::fmt::Display for StringError {
56 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
57 write!(f, "{}", self.0)
58 }
59}
60
61impl std::error::Error for StringError {}
62
63#[derive(Debug, Error)]
86pub enum Mi6Error {
87 #[error(transparent)]
89 Storage(#[from] StorageError),
90
91 #[error(transparent)]
93 Config(#[from] ConfigError),
94
95 #[error(transparent)]
97 TtlParse(#[from] TtlParseError),
98
99 #[error(transparent)]
101 Init(#[from] InitError),
102
103 #[error(transparent)]
105 Transcript(#[from] TranscriptError),
106
107 #[error(transparent)]
109 Scan(#[from] ScanError),
110}
111
112#[derive(Error, Debug)]
118pub enum StorageError {
119 #[error("connection error: {0}")]
121 Connection(#[source] BoxError),
122
123 #[error("query error: {0}")]
125 Query(#[source] BoxError),
126
127 #[error("io error: {0}")]
129 Io(#[from] std::io::Error),
130}
131
132#[derive(Error, Debug)]
134pub enum ConfigError {
135 #[error("failed to read config file: {0}")]
137 ReadError(#[from] std::io::Error),
138
139 #[error("failed to parse config file: {0}")]
141 ParseError(#[from] toml::de::Error),
142
143 #[error("failed to serialize config: {0}")]
145 TomlSerialize(String),
146
147 #[error("failed to determine home directory")]
149 NoHomeDir,
150}
151
152#[derive(Error, Debug, PartialEq, Eq)]
154pub enum TtlParseError {
155 #[error("invalid TTL format: {0}")]
157 InvalidFormat(String),
158
159 #[error("invalid TTL number: {0}")]
161 InvalidNumber(String),
162}
163
164#[derive(Debug, Error)]
166pub enum InitError {
167 #[error("unknown framework: {0}")]
169 UnknownFramework(String),
170
171 #[error("failed to determine settings path: {0}")]
173 SettingsPath(String),
174
175 #[error("settings file error: {0}")]
177 SettingsFile(#[from] std::io::Error),
178
179 #[error("configuration error: {0}")]
181 Config(String),
182
183 #[error("{path} contains invalid {format}: {error}")]
185 InvalidSettings {
186 path: std::path::PathBuf,
188 format: &'static str,
190 error: String,
192 },
193}
194
195#[derive(Debug, Error)]
197pub enum TranscriptError {
198 #[error("IO error: {0}")]
200 Io(#[from] std::io::Error),
201
202 #[error("JSON parse error at line {line}: {message}")]
204 Parse {
205 line: u64,
207 message: String,
209 },
210
211 #[error("File not found: {0}")]
213 NotFound(String),
214}
215
216#[derive(Error, Debug)]
218pub enum ScanError {
219 #[error("transcript parse error: {0}")]
221 Parse(#[from] TranscriptError),
222
223 #[error("storage error: {0}")]
225 Storage(#[from] StorageError),
226}
227
228#[derive(Debug, Error)]
230pub enum FrameworkResolutionError {
231 #[error("unknown framework: {0}")]
233 UnknownFramework(String),
234
235 #[error("{0}")]
237 NoFrameworksFound(String),
238}
239
240#[cfg(test)]
245mod tests {
246 use super::*;
247
248 #[test]
249 fn test_string_error_display() {
250 let msg = "test error message";
251 let err = StringError(msg.to_string());
252 assert_eq!(err.to_string(), msg);
253 }
254
255 #[test]
256 fn test_string_error_is_error() {
257 let err = StringError("test".to_string());
258 let _: &dyn std::error::Error = &err;
260 }
261
262 #[test]
263 fn test_string_error_clone() {
264 let err = StringError("cloneable".to_string());
265 let cloned = err.clone();
266 assert_eq!(err.0, cloned.0);
267 }
268
269 #[test]
270 fn test_box_error_from_string_error() {
271 let err = StringError("boxed".to_string());
272 let boxed: BoxError = Box::new(err);
273 assert_eq!(boxed.to_string(), "boxed");
274 }
275
276 #[test]
277 fn test_mi6_error_from_storage_error() {
278 let storage_err = StorageError::Io(std::io::Error::new(
279 std::io::ErrorKind::NotFound,
280 "not found",
281 ));
282 let mi6_err: Mi6Error = storage_err.into();
283 assert!(matches!(mi6_err, Mi6Error::Storage(_)));
284 }
285
286 #[test]
287 fn test_mi6_error_from_config_error() {
288 let config_err = ConfigError::NoHomeDir;
289 let mi6_err: Mi6Error = config_err.into();
290 assert!(matches!(mi6_err, Mi6Error::Config(_)));
291 }
292
293 #[test]
294 fn test_mi6_error_from_ttl_parse_error() {
295 let ttl_err = TtlParseError::InvalidFormat("bad".to_string());
296 let mi6_err: Mi6Error = ttl_err.into();
297 assert!(matches!(mi6_err, Mi6Error::TtlParse(_)));
298 }
299
300 #[test]
301 fn test_mi6_error_from_init_error() {
302 let init_err = InitError::UnknownFramework("test".to_string());
303 let mi6_err: Mi6Error = init_err.into();
304 assert!(matches!(mi6_err, Mi6Error::Init(_)));
305 }
306
307 #[test]
308 fn test_mi6_error_from_transcript_error() {
309 let transcript_err = TranscriptError::NotFound("file.json".to_string());
310 let mi6_err: Mi6Error = transcript_err.into();
311 assert!(matches!(mi6_err, Mi6Error::Transcript(_)));
312 }
313
314 #[test]
315 fn test_mi6_error_from_scan_error() {
316 let scan_err = ScanError::Parse(TranscriptError::NotFound("file.json".to_string()));
317 let mi6_err: Mi6Error = scan_err.into();
318 assert!(matches!(mi6_err, Mi6Error::Scan(_)));
319 }
320
321 #[test]
322 fn test_mi6_error_display_transparent() {
323 let storage_err = StorageError::Query(Box::new(StringError("query failed".to_string())));
324 let mi6_err: Mi6Error = storage_err.into();
325 assert!(mi6_err.to_string().contains("query failed"));
327 }
328
329 #[test]
330 fn test_storage_error_source_preserved() {
331 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
332 let storage_err = StorageError::Connection(Box::new(io_err));
333
334 use std::error::Error;
335 let source = storage_err.source();
336 assert!(source.is_some());
337 assert!(source.is_some_and(|s| s.to_string().contains("file not found")));
338 }
339}