1use thiserror::Error;
2
3#[derive(Error, Debug)]
4pub enum Error {
5 #[error("HTTP request failed: {0}")]
6 HttpError(#[from] reqwest::Error),
7
8 #[error("Middleware error: {0}")]
9 MiddlewareError(#[from] reqwest_middleware::Error),
10
11 #[error("JSON serialization/deserialization error: {0}")]
12 JsonError(#[from] serde_json::Error),
13
14 #[error("API error: {status} - {message}")]
15 ApiError { status: u16, message: String },
16
17 #[error("Configuration error: {0}")]
18 ConfigError(String),
19
20 #[error("I/O error: {0}")]
21 IoError(#[from] std::io::Error),
22
23 #[error("Invalid response format: {0}")]
24 InvalidResponse(String),
25}
26
27impl Error {
28 #[must_use]
30 pub fn is_client_error(&self) -> bool {
31 matches!(self, Error::ApiError { status, .. } if (400..500).contains(status))
32 }
33
34 #[must_use]
36 pub fn is_server_error(&self) -> bool {
37 matches!(self, Error::ApiError { status, .. } if (500..600).contains(status))
38 }
39
40 #[must_use]
42 pub fn is_not_found(&self) -> bool {
43 matches!(self, Error::ApiError { status: 404, .. })
44 }
45
46 #[must_use]
48 pub fn is_unauthorized(&self) -> bool {
49 matches!(self, Error::ApiError { status: 401, .. })
50 }
51
52 #[must_use]
54 pub fn is_forbidden(&self) -> bool {
55 matches!(self, Error::ApiError { status: 403, .. })
56 }
57
58 #[must_use]
60 pub fn is_rate_limited(&self) -> bool {
61 matches!(self, Error::ApiError { status: 429, .. })
62 }
63
64 #[must_use]
66 pub fn status_code(&self) -> Option<u16> {
67 match self {
68 Error::ApiError { status, .. } => Some(*status),
69 _ => None,
70 }
71 }
72
73 #[must_use]
75 pub fn is_retryable(&self) -> bool {
76 match self {
77 Error::ApiError { status, .. } => {
78 *status == 429 || (500..=504).contains(status)
80 }
81 Error::HttpError(e) => e.is_connect() || e.is_timeout(),
82 Error::MiddlewareError(_) => true,
83 _ => false,
84 }
85 }
86}
87
88pub type Result<T> = std::result::Result<T, Error>;
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn test_config_error_display() {
96 let error = Error::ConfigError("Invalid configuration".to_string());
97 let display = format!("{}", error);
98 assert!(display.contains("Configuration error"));
99 assert!(display.contains("Invalid configuration"));
100 }
101
102 #[test]
103 fn test_api_error_display() {
104 let error = Error::ApiError {
105 status: 404,
106 message: "Not found".to_string(),
107 };
108 let display = format!("{}", error);
109 assert!(display.contains("API error"));
110 assert!(display.contains("404"));
111 assert!(display.contains("Not found"));
112 }
113
114 #[test]
115 fn test_json_error() {
116 let json_error = serde_json::from_str::<serde_json::Value>("invalid json");
117 let error = Error::JsonError(json_error.unwrap_err());
118 let display = format!("{}", error);
119 assert!(display.contains("JSON serialization"));
120 }
121
122 #[test]
123 fn test_invalid_response_error() {
124 let error = Error::InvalidResponse("Bad format".to_string());
125 let display = format!("{}", error);
126 assert!(display.contains("Invalid response format"));
127 assert!(display.contains("Bad format"));
128 }
129
130 #[test]
131 fn test_error_debug() {
132 let error = Error::ConfigError("test".to_string());
133 let debug = format!("{:?}", error);
134 assert!(debug.contains("ConfigError"));
135 }
136
137 #[test]
138 fn test_error_is_send_sync() {
139 fn assert_send_sync<T: Send + Sync>() {}
140 assert_send_sync::<Error>();
141 }
142
143 #[test]
144 fn test_api_error_with_different_status_codes() {
145 let codes = vec![400, 401, 403, 404, 500, 502, 503];
146 for code in codes {
147 let error = Error::ApiError {
148 status: code,
149 message: format!("Error {}", code),
150 };
151 let display = format!("{}", error);
152 assert!(display.contains(&code.to_string()));
153 }
154 }
155
156 #[test]
157 fn test_is_client_error() {
158 let error_400 = Error::ApiError {
159 status: 400,
160 message: "Bad Request".into(),
161 };
162 let error_404 = Error::ApiError {
163 status: 404,
164 message: "Not Found".into(),
165 };
166 let error_500 = Error::ApiError {
167 status: 500,
168 message: "Server Error".into(),
169 };
170
171 assert!(error_400.is_client_error());
172 assert!(error_404.is_client_error());
173 assert!(!error_500.is_client_error());
174 }
175
176 #[test]
177 fn test_is_server_error() {
178 let error_400 = Error::ApiError {
179 status: 400,
180 message: "Bad Request".into(),
181 };
182 let error_500 = Error::ApiError {
183 status: 500,
184 message: "Server Error".into(),
185 };
186 let error_503 = Error::ApiError {
187 status: 503,
188 message: "Service Unavailable".into(),
189 };
190
191 assert!(!error_400.is_server_error());
192 assert!(error_500.is_server_error());
193 assert!(error_503.is_server_error());
194 }
195
196 #[test]
197 fn test_specific_error_checks() {
198 let not_found = Error::ApiError {
199 status: 404,
200 message: "Not Found".into(),
201 };
202 let unauthorized = Error::ApiError {
203 status: 401,
204 message: "Unauthorized".into(),
205 };
206 let forbidden = Error::ApiError {
207 status: 403,
208 message: "Forbidden".into(),
209 };
210 let rate_limited = Error::ApiError {
211 status: 429,
212 message: "Too Many Requests".into(),
213 };
214
215 assert!(not_found.is_not_found());
216 assert!(unauthorized.is_unauthorized());
217 assert!(forbidden.is_forbidden());
218 assert!(rate_limited.is_rate_limited());
219
220 assert!(!not_found.is_unauthorized());
221 assert!(!unauthorized.is_not_found());
222 }
223
224 #[test]
225 fn test_status_code() {
226 let api_error = Error::ApiError {
227 status: 404,
228 message: "Not Found".into(),
229 };
230 let config_error = Error::ConfigError("test".into());
231
232 assert_eq!(api_error.status_code(), Some(404));
233 assert_eq!(config_error.status_code(), None);
234 }
235
236 #[test]
237 fn test_is_retryable() {
238 let rate_limited = Error::ApiError {
239 status: 429,
240 message: "Rate Limited".into(),
241 };
242 let server_error = Error::ApiError {
243 status: 500,
244 message: "Server Error".into(),
245 };
246 let bad_gateway = Error::ApiError {
247 status: 502,
248 message: "Bad Gateway".into(),
249 };
250 let not_found = Error::ApiError {
251 status: 404,
252 message: "Not Found".into(),
253 };
254 let bad_request = Error::ApiError {
255 status: 400,
256 message: "Bad Request".into(),
257 };
258
259 assert!(rate_limited.is_retryable());
260 assert!(server_error.is_retryable());
261 assert!(bad_gateway.is_retryable());
262 assert!(!not_found.is_retryable());
263 assert!(!bad_request.is_retryable());
264 }
265}