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
184#[derive(Debug, Error)]
186pub enum TranscriptError {
187 #[error("IO error: {0}")]
189 Io(#[from] std::io::Error),
190
191 #[error("JSON parse error at line {line}: {message}")]
193 Parse {
194 line: u64,
196 message: String,
198 },
199
200 #[error("File not found: {0}")]
202 NotFound(String),
203}
204
205#[derive(Error, Debug)]
207pub enum ScanError {
208 #[error("transcript parse error: {0}")]
210 Parse(#[from] TranscriptError),
211
212 #[error("storage error: {0}")]
214 Storage(#[from] StorageError),
215}
216
217#[derive(Debug, Error)]
219pub enum FrameworkResolutionError {
220 #[error("unknown framework: {0}")]
222 UnknownFramework(String),
223
224 #[error("{0}")]
226 NoFrameworksFound(String),
227}
228
229#[cfg(test)]
234mod tests {
235 use super::*;
236
237 #[test]
238 fn test_string_error_display() {
239 let msg = "test error message";
240 let err = StringError(msg.to_string());
241 assert_eq!(err.to_string(), msg);
242 }
243
244 #[test]
245 fn test_string_error_is_error() {
246 let err = StringError("test".to_string());
247 let _: &dyn std::error::Error = &err;
249 }
250
251 #[test]
252 fn test_string_error_clone() {
253 let err = StringError("cloneable".to_string());
254 let cloned = err.clone();
255 assert_eq!(err.0, cloned.0);
256 }
257
258 #[test]
259 fn test_box_error_from_string_error() {
260 let err = StringError("boxed".to_string());
261 let boxed: BoxError = Box::new(err);
262 assert_eq!(boxed.to_string(), "boxed");
263 }
264
265 #[test]
266 fn test_mi6_error_from_storage_error() {
267 let storage_err = StorageError::Io(std::io::Error::new(
268 std::io::ErrorKind::NotFound,
269 "not found",
270 ));
271 let mi6_err: Mi6Error = storage_err.into();
272 assert!(matches!(mi6_err, Mi6Error::Storage(_)));
273 }
274
275 #[test]
276 fn test_mi6_error_from_config_error() {
277 let config_err = ConfigError::NoHomeDir;
278 let mi6_err: Mi6Error = config_err.into();
279 assert!(matches!(mi6_err, Mi6Error::Config(_)));
280 }
281
282 #[test]
283 fn test_mi6_error_from_ttl_parse_error() {
284 let ttl_err = TtlParseError::InvalidFormat("bad".to_string());
285 let mi6_err: Mi6Error = ttl_err.into();
286 assert!(matches!(mi6_err, Mi6Error::TtlParse(_)));
287 }
288
289 #[test]
290 fn test_mi6_error_from_init_error() {
291 let init_err = InitError::UnknownFramework("test".to_string());
292 let mi6_err: Mi6Error = init_err.into();
293 assert!(matches!(mi6_err, Mi6Error::Init(_)));
294 }
295
296 #[test]
297 fn test_mi6_error_from_transcript_error() {
298 let transcript_err = TranscriptError::NotFound("file.json".to_string());
299 let mi6_err: Mi6Error = transcript_err.into();
300 assert!(matches!(mi6_err, Mi6Error::Transcript(_)));
301 }
302
303 #[test]
304 fn test_mi6_error_from_scan_error() {
305 let scan_err = ScanError::Parse(TranscriptError::NotFound("file.json".to_string()));
306 let mi6_err: Mi6Error = scan_err.into();
307 assert!(matches!(mi6_err, Mi6Error::Scan(_)));
308 }
309
310 #[test]
311 fn test_mi6_error_display_transparent() {
312 let storage_err = StorageError::Query(Box::new(StringError("query failed".to_string())));
313 let mi6_err: Mi6Error = storage_err.into();
314 assert!(mi6_err.to_string().contains("query failed"));
316 }
317
318 #[test]
319 fn test_storage_error_source_preserved() {
320 let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "file not found");
321 let storage_err = StorageError::Connection(Box::new(io_err));
322
323 use std::error::Error;
324 let source = storage_err.source();
325 assert!(source.is_some());
326 assert!(source.is_some_and(|s| s.to_string().contains("file not found")));
327 }
328}