things3_core/
error.rs

1//! Error types for the Things Core library
2
3use thiserror::Error;
4
5/// Result type alias for Things operations
6pub type Result<T> = std::result::Result<T, ThingsError>;
7
8/// Main error type for Things operations
9#[derive(Error, Debug)]
10pub enum ThingsError {
11    #[error("Database error: {0}")]
12    Database(String),
13
14    #[error("Serialization error: {0}")]
15    Serialization(#[from] serde_json::Error),
16
17    #[error("IO error: {0}")]
18    Io(#[from] std::io::Error),
19
20    #[error("Database not found: {path}")]
21    DatabaseNotFound { path: String },
22
23    #[error("Invalid UUID: {uuid}")]
24    InvalidUuid { uuid: String },
25
26    #[error("Invalid date: {date}")]
27    InvalidDate { date: String },
28
29    #[error("Task not found: {uuid}")]
30    TaskNotFound { uuid: String },
31
32    #[error("Project not found: {uuid}")]
33    ProjectNotFound { uuid: String },
34
35    #[error("Area not found: {uuid}")]
36    AreaNotFound { uuid: String },
37
38    #[error("Validation error: {message}")]
39    Validation { message: String },
40
41    #[error("Configuration error: {message}")]
42    Configuration { message: String },
43
44    #[error("Unknown error: {message}")]
45    Unknown { message: String },
46}
47
48impl ThingsError {
49    /// Create a validation error
50    pub fn validation(message: impl Into<String>) -> Self {
51        Self::Validation {
52            message: message.into(),
53        }
54    }
55
56    /// Create a configuration error
57    pub fn configuration(message: impl Into<String>) -> Self {
58        Self::Configuration {
59            message: message.into(),
60        }
61    }
62
63    /// Create an unknown error
64    pub fn unknown(message: impl Into<String>) -> Self {
65        Self::Unknown {
66            message: message.into(),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::*;
74    use std::io;
75
76    #[test]
77    fn test_database_error_from_rusqlite() {
78        // Skip this test since rusqlite is not available in this build
79        // This test would verify rusqlite error conversion if the dependency was available
80    }
81
82    #[test]
83    fn test_serialization_error_from_serde() {
84        let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
85        let things_error: ThingsError = json_error.into();
86
87        match things_error {
88            ThingsError::Serialization(_) => (),
89            _ => panic!("Expected Serialization error"),
90        }
91    }
92
93    #[test]
94    fn test_io_error_from_std() {
95        let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
96        let things_error: ThingsError = io_error.into();
97
98        match things_error {
99            ThingsError::Io(_) => (),
100            _ => panic!("Expected Io error"),
101        }
102    }
103
104    #[test]
105    fn test_database_not_found_error() {
106        let error = ThingsError::DatabaseNotFound {
107            path: "/path/to/db".to_string(),
108        };
109
110        assert!(error.to_string().contains("Database not found"));
111        assert!(error.to_string().contains("/path/to/db"));
112    }
113
114    #[test]
115    fn test_invalid_uuid_error() {
116        let error = ThingsError::InvalidUuid {
117            uuid: "invalid-uuid".to_string(),
118        };
119
120        assert!(error.to_string().contains("Invalid UUID"));
121        assert!(error.to_string().contains("invalid-uuid"));
122    }
123
124    #[test]
125    fn test_invalid_date_error() {
126        let error = ThingsError::InvalidDate {
127            date: "2023-13-45".to_string(),
128        };
129
130        assert!(error.to_string().contains("Invalid date"));
131        assert!(error.to_string().contains("2023-13-45"));
132    }
133
134    #[test]
135    fn test_task_not_found_error() {
136        let error = ThingsError::TaskNotFound {
137            uuid: "task-uuid-123".to_string(),
138        };
139
140        assert!(error.to_string().contains("Task not found"));
141        assert!(error.to_string().contains("task-uuid-123"));
142    }
143
144    #[test]
145    fn test_project_not_found_error() {
146        let error = ThingsError::ProjectNotFound {
147            uuid: "project-uuid-456".to_string(),
148        };
149
150        assert!(error.to_string().contains("Project not found"));
151        assert!(error.to_string().contains("project-uuid-456"));
152    }
153
154    #[test]
155    fn test_area_not_found_error() {
156        let error = ThingsError::AreaNotFound {
157            uuid: "area-uuid-789".to_string(),
158        };
159
160        assert!(error.to_string().contains("Area not found"));
161        assert!(error.to_string().contains("area-uuid-789"));
162    }
163
164    #[test]
165    fn test_validation_error() {
166        let error = ThingsError::Validation {
167            message: "Invalid input data".to_string(),
168        };
169
170        assert!(error.to_string().contains("Validation error"));
171        assert!(error.to_string().contains("Invalid input data"));
172    }
173
174    #[test]
175    fn test_configuration_error() {
176        let error = ThingsError::Configuration {
177            message: "Missing required config".to_string(),
178        };
179
180        assert!(error.to_string().contains("Configuration error"));
181        assert!(error.to_string().contains("Missing required config"));
182    }
183
184    #[test]
185    fn test_unknown_error() {
186        let error = ThingsError::Unknown {
187            message: "Something went wrong".to_string(),
188        };
189
190        assert!(error.to_string().contains("Unknown error"));
191        assert!(error.to_string().contains("Something went wrong"));
192    }
193
194    #[test]
195    fn test_validation_helper() {
196        let error = ThingsError::validation("Test validation message");
197
198        match error {
199            ThingsError::Validation { message } => {
200                assert_eq!(message, "Test validation message");
201            }
202            _ => panic!("Expected Validation error"),
203        }
204    }
205
206    #[test]
207    fn test_validation_helper_with_string() {
208        let message = "Test validation message".to_string();
209        let error = ThingsError::validation(message);
210
211        match error {
212            ThingsError::Validation { message } => {
213                assert_eq!(message, "Test validation message");
214            }
215            _ => panic!("Expected Validation error"),
216        }
217    }
218
219    #[test]
220    fn test_configuration_helper() {
221        let error = ThingsError::configuration("Test config message");
222
223        match error {
224            ThingsError::Configuration { message } => {
225                assert_eq!(message, "Test config message");
226            }
227            _ => panic!("Expected Configuration error"),
228        }
229    }
230
231    #[test]
232    fn test_configuration_helper_with_string() {
233        let message = "Test config message".to_string();
234        let error = ThingsError::configuration(message);
235
236        match error {
237            ThingsError::Configuration { message } => {
238                assert_eq!(message, "Test config message");
239            }
240            _ => panic!("Expected Configuration error"),
241        }
242    }
243
244    #[test]
245    fn test_unknown_helper() {
246        let error = ThingsError::unknown("Test unknown message");
247
248        match error {
249            ThingsError::Unknown { message } => {
250                assert_eq!(message, "Test unknown message");
251            }
252            _ => panic!("Expected Unknown error"),
253        }
254    }
255
256    #[test]
257    fn test_unknown_helper_with_string() {
258        let message = "Test unknown message".to_string();
259        let error = ThingsError::unknown(message);
260
261        match error {
262            ThingsError::Unknown { message } => {
263                assert_eq!(message, "Test unknown message");
264            }
265            _ => panic!("Expected Unknown error"),
266        }
267    }
268
269    #[test]
270    fn test_error_display_formatting() {
271        let errors = vec![
272            ThingsError::DatabaseNotFound {
273                path: "test.db".to_string(),
274            },
275            ThingsError::InvalidUuid {
276                uuid: "bad-uuid".to_string(),
277            },
278            ThingsError::InvalidDate {
279                date: "bad-date".to_string(),
280            },
281            ThingsError::TaskNotFound {
282                uuid: "task-123".to_string(),
283            },
284            ThingsError::ProjectNotFound {
285                uuid: "project-456".to_string(),
286            },
287            ThingsError::AreaNotFound {
288                uuid: "area-789".to_string(),
289            },
290            ThingsError::Validation {
291                message: "validation failed".to_string(),
292            },
293            ThingsError::Configuration {
294                message: "config error".to_string(),
295            },
296            ThingsError::Unknown {
297                message: "unknown error".to_string(),
298            },
299        ];
300
301        for error in errors {
302            let error_string = error.to_string();
303            assert!(!error_string.is_empty());
304            assert!(error_string.len() > 10); // Should have meaningful content
305        }
306    }
307
308    #[test]
309    fn test_error_debug_formatting() {
310        let error = ThingsError::Validation {
311            message: "test message".to_string(),
312        };
313
314        let debug_string = format!("{error:?}");
315        assert!(debug_string.contains("Validation"));
316        assert!(debug_string.contains("test message"));
317    }
318
319    #[test]
320    fn test_result_type_alias() {
321        // Test that the Result type alias works correctly
322        fn returns_result() -> String {
323            "success".to_string()
324        }
325
326        fn returns_error() -> Result<String> {
327            Err(ThingsError::validation("test error"))
328        }
329
330        assert_eq!(returns_result(), "success");
331        assert!(returns_error().is_err());
332
333        match returns_error() {
334            Err(ThingsError::Validation { message }) => {
335                assert_eq!(message, "test error");
336            }
337            _ => panic!("Expected Validation error"),
338        }
339    }
340}