1use thiserror::Error;
2
3#[derive(Error, Debug)]
8pub enum EveryMapError {
9 #[error("HTTP error {status}: {message}")]
11 HttpError {
12 status: u16,
13 message: String,
14 body: Option<String>,
15 },
16
17 #[error("Authentication failed: {message}")]
19 AuthError { message: String, provider: String },
20
21 #[error("{provider} error: {code} - {message}")]
23 ProviderError {
24 provider: String,
25 code: String,
26 message: String,
27 },
28
29 #[error("Rate limited by {provider}, retry after {retry_after_secs:?}s")]
31 RateLimited {
32 provider: String,
33 retry_after_secs: Option<u64>,
34 },
35
36 #[error("Validation error: {0}")]
38 ValidationError(String),
39
40 #[error("Failed to deserialize response: {source}")]
42 SerializationError { source: serde_json::Error },
43
44 #[error("HTTP client error: {0}")]
46 ClientError(#[from] reqwest::Error),
47
48 #[error("{provider} does not support {domain}")]
50 UnsupportedDomain { provider: String, domain: String },
51
52 #[error("Unknown error occurred")]
54 Unknown,
55}
56
57impl EveryMapError {
58 pub fn http(status: u16, message: impl Into<String>) -> Self {
60 Self::HttpError {
61 status,
62 message: message.into(),
63 body: None,
64 }
65 }
66
67 pub fn http_with_body(
69 status: u16,
70 message: impl Into<String>,
71 body: impl Into<String>,
72 ) -> Self {
73 Self::HttpError {
74 status,
75 message: message.into(),
76 body: Some(body.into()),
77 }
78 }
79
80 pub fn provider(
82 provider: impl Into<String>,
83 code: impl Into<String>,
84 message: impl Into<String>,
85 ) -> Self {
86 Self::ProviderError {
87 provider: provider.into(),
88 code: code.into(),
89 message: message.into(),
90 }
91 }
92
93 pub fn auth(provider: impl Into<String>, message: impl Into<String>) -> Self {
95 Self::AuthError {
96 provider: provider.into(),
97 message: message.into(),
98 }
99 }
100
101 pub fn rate_limited(provider: impl Into<String>, retry_after_secs: Option<u64>) -> Self {
103 Self::RateLimited {
104 provider: provider.into(),
105 retry_after_secs,
106 }
107 }
108
109 pub fn is_status(&self, status: u16) -> bool {
111 match self {
112 Self::HttpError { status: s, .. } => *s == status,
113 _ => false,
114 }
115 }
116
117 pub fn is_rate_limited(&self) -> bool {
119 matches!(self, Self::RateLimited { .. }) || self.is_status(429)
120 }
121
122 pub fn is_auth_error(&self) -> bool {
124 matches!(self, Self::AuthError { .. }) || self.is_status(401) || self.is_status(403)
125 }
126
127 pub fn unsupported_domain(provider: impl Into<String>, domain: impl Into<String>) -> Self {
129 Self::UnsupportedDomain {
130 provider: provider.into(),
131 domain: domain.into(),
132 }
133 }
134}
135
136impl From<serde_json::Error> for EveryMapError {
141 fn from(err: serde_json::Error) -> Self {
142 Self::SerializationError { source: err }
143 }
144}
145
146pub type EveryMapResult<T> = Result<T, EveryMapError>;
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 #[test]
153 fn test_http_error_construction() {
154 let err = EveryMapError::http(404, "Not Found");
155 assert!(err.is_status(404));
156 assert!(!err.is_status(200));
157 assert!(!err.is_rate_limited());
158 }
159
160 #[test]
161 fn test_http_error_with_body() {
162 let err = EveryMapError::http_with_body(500, "Internal Error", "details");
163 assert!(err.is_status(500));
164 }
165
166 #[test]
167 fn test_auth_error() {
168 let err = EveryMapError::auth("here", "Invalid API key");
169 assert!(err.is_auth_error());
170 }
171
172 #[test]
173 fn test_rate_limited_error() {
174 let err = EveryMapError::rate_limited("here", Some(60));
175 assert!(err.is_rate_limited());
176 }
177
178 #[test]
179 fn test_unsupported_domain_error() {
180 let err = EveryMapError::unsupported_domain("google", "traffic");
181 match &err {
182 EveryMapError::UnsupportedDomain { provider, domain } => {
183 assert_eq!(provider, "google");
184 assert_eq!(domain, "traffic");
185 }
186 _ => panic!("Expected UnsupportedDomain error"),
187 }
188 assert!(!err.is_auth_error());
189 assert!(!err.is_rate_limited());
190 }
191
192 #[test]
193 fn test_provider_error() {
194 let err = EveryMapError::provider("here", "E400", "Bad request");
195 match &err {
196 EveryMapError::ProviderError {
197 provider,
198 code,
199 message,
200 } => {
201 assert_eq!(provider, "here");
202 assert_eq!(code, "E400");
203 assert_eq!(message, "Bad request");
204 }
205 _ => panic!("Expected ProviderError"),
206 }
207 }
208
209 #[test]
210 fn test_status_401_is_auth_error() {
211 let err = EveryMapError::http(401, "Unauthorized");
212 assert!(err.is_auth_error());
213 }
214
215 #[test]
216 fn test_status_429_is_rate_limited() {
217 let err = EveryMapError::http(429, "Too Many Requests");
218 assert!(err.is_rate_limited());
219 }
220
221 #[test]
224 fn test_http_error_construction_basic() {
225 let err = EveryMapError::http(500, "Server Error");
226 assert!(err.is_status(500));
227 assert!(!err.is_status(200));
228 }
229
230 #[test]
231 fn test_http_error_with_body_construction() {
232 let err = EveryMapError::http_with_body(502, "Bad Gateway", "upstream timeout");
233 assert!(err.is_status(502));
234 }
235
236 #[test]
237 fn test_auth_error_construction() {
238 let err = EveryMapError::auth("google", "API key expired");
239 assert!(err.is_auth_error());
240 assert!(!err.is_rate_limited());
241 }
242
243 #[test]
244 fn test_provider_error_construction() {
245 let err = EveryMapError::provider("tomtom", "E404", "Not found");
246 assert!(!err.is_auth_error());
247 assert!(!err.is_rate_limited());
248 }
249
250 #[test]
251 fn test_rate_limited_construction() {
252 let err = EveryMapError::rate_limited("here", Some(30));
253 assert!(err.is_rate_limited());
254 assert!(!err.is_auth_error());
255 }
256
257 #[test]
258 fn test_rate_limited_without_retry_after() {
259 let err = EveryMapError::rate_limited("mapbox", None);
260 assert!(err.is_rate_limited());
261 }
262
263 #[test]
264 fn test_validation_error_construction() {
265 let err = EveryMapError::ValidationError("Coordinates out of range".to_string());
266 assert!(!err.is_auth_error());
267 assert!(!err.is_rate_limited());
268 }
269
270 #[test]
271 fn test_serialization_error_construction() {
272 let json_str = "not valid json{{{";
273 let serde_err: Result<serde_json::Value, _> = serde_json::from_str(json_str);
274 let err = EveryMapError::from(serde_err.unwrap_err());
275 assert!(!err.is_auth_error());
276 assert!(!err.is_rate_limited());
277 }
278
279 #[test]
280 fn test_unsupported_domain_construction() {
281 let err = EveryMapError::unsupported_domain("radar", "imaging");
282 assert!(!err.is_auth_error());
283 assert!(!err.is_rate_limited());
284 }
285
286 #[test]
287 fn test_unknown_error_construction() {
288 let err = EveryMapError::Unknown;
289 assert!(!err.is_auth_error());
290 assert!(!err.is_rate_limited());
291 }
292
293 #[test]
296 fn test_display_http_error() {
297 let err = EveryMapError::http(404, "Not Found");
298 let message = format!("{}", err);
299 assert!(message.contains("404"));
300 assert!(message.contains("Not Found"));
301 }
302
303 #[test]
304 fn test_display_auth_error() {
305 let err = EveryMapError::auth("here", "Invalid key");
306 let message = format!("{}", err);
307 assert!(message.contains("Authentication failed"));
308 assert!(message.contains("Invalid key"));
309 }
310
311 #[test]
312 fn test_display_provider_error() {
313 let err = EveryMapError::provider("here", "E400", "Bad request");
314 let message = format!("{}", err);
315 assert!(message.contains("here"));
316 assert!(message.contains("E400"));
317 assert!(message.contains("Bad request"));
318 }
319
320 #[test]
321 fn test_display_rate_limited() {
322 let err = EveryMapError::rate_limited("google", Some(60));
323 let message = format!("{}", err);
324 assert!(message.contains("Rate limited"));
325 assert!(message.contains("google"));
326 assert!(message.contains("60"));
327 }
328
329 #[test]
330 fn test_display_rate_limited_no_retry() {
331 let err = EveryMapError::rate_limited("google", None);
332 let message = format!("{}", err);
333 assert!(message.contains("Rate limited"));
334 }
335
336 #[test]
337 fn test_display_validation_error() {
338 let err = EveryMapError::ValidationError("bad input".to_string());
339 let message = format!("{}", err);
340 assert!(message.contains("Validation error"));
341 assert!(message.contains("bad input"));
342 }
343
344 #[test]
345 fn test_display_serialization_error() {
346 let serde_err = serde_json::from_str::<serde_json::Value>("{bad}").unwrap_err();
347 let err = EveryMapError::from(serde_err);
348 let message = format!("{}", err);
349 assert!(message.contains("Failed to deserialize"));
350 }
351
352 #[test]
353 fn test_display_unsupported_domain() {
354 let err = EveryMapError::unsupported_domain("google", "traffic");
355 let message = format!("{}", err);
356 assert!(message.contains("google"));
357 assert!(message.contains("traffic"));
358 }
359
360 #[test]
361 fn test_display_unknown() {
362 let err = EveryMapError::Unknown;
363 let message = format!("{}", err);
364 assert!(message.contains("Unknown"));
365 }
366
367 #[test]
370 fn test_is_status_200() {
371 let err = EveryMapError::http(200, "OK");
372 assert!(err.is_status(200));
373 assert!(!err.is_status(201));
374 }
375
376 #[test]
377 fn test_is_status_403() {
378 let err = EveryMapError::http(403, "Forbidden");
379 assert!(err.is_status(403));
380 }
381
382 #[test]
383 fn test_is_status_non_http_error_returns_false() {
384 let err = EveryMapError::ValidationError("test".to_string());
385 assert!(!err.is_status(400));
386 }
387
388 #[test]
391 fn test_is_rate_limited_rate_limited_variant() {
392 let err = EveryMapError::rate_limited("here", Some(60));
393 assert!(err.is_rate_limited());
394 }
395
396 #[test]
397 fn test_is_rate_limited_http_429() {
398 let err = EveryMapError::http(429, "Too Many Requests");
399 assert!(err.is_rate_limited());
400 }
401
402 #[test]
403 fn test_is_rate_limited_http_other_code() {
404 let err = EveryMapError::http(500, "Server Error");
405 assert!(!err.is_rate_limited());
406 }
407
408 #[test]
411 fn test_is_auth_error_auth_variant() {
412 let err = EveryMapError::auth("here", "Invalid key");
413 assert!(err.is_auth_error());
414 }
415
416 #[test]
417 fn test_is_auth_error_http_401() {
418 let err = EveryMapError::http(401, "Unauthorized");
419 assert!(err.is_auth_error());
420 }
421
422 #[test]
423 fn test_is_auth_error_http_403() {
424 let err = EveryMapError::http(403, "Forbidden");
425 assert!(err.is_auth_error());
426 }
427
428 #[test]
429 fn test_is_auth_error_http_other_code() {
430 let err = EveryMapError::http(500, "Server Error");
431 assert!(!err.is_auth_error());
432 }
433
434 #[test]
437 fn test_from_serde_json_error() {
438 let serde_err = serde_json::from_str::<serde_json::Value>("invalid json").unwrap_err();
439 let err: EveryMapError = serde_err.into();
440 match err {
441 EveryMapError::SerializationError { .. } => {}
442 _ => panic!("Expected SerializationError"),
443 }
444 }
445
446 #[test]
447 fn test_from_serde_json_error_is_not_auth() {
448 let serde_err = serde_json::from_str::<serde_json::Value>("{").unwrap_err();
449 let err: EveryMapError = serde_err.into();
450 assert!(!err.is_auth_error());
451 assert!(!err.is_rate_limited());
452 }
453
454 #[test]
457 fn test_http_error_with_body_contains_body() {
458 match EveryMapError::http_with_body(500, "Error", "detailed body") {
459 EveryMapError::HttpError {
460 status,
461 message,
462 body,
463 } => {
464 assert_eq!(status, 500);
465 assert_eq!(message, "Error");
466 assert_eq!(body, Some("detailed body".to_string()));
467 }
468 _ => panic!("Expected HttpError"),
469 }
470 }
471
472 #[test]
473 fn test_http_error_without_body() {
474 match EveryMapError::http(404, "Not Found") {
475 EveryMapError::HttpError {
476 status,
477 message,
478 body,
479 } => {
480 assert_eq!(status, 404);
481 assert_eq!(message, "Not Found");
482 assert_eq!(body, None);
483 }
484 _ => panic!("Expected HttpError"),
485 }
486 }
487
488 #[test]
491 fn test_rate_limited_retry_after_some() {
492 match EveryMapError::rate_limited("here", Some(120)) {
493 EveryMapError::RateLimited {
494 provider,
495 retry_after_secs,
496 } => {
497 assert_eq!(provider, "here");
498 assert_eq!(retry_after_secs, Some(120));
499 }
500 _ => panic!("Expected RateLimited"),
501 }
502 }
503
504 #[test]
505 fn test_rate_limited_retry_after_none() {
506 match EveryMapError::rate_limited("here", None) {
507 EveryMapError::RateLimited {
508 retry_after_secs, ..
509 } => {
510 assert_eq!(retry_after_secs, None);
511 }
512 _ => panic!("Expected RateLimited"),
513 }
514 }
515}