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(#[from] rusqlite::Error),
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        let sqlite_error = rusqlite::Error::SqliteFailure(
79            rusqlite::ffi::Error::new(1),
80            Some("test error".to_string()),
81        );
82        let things_error: ThingsError = sqlite_error.into();
83
84        match things_error {
85            ThingsError::Database(_) => (),
86            _ => panic!("Expected Database error"),
87        }
88    }
89
90    #[test]
91    fn test_serialization_error_from_serde() {
92        let json_error = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
93        let things_error: ThingsError = json_error.into();
94
95        match things_error {
96            ThingsError::Serialization(_) => (),
97            _ => panic!("Expected Serialization error"),
98        }
99    }
100
101    #[test]
102    fn test_io_error_from_std() {
103        let io_error = io::Error::new(io::ErrorKind::NotFound, "file not found");
104        let things_error: ThingsError = io_error.into();
105
106        match things_error {
107            ThingsError::Io(_) => (),
108            _ => panic!("Expected Io error"),
109        }
110    }
111
112    #[test]
113    fn test_database_not_found_error() {
114        let error = ThingsError::DatabaseNotFound {
115            path: "/path/to/db".to_string(),
116        };
117
118        assert!(error.to_string().contains("Database not found"));
119        assert!(error.to_string().contains("/path/to/db"));
120    }
121
122    #[test]
123    fn test_invalid_uuid_error() {
124        let error = ThingsError::InvalidUuid {
125            uuid: "invalid-uuid".to_string(),
126        };
127
128        assert!(error.to_string().contains("Invalid UUID"));
129        assert!(error.to_string().contains("invalid-uuid"));
130    }
131
132    #[test]
133    fn test_invalid_date_error() {
134        let error = ThingsError::InvalidDate {
135            date: "2023-13-45".to_string(),
136        };
137
138        assert!(error.to_string().contains("Invalid date"));
139        assert!(error.to_string().contains("2023-13-45"));
140    }
141
142    #[test]
143    fn test_task_not_found_error() {
144        let error = ThingsError::TaskNotFound {
145            uuid: "task-uuid-123".to_string(),
146        };
147
148        assert!(error.to_string().contains("Task not found"));
149        assert!(error.to_string().contains("task-uuid-123"));
150    }
151
152    #[test]
153    fn test_project_not_found_error() {
154        let error = ThingsError::ProjectNotFound {
155            uuid: "project-uuid-456".to_string(),
156        };
157
158        assert!(error.to_string().contains("Project not found"));
159        assert!(error.to_string().contains("project-uuid-456"));
160    }
161
162    #[test]
163    fn test_area_not_found_error() {
164        let error = ThingsError::AreaNotFound {
165            uuid: "area-uuid-789".to_string(),
166        };
167
168        assert!(error.to_string().contains("Area not found"));
169        assert!(error.to_string().contains("area-uuid-789"));
170    }
171
172    #[test]
173    fn test_validation_error() {
174        let error = ThingsError::Validation {
175            message: "Invalid input data".to_string(),
176        };
177
178        assert!(error.to_string().contains("Validation error"));
179        assert!(error.to_string().contains("Invalid input data"));
180    }
181
182    #[test]
183    fn test_configuration_error() {
184        let error = ThingsError::Configuration {
185            message: "Missing required config".to_string(),
186        };
187
188        assert!(error.to_string().contains("Configuration error"));
189        assert!(error.to_string().contains("Missing required config"));
190    }
191
192    #[test]
193    fn test_unknown_error() {
194        let error = ThingsError::Unknown {
195            message: "Something went wrong".to_string(),
196        };
197
198        assert!(error.to_string().contains("Unknown error"));
199        assert!(error.to_string().contains("Something went wrong"));
200    }
201
202    #[test]
203    fn test_validation_helper() {
204        let error = ThingsError::validation("Test validation message");
205
206        match error {
207            ThingsError::Validation { message } => {
208                assert_eq!(message, "Test validation message");
209            }
210            _ => panic!("Expected Validation error"),
211        }
212    }
213
214    #[test]
215    fn test_validation_helper_with_string() {
216        let message = "Test validation message".to_string();
217        let error = ThingsError::validation(message);
218
219        match error {
220            ThingsError::Validation { message } => {
221                assert_eq!(message, "Test validation message");
222            }
223            _ => panic!("Expected Validation error"),
224        }
225    }
226
227    #[test]
228    fn test_configuration_helper() {
229        let error = ThingsError::configuration("Test config message");
230
231        match error {
232            ThingsError::Configuration { message } => {
233                assert_eq!(message, "Test config message");
234            }
235            _ => panic!("Expected Configuration error"),
236        }
237    }
238
239    #[test]
240    fn test_configuration_helper_with_string() {
241        let message = "Test config message".to_string();
242        let error = ThingsError::configuration(message);
243
244        match error {
245            ThingsError::Configuration { message } => {
246                assert_eq!(message, "Test config message");
247            }
248            _ => panic!("Expected Configuration error"),
249        }
250    }
251
252    #[test]
253    fn test_unknown_helper() {
254        let error = ThingsError::unknown("Test unknown message");
255
256        match error {
257            ThingsError::Unknown { message } => {
258                assert_eq!(message, "Test unknown message");
259            }
260            _ => panic!("Expected Unknown error"),
261        }
262    }
263
264    #[test]
265    fn test_unknown_helper_with_string() {
266        let message = "Test unknown message".to_string();
267        let error = ThingsError::unknown(message);
268
269        match error {
270            ThingsError::Unknown { message } => {
271                assert_eq!(message, "Test unknown message");
272            }
273            _ => panic!("Expected Unknown error"),
274        }
275    }
276
277    #[test]
278    fn test_error_display_formatting() {
279        let errors = vec![
280            ThingsError::DatabaseNotFound {
281                path: "test.db".to_string(),
282            },
283            ThingsError::InvalidUuid {
284                uuid: "bad-uuid".to_string(),
285            },
286            ThingsError::InvalidDate {
287                date: "bad-date".to_string(),
288            },
289            ThingsError::TaskNotFound {
290                uuid: "task-123".to_string(),
291            },
292            ThingsError::ProjectNotFound {
293                uuid: "project-456".to_string(),
294            },
295            ThingsError::AreaNotFound {
296                uuid: "area-789".to_string(),
297            },
298            ThingsError::Validation {
299                message: "validation failed".to_string(),
300            },
301            ThingsError::Configuration {
302                message: "config error".to_string(),
303            },
304            ThingsError::Unknown {
305                message: "unknown error".to_string(),
306            },
307        ];
308
309        for error in errors {
310            let error_string = error.to_string();
311            assert!(!error_string.is_empty());
312            assert!(error_string.len() > 10); // Should have meaningful content
313        }
314    }
315
316    #[test]
317    fn test_error_debug_formatting() {
318        let error = ThingsError::Validation {
319            message: "test message".to_string(),
320        };
321
322        let debug_string = format!("{:?}", error);
323        assert!(debug_string.contains("Validation"));
324        assert!(debug_string.contains("test message"));
325    }
326
327    #[test]
328    fn test_result_type_alias() {
329        // Test that the Result type alias works correctly
330        fn returns_result() -> Result<String> {
331            Ok("success".to_string())
332        }
333
334        fn returns_error() -> Result<String> {
335            Err(ThingsError::validation("test error"))
336        }
337
338        assert!(returns_result().is_ok());
339        assert!(returns_error().is_err());
340
341        match returns_error() {
342            Err(ThingsError::Validation { message }) => {
343                assert_eq!(message, "test error");
344            }
345            _ => panic!("Expected Validation error"),
346        }
347    }
348}