Skip to main content

timvw_rust_runtime/
error.rs

1use std::error::Error as StdError;
2use std::fmt;
3
4use crate::BoxError;
5
6/// An error building a request, sending it, or reading the response body.
7pub struct Error {
8    kind: Kind,
9    source: Option<BoxError>,
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13enum Kind {
14    Builder,
15    Request,
16    Body,
17    Decode,
18    Io,
19}
20
21impl Error {
22    pub(crate) fn builder(source: impl Into<BoxError>) -> Self {
23        Self {
24            kind: Kind::Builder,
25            source: Some(source.into()),
26        }
27    }
28
29    pub(crate) fn request(source: impl Into<BoxError>) -> Self {
30        Self {
31            kind: Kind::Request,
32            source: Some(source.into()),
33        }
34    }
35
36    pub(crate) fn body(source: impl Into<BoxError>) -> Self {
37        Self {
38            kind: Kind::Body,
39            source: Some(source.into()),
40        }
41    }
42
43    pub(crate) fn decode(source: impl Into<BoxError>) -> Self {
44        Self {
45            kind: Kind::Decode,
46            source: Some(source.into()),
47        }
48    }
49
50    pub(crate) fn io(source: impl Into<BoxError>) -> Self {
51        Self {
52            kind: Kind::Io,
53            source: Some(source.into()),
54        }
55    }
56
57    /// The request could not be built: an invalid URL, header, or body.
58    pub fn is_builder(&self) -> bool {
59        self.kind == Kind::Builder
60    }
61
62    /// The service failed to send the request or to receive a response: connection refused, a timeout layer gave up,
63    /// and so on. [`Error::source`](std::error::Error::source) is the service's error.
64    pub fn is_request(&self) -> bool {
65        self.kind == Kind::Request
66    }
67
68    /// Reading the response body failed.
69    pub fn is_body(&self) -> bool {
70        self.kind == Kind::Body
71    }
72
73    /// The response body could not be decoded.
74    pub fn is_decode(&self) -> bool {
75        self.kind == Kind::Decode
76    }
77
78    /// A file could not be read or written: an [`Upload`](crate::Upload) whose file is missing, or
79    #[cfg_attr(
80        all(feature = "fs", not(target_family = "wasm")),
81        doc = "[`Response::save_to`](crate::Response::save_to) failing to write."
82    )]
83    #[cfg_attr(
84        not(all(feature = "fs", not(target_family = "wasm"))),
85        doc = "`Response::save_to` (feature `fs`, not on WebAssembly) failing to write."
86    )]
87    /// [`Error::source`](std::error::Error::source) is the `std::io::Error`.
88    pub fn is_io(&self) -> bool {
89        self.kind == Kind::Io
90    }
91
92    /// Whether the request timed out: a reqwest timeout, tower's `timeout` layer, this crate's
93    #[cfg_attr(
94        feature = "time",
95        doc = "[`TimeoutLayer`](crate::timeout::TimeoutLayer), or an [`idle_timeout`](crate::sse::Events::idle_timeout)"
96    )]
97    #[cfg_attr(
98        not(feature = "time"),
99        doc = "`timeout::TimeoutLayer`, or an `idle_timeout` (both feature `time`)"
100    )]
101    /// of server-sent events.
102    pub fn is_timeout(&self) -> bool {
103        self.source
104            .as_deref()
105            .is_some_and(|source| is_timeout(source as _))
106    }
107
108    /// Whether connecting to the server failed (only known for reqwest, and not on WebAssembly, where `fetch` does not
109    /// tell).
110    pub fn is_connect(&self) -> bool {
111        #[cfg(all(feature = "reqwest", not(target_family = "wasm")))]
112        {
113            let mut source: Option<&(dyn StdError + 'static)> =
114                self.source.as_deref().map(|e| e as _);
115            while let Some(error) = source {
116                if let Some(error) = error.downcast_ref::<reqwest::Error>() {
117                    if error.is_connect() {
118                        return true;
119                    }
120                }
121                source = error.source();
122            }
123        }
124        false
125    }
126}
127
128impl fmt::Debug for Error {
129    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
130        f.debug_struct("Error")
131            .field("kind", &self.kind)
132            .field("source", &self.source)
133            .finish()
134    }
135}
136
137impl fmt::Display for Error {
138    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139        let what = match self.kind {
140            Kind::Builder => "error building the request",
141            Kind::Request => "error sending the request",
142            Kind::Body => "error reading the response body",
143            Kind::Decode => "error decoding the response body",
144            Kind::Io => "I/O error",
145        };
146        match &self.source {
147            Some(source) => write!(f, "{what}: {source}"),
148            None => f.write_str(what),
149        }
150    }
151}
152
153impl StdError for Error {
154    fn source(&self) -> Option<&(dyn StdError + 'static)> {
155        self.source.as_deref().map(|e| e as _)
156    }
157}
158
159/// Whether `error`, or an error it was caused by, is a timeout: tower's `Elapsed`, a reqwest timeout, or [`TimedOut`].
160pub(crate) fn is_timeout(error: &(dyn StdError + 'static)) -> bool {
161    let mut source = Some(error);
162    while let Some(error) = source {
163        if error.to_string() == "request timed out" || error.is::<TimedOut>() {
164            return true;
165        }
166        #[cfg(feature = "reqwest")]
167        if let Some(error) = error.downcast_ref::<reqwest::Error>() {
168            if error.is_timeout() {
169                return true;
170            }
171        }
172        source = error.source();
173    }
174    false
175}
176
177/// Nothing arrived within a time limit, such as an `idle_timeout` of server-sent events.
178#[derive(Debug)]
179#[cfg_attr(not(feature = "time"), allow(dead_code))]
180pub(crate) struct TimedOut(pub(crate) String);
181
182impl fmt::Display for TimedOut {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        f.write_str(&self.0)
185    }
186}
187
188impl StdError for TimedOut {}