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