1use derive_more::{Display, Error, From};
4
5pub trait ErrorDecoder: Send + Sync + 'static {
41 type Error: std::fmt::Debug + Send + Sync + 'static;
43
44 fn decode(&self, status: u16, body: &bytes::Bytes) -> Option<Self::Error>;
49}
50
51#[derive(Debug, Clone, Copy, Default)]
56pub struct DefaultErrorDecoder;
57
58impl ErrorDecoder for DefaultErrorDecoder {
59 type Error = std::convert::Infallible;
60
61 fn decode(&self, _status: u16, _body: &bytes::Bytes) -> Option<Self::Error> {
62 None
63 }
64}
65
66#[derive(Debug, Display, Error, From)]
72pub enum Error {
73 #[display("HTTP error {status}: {message}")]
75 #[from(skip)]
76 Http {
77 status: u16,
79 message: String,
81 #[error(not(source))]
83 body: Option<bytes::Bytes>,
84 },
85
86 #[display("connection error: {_0}")]
88 #[from(skip)]
89 Connection(#[error(not(source))] String),
90
91 #[display("TLS error: {_0}")]
93 #[from(skip)]
94 Tls(#[error(not(source))] String),
95
96 #[display("request timeout")]
98 #[from(skip)]
99 Timeout,
100
101 #[display("invalid request: {_0}")]
103 #[from(skip)]
104 InvalidRequest(#[error(not(source))] String),
105
106 #[display("JSON serialization error: {_0}")]
108 #[from]
109 JsonSerialization(serde_json::Error),
110
111 #[display("JSON deserialization error at '{path}': {message}")]
113 #[from(skip)]
114 JsonDeserialization {
115 path: String,
117 message: String,
119 },
120
121 #[display("form serialization error: {_0}")]
123 #[from]
124 FormSerialization(serde_urlencoded::ser::Error),
125
126 #[display("query serialization error: {_0}")]
128 #[from]
129 QuerySerialization(serde_html_form::ser::Error),
130
131 #[display("invalid URL: {_0}")]
133 #[from]
134 InvalidUrl(url::ParseError),
135
136 #[display("too many redirects ({count} exceeded max of {max})")]
138 #[from(skip)]
139 TooManyRedirects {
140 count: usize,
142 max: usize,
144 },
145
146 #[display("invalid redirect: {_0}")]
148 #[from(skip)]
149 InvalidRedirect(#[error(not(source))] String),
150}
151
152pub type Result<T> = std::result::Result<T, Error>;
154
155impl Error {
156 #[must_use]
158 pub fn http(status: u16, message: impl Into<String>) -> Self {
159 Self::Http {
160 status,
161 message: message.into(),
162 body: None,
163 }
164 }
165
166 #[must_use]
168 pub fn http_with_body(status: u16, message: impl Into<String>, body: bytes::Bytes) -> Self {
169 Self::Http {
170 status,
171 message: message.into(),
172 body: Some(body),
173 }
174 }
175
176 #[must_use]
178 pub fn connection(message: impl Into<String>) -> Self {
179 Self::Connection(message.into())
180 }
181
182 #[must_use]
184 pub fn tls(message: impl Into<String>) -> Self {
185 Self::Tls(message.into())
186 }
187
188 #[must_use]
190 pub fn invalid_request(message: impl Into<String>) -> Self {
191 Self::InvalidRequest(message.into())
192 }
193
194 #[must_use]
196 pub fn json_deserialization(path: impl Into<String>, message: impl Into<String>) -> Self {
197 Self::JsonDeserialization {
198 path: path.into(),
199 message: message.into(),
200 }
201 }
202
203 #[must_use]
205 pub const fn is_timeout(&self) -> bool {
206 matches!(self, Self::Timeout)
207 }
208
209 #[must_use]
211 pub const fn is_connection(&self) -> bool {
212 matches!(self, Self::Connection(_))
213 }
214
215 #[must_use]
217 pub const fn status(&self) -> Option<u16> {
218 match self {
219 Self::Http { status, .. } => Some(*status),
220 _ => None,
221 }
222 }
223
224 #[must_use]
226 pub fn is_client_error(&self) -> bool {
227 self.status().is_some_and(|s| (400..500).contains(&s))
228 }
229
230 #[must_use]
232 pub fn is_server_error(&self) -> bool {
233 self.status().is_some_and(|s| (500..600).contains(&s))
234 }
235
236 #[must_use]
238 pub fn is_not_found(&self) -> bool {
239 self.status() == Some(404)
240 }
241
242 #[must_use]
244 pub fn body(&self) -> Option<&bytes::Bytes> {
245 match self {
246 Self::Http { body, .. } => body.as_ref(),
247 _ => None,
248 }
249 }
250
251 pub fn decode_body<T: serde::de::DeserializeOwned>(&self) -> Option<Result<T>> {
278 self.body().map(|body| crate::from_json(body))
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 #[test]
287 fn error_display() {
288 let err = Error::http(404, "Not Found");
289 assert_eq!(err.to_string(), "HTTP error 404: Not Found");
290
291 let err = Error::Timeout;
292 assert_eq!(err.to_string(), "request timeout");
293
294 let err = Error::connection("failed to connect");
295 assert_eq!(err.to_string(), "connection error: failed to connect");
296
297 let err = Error::json_deserialization("user.address.city", "missing field `city`");
298 assert_eq!(
299 err.to_string(),
300 "JSON deserialization error at 'user.address.city': missing field `city`"
301 );
302 }
303
304 #[test]
305 fn error_status() {
306 let err = Error::http(404, "Not Found");
307 assert_eq!(err.status(), Some(404));
308 assert!(err.is_client_error());
309 assert!(!err.is_server_error());
310
311 let err = Error::http(500, "Internal Server Error");
312 assert_eq!(err.status(), Some(500));
313 assert!(!err.is_client_error());
314 assert!(err.is_server_error());
315
316 let err = Error::Timeout;
317 assert_eq!(err.status(), None);
318 assert!(!err.is_client_error());
319 assert!(!err.is_server_error());
320 }
321
322 #[test]
323 fn error_is_timeout() {
324 assert!(Error::Timeout.is_timeout());
325 assert!(!Error::http(404, "Not Found").is_timeout());
326 }
327
328 #[test]
329 fn error_is_connection() {
330 assert!(Error::connection("failed").is_connection());
331 assert!(!Error::Timeout.is_connection());
332 }
333
334 #[test]
335 fn error_is_not_found() {
336 assert!(Error::http(404, "Not Found").is_not_found());
337 assert!(!Error::http(400, "Bad Request").is_not_found());
338 assert!(!Error::Timeout.is_not_found());
339 }
340
341 #[test]
342 fn error_body() {
343 let err = Error::http(404, "Not Found");
344 assert!(err.body().is_none());
345
346 let body = bytes::Bytes::from(r#"{"error": "not found"}"#);
347 let err = Error::http_with_body(404, "Not Found", body.clone());
348 assert_eq!(err.body(), Some(&body));
349
350 assert!(Error::Timeout.body().is_none());
351 }
352
353 #[test]
354 fn error_decode_body() {
355 #[derive(Debug, PartialEq, serde::Deserialize)]
356 struct ApiError {
357 error: String,
358 }
359
360 let body = bytes::Bytes::from(r#"{"error": "not found"}"#);
361 let err = Error::http_with_body(404, "Not Found", body);
362
363 let decoded = err.decode_body::<ApiError>();
364 assert!(decoded.is_some());
365 let result = decoded.expect("should have body");
366 assert!(result.is_ok());
367 assert_eq!(
368 result.expect("should decode"),
369 ApiError {
370 error: "not found".to_string()
371 }
372 );
373
374 let err = Error::http(404, "Not Found");
376 assert!(err.decode_body::<ApiError>().is_none());
377
378 assert!(Error::Timeout.decode_body::<ApiError>().is_none());
380 }
381
382 #[test]
383 fn default_error_decoder() {
384 let decoder = DefaultErrorDecoder;
385 let body = bytes::Bytes::from("test");
386 assert!(decoder.decode(404, &body).is_none());
387 assert!(decoder.decode(500, &body).is_none());
388 }
389}