1use thiserror::Error;
4
5pub type Result<T> = std::result::Result<T, Error>;
7
8#[derive(Error, Debug)]
10pub enum Error {
11 #[error("HTTP request failed: {0}")]
13 Http(#[from] reqwest::Error),
14
15 #[error("API error ({code}): {message}")]
17 Api {
18 code: u16,
20 message: String,
22 },
23
24 #[error("Token expired, please refresh")]
26 TokenExpired,
27
28 #[error("Invalid URL: {0}")]
30 InvalidUrl(#[from] url::ParseError),
31
32 #[error("Serialization error: {0}")]
34 Serialization(#[from] serde_json::Error),
35
36 #[error("WebSocket error: {0}")]
38 WebSocket(String),
39
40 #[error("Connection error: {0}")]
42 Connection(String),
43
44 #[error("Operation timed out")]
46 Timeout,
47
48 #[error("Rate limit exceeded. Retry after {retry_after_secs} seconds")]
50 RateLimit {
51 retry_after_secs: u64,
53 },
54
55 #[error("Service unavailable: {0}")]
57 ServiceUnavailable(String),
58
59 #[error("Invalid configuration: {0}")]
61 InvalidConfig(String),
62
63 #[error("Record not found")]
65 NotFound,
66
67 #[error("Validation error: {0}")]
69 Validation(String),
70
71 #[error("Authentication error: {0}")]
73 Authentication(String),
74
75 #[error("Tool execution failed: {0}")]
77 ToolExecution(String),
78}
79
80impl Error {
81 pub fn api(code: u16, message: impl Into<String>) -> Self {
83 Error::Api {
84 code,
85 message: message.into(),
86 }
87 }
88
89 pub fn is_retryable(&self) -> bool {
91 matches!(
92 self,
93 Error::RateLimit { .. }
94 | Error::ServiceUnavailable(_)
95 | Error::Timeout
96 | Error::Connection(_)
97 )
98 }
99
100 pub fn retry_delay_secs(&self) -> Option<u64> {
102 match self {
103 Error::RateLimit { retry_after_secs } => Some(*retry_after_secs),
104 Error::ServiceUnavailable(_) => Some(10),
105 Error::Timeout => Some(5),
106 Error::Connection(_) => Some(3),
107 _ => None,
108 }
109 }
110}
111
112#[cfg(test)]
113mod tests {
114 use super::*;
115
116 #[test]
121 fn test_error_api_construction() {
122 let err = Error::api(404, "Not found");
123 match err {
124 Error::Api { code, message } => {
125 assert_eq!(code, 404);
126 assert_eq!(message, "Not found");
127 }
128 _ => panic!("Expected Api error"),
129 }
130 }
131
132 #[test]
133 fn test_error_api_with_string_message() {
134 let err = Error::api(500, String::from("Internal server error"));
135 match err {
136 Error::Api { code, message } => {
137 assert_eq!(code, 500);
138 assert_eq!(message, "Internal server error");
139 }
140 _ => panic!("Expected Api error"),
141 }
142 }
143
144 #[test]
149 fn test_error_display_api() {
150 let err = Error::api(404, "Resource not found");
151 assert_eq!(format!("{}", err), "API error (404): Resource not found");
152 }
153
154 #[test]
155 fn test_error_display_auth() {
156 let err = Error::Authentication("Invalid credentials".to_string());
157 assert_eq!(
158 format!("{}", err),
159 "Authentication error: Invalid credentials"
160 );
161 }
162
163 #[test]
164 fn test_error_display_token_expired() {
165 let err = Error::TokenExpired;
166 assert_eq!(format!("{}", err), "Token expired, please refresh");
167 }
168
169 #[test]
170 fn test_error_display_timeout() {
171 let err = Error::Timeout;
172 assert_eq!(format!("{}", err), "Operation timed out");
173 }
174
175 #[test]
176 fn test_error_display_rate_limit() {
177 let err = Error::RateLimit {
178 retry_after_secs: 30,
179 };
180 assert_eq!(
181 format!("{}", err),
182 "Rate limit exceeded. Retry after 30 seconds"
183 );
184 }
185
186 #[test]
187 fn test_error_display_service_unavailable() {
188 let err = Error::ServiceUnavailable("Maintenance".to_string());
189 assert_eq!(format!("{}", err), "Service unavailable: Maintenance");
190 }
191
192 #[test]
193 fn test_error_display_not_found() {
194 let err = Error::NotFound;
195 assert_eq!(format!("{}", err), "Record not found");
196 }
197
198 #[test]
199 fn test_error_display_validation() {
200 let err = Error::Validation("Invalid email format".to_string());
201 assert_eq!(format!("{}", err), "Validation error: Invalid email format");
202 }
203
204 #[test]
205 fn test_error_display_connection() {
206 let err = Error::Connection("Connection refused".to_string());
207 assert_eq!(format!("{}", err), "Connection error: Connection refused");
208 }
209
210 #[test]
211 fn test_error_display_websocket() {
212 let err = Error::WebSocket("Connection closed".to_string());
213 assert_eq!(format!("{}", err), "WebSocket error: Connection closed");
214 }
215
216 #[test]
217 fn test_error_display_invalid_config() {
218 let err = Error::InvalidConfig("Missing API key".to_string());
219 assert_eq!(format!("{}", err), "Invalid configuration: Missing API key");
220 }
221
222 #[test]
223 fn test_error_display_authentication() {
224 let err = Error::Authentication("Token expired".to_string());
225 assert_eq!(format!("{}", err), "Authentication error: Token expired");
226 }
227
228 #[test]
233 fn test_is_retryable_rate_limit() {
234 let err = Error::RateLimit {
235 retry_after_secs: 30,
236 };
237 assert!(err.is_retryable());
238 }
239
240 #[test]
241 fn test_is_retryable_service_unavailable() {
242 let err = Error::ServiceUnavailable("Maintenance".to_string());
243 assert!(err.is_retryable());
244 }
245
246 #[test]
247 fn test_is_retryable_timeout() {
248 let err = Error::Timeout;
249 assert!(err.is_retryable());
250 }
251
252 #[test]
253 fn test_is_retryable_connection() {
254 let err = Error::Connection("Connection reset".to_string());
255 assert!(err.is_retryable());
256 }
257
258 #[test]
259 fn test_is_not_retryable_api_error() {
260 let err = Error::api(404, "Not found");
261 assert!(!err.is_retryable());
262 }
263
264 #[test]
265 fn test_is_not_retryable_auth() {
266 let err = Error::Authentication("Invalid credentials".to_string());
267 assert!(!err.is_retryable());
268 }
269
270 #[test]
271 fn test_is_not_retryable_token_expired() {
272 let err = Error::TokenExpired;
273 assert!(!err.is_retryable());
274 }
275
276 #[test]
277 fn test_is_not_retryable_validation() {
278 let err = Error::Validation("Bad input".to_string());
279 assert!(!err.is_retryable());
280 }
281
282 #[test]
283 fn test_is_not_retryable_not_found() {
284 let err = Error::NotFound;
285 assert!(!err.is_retryable());
286 }
287
288 #[test]
289 fn test_is_not_retryable_invalid_config() {
290 let err = Error::InvalidConfig("Missing field".to_string());
291 assert!(!err.is_retryable());
292 }
293
294 #[test]
295 fn test_is_not_retryable_websocket() {
296 let err = Error::WebSocket("Connection closed".to_string());
297 assert!(!err.is_retryable());
298 }
299
300 #[test]
301 fn test_is_not_retryable_authentication() {
302 let err = Error::Authentication("Invalid token".to_string());
303 assert!(!err.is_retryable());
304 }
305
306 #[test]
311 fn test_retry_delay_rate_limit() {
312 let err = Error::RateLimit {
313 retry_after_secs: 60,
314 };
315 assert_eq!(err.retry_delay_secs(), Some(60));
316 }
317
318 #[test]
319 fn test_retry_delay_rate_limit_custom() {
320 let err = Error::RateLimit {
321 retry_after_secs: 120,
322 };
323 assert_eq!(err.retry_delay_secs(), Some(120));
324 }
325
326 #[test]
327 fn test_retry_delay_service_unavailable() {
328 let err = Error::ServiceUnavailable("Maintenance".to_string());
329 assert_eq!(err.retry_delay_secs(), Some(10));
330 }
331
332 #[test]
333 fn test_retry_delay_timeout() {
334 let err = Error::Timeout;
335 assert_eq!(err.retry_delay_secs(), Some(5));
336 }
337
338 #[test]
339 fn test_retry_delay_connection() {
340 let err = Error::Connection("Failed".to_string());
341 assert_eq!(err.retry_delay_secs(), Some(3));
342 }
343
344 #[test]
345 fn test_retry_delay_none_for_api_error() {
346 let err = Error::api(404, "Not found");
347 assert_eq!(err.retry_delay_secs(), None);
348 }
349
350 #[test]
351 fn test_retry_delay_none_for_auth() {
352 let err = Error::Authentication("Failed".to_string());
353 assert_eq!(err.retry_delay_secs(), None);
354 }
355
356 #[test]
357 fn test_retry_delay_none_for_not_found() {
358 let err = Error::NotFound;
359 assert_eq!(err.retry_delay_secs(), None);
360 }
361
362 #[test]
363 fn test_retry_delay_none_for_validation() {
364 let err = Error::Validation("Invalid".to_string());
365 assert_eq!(err.retry_delay_secs(), None);
366 }
367
368 #[test]
369 fn test_retry_delay_none_for_token_expired() {
370 let err = Error::TokenExpired;
371 assert_eq!(err.retry_delay_secs(), None);
372 }
373
374 #[test]
379 fn test_error_debug_format() {
380 let err = Error::api(500, "Server error");
381 let debug_str = format!("{:?}", err);
382 assert!(debug_str.contains("Api"));
383 assert!(debug_str.contains("500"));
384 assert!(debug_str.contains("Server error"));
385 }
386
387 #[test]
392 fn test_rate_limit_zero_seconds() {
393 let err = Error::RateLimit {
394 retry_after_secs: 0,
395 };
396 assert!(err.is_retryable());
397 assert_eq!(err.retry_delay_secs(), Some(0));
398 }
399
400 #[test]
401 fn test_empty_message_errors() {
402 let err = Error::Authentication(String::new());
403 assert_eq!(format!("{}", err), "Authentication error: ");
404
405 let err = Error::Connection(String::new());
406 assert_eq!(format!("{}", err), "Connection error: ");
407 }
408
409 #[test]
410 fn test_api_error_various_codes() {
411 for code in [400, 401, 403, 404, 500, 502, 503] {
412 let err = Error::api(code, "test");
413 match err {
414 Error::Api { code: c, .. } => assert_eq!(c, code),
415 _ => panic!("Expected Api error"),
416 }
417 }
418 }
419}