trailcache_core/api/
error.rs1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum ApiError {
5 #[error("Access denied: {0}")]
6 AccessDenied(String),
7
8 #[error("Unauthorized - token may be expired")]
9 Unauthorized,
10
11 #[error("Resource not found: {0}")]
12 NotFound(String),
13
14 #[error("Rate limited - please wait before retrying")]
15 RateLimited,
16
17 #[error("Server error ({1}): {0}")]
18 ServerError(String, u16),
19
20 #[error("Network error: {0}")]
21 NetworkError(#[from] reqwest::Error),
22
23 #[error("Invalid response: {0}")]
24 InvalidResponse(String),
25}
26
27const MAX_ERROR_BODY_LENGTH: usize = 500;
29
30impl ApiError {
31 fn truncate_body(body: &str) -> String {
33 if body.len() <= MAX_ERROR_BODY_LENGTH {
34 body.to_string()
35 } else {
36 format!("{}... (truncated, {} total bytes)",
37 &body[..MAX_ERROR_BODY_LENGTH],
38 body.len())
39 }
40 }
41
42 pub fn from_status(status: reqwest::StatusCode, body: &str) -> Self {
43 let truncated = Self::truncate_body(body);
44 match status.as_u16() {
45 401 => ApiError::Unauthorized,
46 403 => ApiError::AccessDenied(truncated),
47 404 => ApiError::NotFound(truncated),
48 429 => ApiError::RateLimited,
49 500..=599 => ApiError::ServerError(truncated, status.as_u16()),
50 _ => ApiError::InvalidResponse(format!("Status {}: {}", status, truncated)),
51 }
52 }
53}