1use thiserror::Error;
2
3#[derive(Error, Debug)]
5pub enum ApiError {
6 #[error("API error: {status} - {message}")]
8 Api { status: u16, message: String },
9
10 #[error("Authentication failed: {0}")]
12 Authentication(String),
13
14 #[error("Validation error: {0}")]
16 Validation(String),
17
18 #[error("Rate limit exceeded")]
20 RateLimit,
21
22 #[error("Request timeout")]
24 Timeout,
25
26 #[error("Network error: {0}")]
28 Network(#[from] reqwest::Error),
29
30 #[error("Serialization error: {0}")]
32 Serialization(#[from] serde_json::Error),
33
34 #[error("URL error: {0}")]
36 Url(#[from] url::ParseError),
37}
38
39impl ApiError {
40 pub async fn from_response(response: reqwest::Response) -> Self {
42 let status = response.status().as_u16();
43
44 let body_text = response.text().await.unwrap_or_default();
46 tracing::error!("API error response body: {}", body_text);
47
48 let message = serde_json::from_str::<serde_json::Value>(&body_text)
49 .ok()
50 .and_then(|v| {
51 v.get("error")
52 .or(v.get("message"))
53 .and_then(|m| m.as_str())
54 .map(String::from)
55 })
56 .unwrap_or_else(|| body_text.clone());
57
58 match status {
59 401 | 403 => Self::Authentication(message),
60 400 => Self::Validation(message),
61 429 => Self::RateLimit,
62 408 => Self::Timeout,
63 _ => Self::Api { status, message },
64 }
65 }
66}