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(#[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 pub fn validation(message: impl Into<String>) -> Self {
51 Self::Validation {
52 message: message.into(),
53 }
54 }
55
56 pub fn configuration(message: impl Into<String>) -> Self {
58 Self::Configuration {
59 message: message.into(),
60 }
61 }
62
63 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); }
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 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}