fetch_kit/error.rs
1/// Errors that can occur when making requests with `fetch_kit`.
2#[derive(Debug, thiserror::Error)]
3pub enum FetchError {
4 /// A network-level error (DNS, connection refused, etc.).
5 #[error("network error: {0}")]
6 Network(String),
7
8 /// The request timed out.
9 #[error("request timed out after {0:?}")]
10 Timeout(std::time::Duration),
11
12 /// The server returned a non-success status code.
13 #[error("HTTP {status}: {body}")]
14 StatusCode {
15 /// HTTP status code.
16 status: u16,
17 /// Response body.
18 body: String,
19 },
20
21 /// Failed to serialize or deserialize JSON.
22 #[error("serialization error: {0}")]
23 Serialization(#[from] serde_json::Error),
24
25 /// The circuit breaker is open — requests are being rejected.
26 #[error("circuit breaker is open; requests temporarily blocked")]
27 CircuitOpen,
28
29 /// A middleware error from reqwest-middleware.
30 #[error("middleware error: {0}")]
31 Middleware(String),
32
33 /// Failed to build the client.
34 #[error("build error: {0}")]
35 BuildError(String),
36
37 /// Failed to set a header.
38 #[error("header error: {0}")]
39 HeaderError(String),
40}
41
42impl From<reqwest::Error> for FetchError {
43 fn from(err: reqwest::Error) -> Self {
44 if err.is_timeout() {
45 // The reqwest::Error does not expose the configured timeout duration,
46 // so we use ZERO to indicate "the timeout triggered but the actual
47 // duration is unknown at this point."
48 FetchError::Timeout(std::time::Duration::ZERO)
49 } else {
50 FetchError::Network(err.to_string())
51 }
52 }
53}
54
55impl From<reqwest_middleware::Error> for FetchError {
56 fn from(err: reqwest_middleware::Error) -> Self {
57 match err {
58 reqwest_middleware::Error::Middleware(e) => {
59 // Preserve fetch_kit's own middleware errors (e.g.
60 // `CircuitOpen`) so callers can match on them.
61 let e = match e.downcast::<FetchError>() {
62 Ok(fetch_err) => return fetch_err,
63 Err(e) => e,
64 };
65 // `RetryTransientMiddleware` re-wraps the final error in a
66 // `RetryError` even when no retries were performed —
67 // unwrap that layer before degrading to `Middleware`.
68 let e = match e.downcast::<reqwest_retry::RetryError>() {
69 Ok(retry_err) => {
70 let inner = match retry_err {
71 reqwest_retry::RetryError::Error(inner) => inner,
72 reqwest_retry::RetryError::WithRetries { err, .. } => err,
73 };
74 return FetchError::from(inner);
75 }
76 Err(e) => e,
77 };
78 FetchError::Middleware(e.to_string())
79 }
80 reqwest_middleware::Error::Reqwest(e) => FetchError::from(e),
81 }
82 }
83}