[go: up one dir, main page]

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")] This is deliberately not _tls, because we want to let
80    // bespoke TLS transports use this error code.
81    Tls(&'static str),
82
83    /// Error in reading PEM certificates/private keys.
84    ///
85    /// *Note:* The wrapped error struct is not considered part of ureq API.
86    /// Breaking changes in that struct will not be reflected in ureq
87    /// major versions.
88    #[cfg(feature = "_tls")]
89    Pem(rustls_pki_types::pem::Error),
90
91    /// An error originating in Rustls.
92    ///
93    /// *Note:* The wrapped error struct is not considered part of ureq API.
94    /// Breaking changes in that struct will not be reflected in ureq
95    /// major versions.
96    #[cfg(feature = "_rustls")]
97    Rustls(rustls::Error),
98
99    /// An error originating in Native-TLS.
100    ///
101    /// *Note:* The wrapped error struct is not considered part of ureq API.
102    /// Breaking changes in that struct will not be reflected in ureq
103    /// major versions.
104    #[cfg(feature = "native-tls")]
105    NativeTls(native_tls::Error),
106
107    /// An error providing DER encoded certificates or private keys to Native-TLS.
108    ///
109    /// *Note:* The wrapped error struct is not considered part of ureq API.
110    /// Breaking changes in that struct will not be reflected in ureq
111    /// major versions.
112    #[cfg(feature = "native-tls")]
113    Der(der::Error),
114
115    /// An error with the cookies.
116    ///
117    /// *Note:* The wrapped error struct is not considered part of ureq API.
118    /// Breaking changes in that struct will not be reflected in ureq
119    /// major versions.
120    #[cfg(feature = "cookies")]
121    Cookie(cookie_store::CookieError),
122
123    /// An error parsing a cookie value.
124    #[cfg(feature = "cookies")]
125    CookieValue(&'static str),
126
127    /// An error in the cookie store.
128    ///
129    /// *Note:* The wrapped error struct is not considered part of ureq API.
130    /// Breaking changes in that struct will not be reflected in ureq
131    /// major versions.
132    #[cfg(feature = "cookies")]
133    CookieJar(cookie_store::Error),
134
135    /// An unrecognised character set.
136    #[cfg(feature = "charset")]
137    UnknownCharset(String),
138
139    /// The setting [`https_only`](crate::config::ConfigBuilder::https_only) is true and
140    /// the URI is not https.
141    RequireHttpsOnly(String),
142
143    /// The response header, from status up until body, is too big.
144    LargeResponseHeader(usize, usize),
145
146    /// Body decompression failed (gzip or brotli).
147    #[cfg(any(feature = "gzip", feature = "brotli"))]
148    Decompress(&'static str, io::Error),
149
150    /// Serde JSON error.
151    #[cfg(feature = "json")]
152    Json(serde_json::Error),
153
154    /// Invalid MIME type in multipart form.
155    ///
156    /// *Note:* The wrapped error struct is not considered part of ureq API.
157    /// Breaking changes in that struct will not be reflected in ureq
158    /// major versions.
159    #[cfg(feature = "multipart")]
160    InvalidMimeType(mime_guess::mime::FromStrError),
161
162    /// Attempt to connect to a CONNECT proxy failed.
163    ConnectProxyFailed(String),
164
165    /// The protocol requires TLS (https), but the connector did not
166    /// create a TLS secured transport.
167    ///
168    /// This typically indicates a fault in bespoke `Connector` chains.
169    TlsRequired,
170
171    /// Some other error occured.
172    ///
173    /// This is an escape hatch for bespoke connector chains having errors that don't naturally
174    /// map to any other error. For connector chains we recommend:
175    ///
176    /// 1. Map to [`Error::Io`] as far as possible.
177    /// 2. Map to other [`Error`] where reasonable.
178    /// 3. Fall back on [`Error::Other`].
179    /// 4. As a last resort [`Error::ConnectionFailed`].
180    ///
181    /// ureq does not produce this error using the default connectors.
182    Other(Box<dyn std::error::Error + Send + Sync>),
183
184    /// ureq-proto made no progress and there is no more input to read.
185    ///
186    /// We should never see this value.
187    #[doc(hidden)]
188    BodyStalled,
189}
190
191impl std::error::Error for Error {}
192
193impl Error {
194    /// Convert the error into a [`std::io::Error`].
195    ///
196    /// If the error is [`Error::Io`], we unpack the error. In othe cases we make
197    /// an `std::io::ErrorKind::Other`.
198    pub fn into_io(self) -> io::Error {
199        if let Self::Io(e) = self {
200            e
201        } else {
202            io::Error::new(io::ErrorKind::Other, self)
203        }
204    }
205
206    pub(crate) fn disconnected(reason: &'static str) -> Error {
207        trace!("UnexpectedEof reason: {}", reason);
208        io::Error::new(io::ErrorKind::UnexpectedEof, "Peer disconnected").into()
209    }
210}
211
212pub(crate) fn is_wrapped_ureq_error(e: &io::Error) -> bool {
213    e.get_ref().map(|x| x.is::<Error>()).unwrap_or(false)
214}
215
216impl From<io::Error> for Error {
217    fn from(e: io::Error) -> Self {
218        if is_wrapped_ureq_error(&e) {
219            // unwraps are ok, see above.
220            let boxed = e.into_inner().unwrap();
221            let ureq = boxed.downcast::<Error>().unwrap();
222            *ureq
223        } else {
224            Error::Io(e)
225        }
226    }
227}
228
229impl fmt::Display for Error {
230    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231        match self {
232            Error::StatusCode(v) => write!(f, "http status: {}", v),
233            Error::Http(v) => write!(f, "http: {}", v),
234            Error::BadUri(v) => write!(f, "bad uri: {}", v),
235            Error::Protocol(v) => write!(f, "protocol: {}", v),
236            Error::Io(v) => write!(f, "io: {}", v),
237            Error::Timeout(v) => write!(f, "timeout: {}", v),
238            Error::HostNotFound => write!(f, "host not found"),
239            Error::RedirectFailed => write!(f, "redirect failed"),
240            Error::InvalidProxyUrl => write!(f, "invalid proxy url"),
241            Error::ConnectionFailed => write!(f, "connection failed"),
242            Error::BodyExceedsLimit(v) => {
243                write!(f, "the response body is larger than request limit: {}", v)
244            }
245            Error::TooManyRedirects => write!(f, "too many redirects"),
246            Error::Tls(v) => write!(f, "{}", v),
247            #[cfg(feature = "_tls")]
248            Error::Pem(v) => write!(f, "PEM: {:?}", v),
249            #[cfg(feature = "_rustls")]
250            Error::Rustls(v) => write!(f, "rustls: {}", v),
251            #[cfg(feature = "native-tls")]
252            Error::NativeTls(v) => write!(f, "native-tls: {}", v),
253            #[cfg(feature = "native-tls")]
254            Error::Der(v) => write!(f, "der: {}", v),
255            #[cfg(feature = "cookies")]
256            Error::Cookie(v) => write!(f, "cookie: {}", v),
257            #[cfg(feature = "cookies")]
258            Error::CookieValue(v) => write!(f, "{}", v),
259            #[cfg(feature = "cookies")]
260            Error::CookieJar(v) => write!(f, "cookie: {}", v),
261            #[cfg(feature = "charset")]
262            Error::UnknownCharset(v) => write!(f, "unknown character set: {}", v),
263            Error::RequireHttpsOnly(v) => write!(f, "configured for https only: {}", v),
264            Error::LargeResponseHeader(x, y) => {
265                write!(f, "response header is too big: {} > {}", x, y)
266            }
267            #[cfg(any(feature = "gzip", feature = "brotli"))]
268            Error::Decompress(x, y) => write!(f, "{} decompression failed: {}", x, y),
269            #[cfg(feature = "json")]
270            Error::Json(v) => write!(f, "json: {}", v),
271            #[cfg(feature = "multipart")]
272            Error::InvalidMimeType(v) => write!(f, "invalid MIME type: {}", v),
273            Error::ConnectProxyFailed(v) => write!(f, "CONNECT proxy failed: {}", v),
274            Error::TlsRequired => write!(f, "TLS required, but transport is unsecured"),
275            Error::Other(v) => write!(f, "other: {}", v),
276            Error::BodyStalled => write!(f, "body data reading stalled"),
277        }
278    }
279}
280
281impl From<http::Error> for Error {
282    fn from(value: http::Error) -> Self {
283        Self::Http(value)
284    }
285}
286
287impl From<ureq_proto::Error> for Error {
288    fn from(value: ureq_proto::Error) -> Self {
289        Self::Protocol(value)
290    }
291}
292
293#[cfg(feature = "_rustls")]
294impl From<rustls::Error> for Error {
295    fn from(value: rustls::Error) -> Self {
296        Self::Rustls(value)
297    }
298}
299
300#[cfg(feature = "native-tls")]
301impl From<native_tls::Error> for Error {
302    fn from(value: native_tls::Error) -> Self {
303        Self::NativeTls(value)
304    }
305}
306
307#[cfg(feature = "native-tls")]
308impl From<der::Error> for Error {
309    fn from(value: der::Error) -> Self {
310        Self::Der(value)
311    }
312}
313
314#[cfg(feature = "cookies")]
315impl From<cookie_store::CookieError> for Error {
316    fn from(value: cookie_store::CookieError) -> Self {
317        Self::Cookie(value)
318    }
319}
320
321#[cfg(feature = "cookies")]
322impl From<cookie_store::Error> for Error {
323    fn from(value: cookie_store::Error) -> Self {
324        Self::CookieJar(value)
325    }
326}
327
328#[cfg(feature = "json")]
329impl From<serde_json::Error> for Error {
330    fn from(value: serde_json::Error) -> Self {
331        Self::Json(value)
332    }
333}
334
335#[cfg(test)]
336mod test {
337
338    use super::*;
339
340    #[test]
341    #[cfg(feature = "_test")]
342    fn status_code_error_redirect() {
343        use crate::test::init_test_log;
344        use crate::transport::set_handler;
345        init_test_log();
346        set_handler(
347            "/redirect_a",
348            302,
349            &[("Location", "http://example.edu/redirect_b")],
350            &[],
351        );
352        set_handler(
353            "/redirect_b",
354            302,
355            &[("Location", "http://example.com/status/500")],
356            &[],
357        );
358        set_handler("/status/500", 500, &[], &[]);
359        let err = crate::get("http://example.org/redirect_a")
360            .call()
361            .unwrap_err();
362        assert!(matches!(err, Error::StatusCode(500)));
363    }
364
365    #[test]
366    fn ensure_error_size() {
367        // This is platform dependent, so we can't be too strict or precise.
368        let size = std::mem::size_of::<Error>();
369        assert!(size < 100); // 40 on Macbook M1
370    }
371}