ureq/
error.rs

1use std::{fmt, io};
2
3use crate::http;
4use crate::Timeout;
5
6/// Errors from ureq.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum Error {
10    /// When [`http_status_as_error()`](crate::config::ConfigBuilder::http_status_as_error) is true,
11    /// 4xx and 5xx response status codes are translated to this error.
12    ///
13    /// This is the default behavior.
14    StatusCode(u16),
15
16    /// Errors arising from the http-crate.
17    ///
18    /// These errors happen for things like invalid characters in header names.
19    Http(http::Error),
20
21    /// Error if the URI is missing scheme or host.
22    BadUri(String),
23
24    /// An HTTP/1.1 protocol error.
25    ///
26    /// This can happen if the remote server ends incorrect HTTP data like
27    /// missing version or invalid chunked transfer.
28    Protocol(ureq_proto::Error),
29
30    /// Error in io such as the TCP socket.
31    Io(io::Error),
32
33    /// Error raised if the request hits any configured timeout.
34    ///
35    /// By default no timeouts are set, which means this error can't happen.
36    Timeout(Timeout),
37
38    /// Error when resolving a hostname fails.
39    HostNotFound,
40
41    /// A redirect failed.
42    ///
43    /// This happens when ureq encounters a redirect when sending a request body
44    /// such as a POST request, and receives a 307/308 response. ureq refuses to
45    /// redirect the POST body and instead raises this error.
46    RedirectFailed,
47
48    /// Error when creating proxy settings.
49    InvalidProxyUrl,
50
51    /// A connection failed.
52    ///
53    /// This is a fallback error when there is no other explanation as to
54    /// why a connector chain didn't produce a connection. The idea is that the connector
55    /// chain would return some other [`Error`] rather than rely om this value.
56    ///
57    /// Typically bespoke connector chains should, as far as possible, map their underlying
58    /// errors to [`Error::Io`] and use the [`io::ErrorKind`] to provide a reason.
59    ///
60    /// A bespoke chain is allowed to map to this value, but that provides very little
61    /// information to the user as to why the connection failed. One way to mitigate that
62    /// would be to rely on the `log` crate to provide additional information.
63    ConnectionFailed,
64
65    /// A send body (Such as `&str`) is larger than the `content-length` header.
66    BodyExceedsLimit(u64),
67
68    /// Too many redirects.
69    ///
70    /// The error can be turned off by setting
71    /// [`max_redirects_will_error()`](crate::config::ConfigBuilder::max_redirects_will_error)
72    /// to false. When turned off, the last response will be returned instead of causing
73    /// an error, even if it is a redirect.
74    ///
75    /// The number of redirects is limited to 10 by default.
76    TooManyRedirects,
77
78    /// Some error with TLS.
79    #[cfg(feature = "_tls")]
80    Tls(&'static str),
81
82    /// Error in reading PEM certificates/private keys.
83    ///
84    /// *Note:* The wrapped error struct is not considered part of ureq API.
85    /// Breaking changes in that struct will not be reflected in ureq
86    /// major versions.
87    #[cfg(feature = "_tls")]
88    Pem(rustls_pemfile::Error),
89
90    /// An error originating in Rustls.
91    ///
92    /// *Note:* The wrapped error struct is not considered part of ureq API.
93    /// Breaking changes in that struct will not be reflected in ureq
94    /// major versions.
95    #[cfg(feature = "_rustls")]
96    Rustls(rustls::Error),
97
98    /// An error originating in Native-TLS.
99    ///
100    /// *Note:* The wrapped error struct is not considered part of ureq API.
101    /// Breaking changes in that struct will not be reflected in ureq
102    /// major versions.
103    #[cfg(feature = "native-tls")]
104    NativeTls(native_tls::Error),
105
106    /// An error providing DER encoded certificates or private keys to Native-TLS.
107    ///
108    /// *Note:* The wrapped error struct is not considered part of ureq API.
109    /// Breaking changes in that struct will not be reflected in ureq
110    /// major versions.
111    #[cfg(feature = "native-tls")]
112    Der(der::Error),
113
114    /// An error with the cookies.
115    ///
116    /// *Note:* The wrapped error struct is not considered part of ureq API.
117    /// Breaking changes in that struct will not be reflected in ureq
118    /// major versions.
119    #[cfg(feature = "cookies")]
120    Cookie(cookie_store::CookieError),
121
122    /// An error parsing a cookie value.
123    #[cfg(feature = "cookies")]
124    CookieValue(&'static str),
125
126    /// An error in the cookie store.
127    ///
128    /// *Note:* The wrapped error struct is not considered part of ureq API.
129    /// Breaking changes in that struct will not be reflected in ureq
130    /// major versions.
131    #[cfg(feature = "cookies")]
132    CookieJar(cookie_store::Error),
133
134    /// An unrecognised character set.
135    #[cfg(feature = "charset")]
136    UnknownCharset(String),
137
138    /// The setting [`https_only`](crate::config::ConfigBuilder::https_only) is true and
139    /// the URI is not https.
140    RequireHttpsOnly(String),
141
142    /// The response header, from status up until body, is too big.
143    LargeResponseHeader(usize, usize),
144
145    /// Body decompression failed (gzip or brotli).
146    #[cfg(any(feature = "gzip", feature = "brotli"))]
147    Decompress(&'static str, io::Error),
148
149    /// Serde JSON error.
150    #[cfg(feature = "json")]
151    Json(serde_json::Error),
152
153    /// Attempt to connect to a CONNECT proxy failed.
154    ConnectProxyFailed(String),
155
156    /// The protocol requires TLS (https), but the connector did not
157    /// create a TLS secured transport.
158    ///
159    /// This typically indicates a fault in bespoke `Connector` chains.
160    TlsRequired,
161
162    /// Some other error occured.
163    ///
164    /// This is an escape hatch for bespoke connector chains having errors that don't naturally
165    /// map to any other error. For connector chains we recommend:
166    ///
167    /// 1. Map to [`Error::Io`] as far as possible.
168    /// 2. Map to other [`Error`] where reasonable.
169    /// 3. Fall back on [`Error::Other`].
170    /// 4. As a last resort [`Error::ConnectionFailed`].
171    ///
172    /// ureq does not produce this error using the default connectors.
173    Other(Box<dyn std::error::Error + Send + Sync>),
174
175    /// ureq-proto made no progress and there is no more input to read.
176    ///
177    /// We should never see this value.
178    #[doc(hidden)]
179    BodyStalled,
180}
181
182impl std::error::Error for Error {}
183
184impl Error {
185    /// Convert the error into a [`std::io::Error`].
186    ///
187    /// If the error is [`Error::Io`], we unpack the error. In othe cases we make
188    /// an `std::io::ErrorKind::Other`.
189    pub fn into_io(self) -> io::Error {
190        if let Self::Io(e) = self {
191            e
192        } else {
193            io::Error::new(io::ErrorKind::Other, self)
194        }
195    }
196
197    pub(crate) fn disconnected() -> Error {
198        io::Error::new(io::ErrorKind::UnexpectedEof, "Peer disconnected").into()
199    }
200}
201
202pub(crate) fn is_wrapped_ureq_error(e: &io::Error) -> bool {
203    e.get_ref().map(|x| x.is::<Error>()).unwrap_or(false)
204}
205
206impl From<io::Error> for Error {
207    fn from(e: io::Error) -> Self {
208        if is_wrapped_ureq_error(&e) {
209            // unwraps are ok, see above.
210            let boxed = e.into_inner().unwrap();
211            let ureq = boxed.downcast::<Error>().unwrap();
212            *ureq
213        } else {
214            Error::Io(e)
215        }
216    }
217}
218
219impl fmt::Display for Error {
220    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221        match self {
222            Error::StatusCode(v) => write!(f, "http status: {}", v),
223            Error::Http(v) => write!(f, "http: {}", v),
224            Error::BadUri(v) => write!(f, "bad uri: {}", v),
225            Error::Protocol(v) => write!(f, "protocol: {}", v),
226            Error::Io(v) => write!(f, "io: {}", v),
227            Error::Timeout(v) => write!(f, "timeout: {}", v),
228            Error::HostNotFound => write!(f, "host not found"),
229            Error::RedirectFailed => write!(f, "redirect failed"),
230            Error::InvalidProxyUrl => write!(f, "invalid proxy url"),
231            Error::ConnectionFailed => write!(f, "connection failed"),
232            Error::BodyExceedsLimit(v) => {
233                write!(f, "the response body is larger than request limit: {}", v)
234            }
235            Error::TooManyRedirects => write!(f, "too many redirects"),
236            #[cfg(feature = "_tls")]
237            Error::Tls(v) => write!(f, "{}", v),
238            #[cfg(feature = "_tls")]
239            Error::Pem(v) => write!(f, "PEM: {:?}", v),
240            #[cfg(feature = "_rustls")]
241            Error::Rustls(v) => write!(f, "rustls: {}", v),
242            #[cfg(feature = "native-tls")]
243            Error::NativeTls(v) => write!(f, "native-tls: {}", v),
244            #[cfg(feature = "native-tls")]
245            Error::Der(v) => write!(f, "der: {}", v),
246            #[cfg(feature = "cookies")]
247            Error::Cookie(v) => write!(f, "cookie: {}", v),
248            #[cfg(feature = "cookies")]
249            Error::CookieValue(v) => write!(f, "{}", v),
250            #[cfg(feature = "cookies")]
251            Error::CookieJar(v) => write!(f, "cookie: {}", v),
252            #[cfg(feature = "charset")]
253            Error::UnknownCharset(v) => write!(f, "unknown character set: {}", v),
254            Error::RequireHttpsOnly(v) => write!(f, "configured for https only: {}", v),
255            Error::LargeResponseHeader(x, y) => {
256                write!(f, "response header is too big: {} > {}", x, y)
257            }
258            #[cfg(any(feature = "gzip", feature = "brotli"))]
259            Error::Decompress(x, y) => write!(f, "{} decompression failed: {}", x, y),
260            #[cfg(feature = "json")]
261            Error::Json(v) => write!(f, "json: {}", v),
262            Error::ConnectProxyFailed(v) => write!(f, "CONNECT proxy failed: {}", v),
263            Error::TlsRequired => write!(f, "TLS required, but transport is unsecured"),
264            Error::Other(v) => write!(f, "other: {}", v),
265            Error::BodyStalled => write!(f, "body data reading stalled"),
266        }
267    }
268}
269
270impl From<http::Error> for Error {
271    fn from(value: http::Error) -> Self {
272        Self::Http(value)
273    }
274}
275
276impl From<ureq_proto::Error> for Error {
277    fn from(value: ureq_proto::Error) -> Self {
278        Self::Protocol(value)
279    }
280}
281
282#[cfg(feature = "_rustls")]
283impl From<rustls::Error> for Error {
284    fn from(value: rustls::Error) -> Self {
285        Self::Rustls(value)
286    }
287}
288
289#[cfg(feature = "native-tls")]
290impl From<native_tls::Error> for Error {
291    fn from(value: native_tls::Error) -> Self {
292        Self::NativeTls(value)
293    }
294}
295
296#[cfg(feature = "native-tls")]
297impl From<der::Error> for Error {
298    fn from(value: der::Error) -> Self {
299        Self::Der(value)
300    }
301}
302
303#[cfg(feature = "cookies")]
304impl From<cookie_store::CookieError> for Error {
305    fn from(value: cookie_store::CookieError) -> Self {
306        Self::Cookie(value)
307    }
308}
309
310#[cfg(feature = "cookies")]
311impl From<cookie_store::Error> for Error {
312    fn from(value: cookie_store::Error) -> Self {
313        Self::CookieJar(value)
314    }
315}
316
317#[cfg(feature = "json")]
318impl From<serde_json::Error> for Error {
319    fn from(value: serde_json::Error) -> Self {
320        Self::Json(value)
321    }
322}
323
324#[cfg(test)]
325mod test {
326
327    use super::*;
328
329    #[test]
330    #[cfg(feature = "_test")]
331    fn status_code_error_redirect() {
332        use crate::test::init_test_log;
333        use crate::transport::set_handler;
334        init_test_log();
335        set_handler(
336            "/redirect_a",
337            302,
338            &[("Location", "http://example.edu/redirect_b")],
339            &[],
340        );
341        set_handler(
342            "/redirect_b",
343            302,
344            &[("Location", "http://example.com/status/500")],
345            &[],
346        );
347        set_handler("/status/500", 500, &[], &[]);
348        let err = crate::get("http://example.org/redirect_a")
349            .call()
350            .unwrap_err();
351        assert!(matches!(err, Error::StatusCode(500)));
352    }
353
354    #[test]
355    fn ensure_error_size() {
356        // This is platform dependent, so we can't be too strict or precise.
357        let size = std::mem::size_of::<Error>();
358        assert!(size < 100); // 40 on Macbook M1
359    }
360}