1use std::fmt::{self, Display, Formatter};
2
3use thiserror::Error;
4
5use crate::db_error::DatabaseError;
6
7#[derive(Debug, Error)]
8pub enum CommandError {
9 NoTasksSpecified,
10 TaskExists { name: String },
11 TaskNotFound { name: String },
12 DuplicateInput { name: String },
13 DataError { what: String, err: DatabaseError },
14 HTTPError { name: String, status_code: u16 },
15 ConnectionError { name: String },
16 InvalidUrlError { why: String },
17 UnlinkedError,
18}
19
20impl CommandError {
21 pub fn handle(&self) {
34 eprintln!("{self}")
35 }
36}
37
38impl Display for CommandError {
39 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
57 match self {
58 CommandError::NoTasksSpecified => write!(f, "No tasks specified!"),
59 CommandError::TaskExists { name } => write!(f, "Task {name} already exists!"),
60 CommandError::TaskNotFound { name } => write!(f, "Task {name} not found!"),
61 CommandError::DuplicateInput { name } => write!(f, "Task {name} was specified multiple times!"),
62 CommandError::DataError { what, err } => write!(f, "Unable to save {what}: {err}!"),
63 CommandError::HTTPError { name, status_code } => {
64 if name.is_empty() {
65 write!(f, "HTTP error code: {status_code}!")
66 } else {
67 write!(f, "HTTP error code: {status_code} for task {name}")
68 }
69 }
70 CommandError::ConnectionError { name } => {
71 if name.is_empty() {
72 write!(f, "Failed to connect to the server!")
73 } else {
74 write!(f, "Failed to connect to the server for task {name}")
75 }
76 }
77 Self::InvalidUrlError { why } => {
78 write!(f, "Invalid URL: {why}")
79 }
80 CommandError::UnlinkedError => write!(f, "You're already unlinked!"),
81 }
82 }
83}
84
85#[cfg(test)]
86mod tests {
87 use super::*;
88
89 #[test]
90 fn test_no_tasks_specified_display() {
91 let err = CommandError::NoTasksSpecified;
92 assert_eq!(format!("{}", err), "No tasks specified!");
93 }
94
95 #[test]
96 fn test_task_exists_display() {
97 let err = CommandError::TaskExists { name: "test task".to_string() };
98 assert_eq!(format!("{}", err), "Task test task already exists!");
99 }
100
101 #[test]
102 fn test_task_not_found_display() {
103 let err = CommandError::TaskNotFound { name: "missing".to_string() };
104 assert_eq!(format!("{}", err), "Task missing not found!");
105 }
106
107 #[test]
108 fn test_duplicate_input_display() {
109 let err = CommandError::DuplicateInput { name: "duplicate".to_string() };
110 assert_eq!(format!("{}", err), "Task duplicate was specified multiple times!");
111 }
112
113 #[test]
114 fn test_data_error_display() {
115 let db_err = DatabaseError::UserDirsError;
116 let err = CommandError::DataError {
117 what: "tasks".to_string(),
118 err: db_err,
119 };
120 let display = format!("{}", err);
121 assert!(display.contains("Unable to save tasks"));
122 assert!(display.contains("Could not get user config directory location"));
123 }
124
125 #[test]
126 fn test_http_error_with_name() {
127 let err = CommandError::HTTPError {
128 name: "my-task".to_string(),
129 status_code: 404,
130 };
131 assert_eq!(format!("{}", err), "HTTP error code: 404 for task my-task");
132 }
133
134 #[test]
135 fn test_http_error_without_name() {
136 let err = CommandError::HTTPError {
137 name: String::new(),
138 status_code: 500,
139 };
140 assert_eq!(format!("{}", err), "HTTP error code: 500!");
141 }
142
143 #[test]
144 fn test_connection_error_with_name() {
145 let err = CommandError::ConnectionError { name: "test-task".to_string() };
146 assert_eq!(format!("{}", err), "Failed to connect to the server for task test-task");
147 }
148
149 #[test]
150 fn test_connection_error_without_name() {
151 let err = CommandError::ConnectionError { name: String::new() };
152 assert_eq!(format!("{}", err), "Failed to connect to the server!");
153 }
154
155 #[test]
156 fn test_unlinked_error_display() {
157 let err = CommandError::UnlinkedError;
158 assert_eq!(format!("{}", err), "You're already unlinked!");
159 }
160
161 #[test]
162 fn test_command_error_is_error_trait() {
163 fn assert_error<T: std::error::Error>() {}
164 assert_error::<CommandError>();
165 }
166
167 #[test]
168 fn test_command_error_debug() {
169 let err = CommandError::NoTasksSpecified;
170 let debug_str = format!("{:?}", err);
171 assert!(debug_str.contains("NoTasksSpecified"));
172 }
173
174 #[test]
175 fn test_various_http_status_codes() {
176 let test_cases = vec![
177 (200, "HTTP error code: 200!"),
178 (400, "HTTP error code: 400!"),
179 (404, "HTTP error code: 404!"),
180 (500, "HTTP error code: 500!"),
181 ];
182
183 for (code, expected) in test_cases {
184 let err = CommandError::HTTPError {
185 name: String::new(),
186 status_code: code,
187 };
188 assert_eq!(format!("{}", err), expected);
189 }
190 }
191
192 #[test]
193 fn test_task_names_with_special_characters() {
194 let err = CommandError::TaskExists {
195 name: "task with spaces & symbols!".to_string(),
196 };
197 assert_eq!(format!("{}", err), "Task task with spaces & symbols! already exists!");
198 }
199
200 #[test]
201 fn test_data_error_with_io_error() {
202 let io_err = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "access denied");
203 let db_err = DatabaseError::IOError(io_err);
204 let err = CommandError::DataError {
205 what: "config".to_string(),
206 err: db_err,
207 };
208 let display = format!("{}", err);
209 assert!(display.contains("Unable to save config"));
210 assert!(display.contains("access denied"));
211 }
212
213 #[test]
214 fn test_invalid_url_error_display() {
215 let err = CommandError::InvalidUrlError {
216 why: "scheme is not http or https".to_string(),
217 };
218 assert_eq!(format!("{}", err), "Invalid URL: scheme is not http or https");
219 }
220
221 #[test]
222 fn test_invalid_url_error_with_empty_reason() {
223 let err = CommandError::InvalidUrlError { why: String::new() };
224 assert_eq!(format!("{}", err), "Invalid URL: ");
225 }
226
227 #[test]
228 fn test_invalid_url_error_with_multiline_reason() {
229 let err = CommandError::InvalidUrlError {
230 why: "invalid scheme\nHelp: use http or https".to_string(),
231 };
232 let display = format!("{}", err);
233 assert!(display.contains("Invalid URL: invalid scheme"));
234 assert!(display.contains("Help: use http or https"));
235 }
236
237 #[test]
238 fn test_invalid_url_error_debug() {
239 let err = CommandError::InvalidUrlError { why: "test".to_string() };
240 let debug_str = format!("{:?}", err);
241 assert!(debug_str.contains("InvalidUrlError"));
242 assert!(debug_str.contains("test"));
243 }
244}