reqwest/
error.rs

1#![cfg_attr(target_arch = "wasm32", allow(unused))]
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5
6use crate::util::Escape;
7use crate::{StatusCode, Url};
8
9/// A `Result` alias where the `Err` case is `reqwest::Error`.
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// The Errors that may occur when processing a `Request`.
13///
14/// Note: Errors may include the full URL used to make the `Request`. If the URL
15/// contains sensitive information (e.g. an API key as a query parameter), be
16/// sure to remove it ([`without_url`](Error::without_url))
17pub struct Error {
18    inner: Box<Inner>,
19}
20
21pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
22
23struct Inner {
24    kind: Kind,
25    source: Option<BoxError>,
26    url: Option<Url>,
27}
28
29impl Error {
30    pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
31    where
32        E: Into<BoxError>,
33    {
34        Error {
35            inner: Box::new(Inner {
36                kind,
37                source: source.map(Into::into),
38                url: None,
39            }),
40        }
41    }
42
43    /// Returns a possible URL related to this error.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// # async fn run() {
49    /// // displays last stop of a redirect loop
50    /// let response = reqwest::get("http://site.with.redirect.loop").await;
51    /// if let Err(e) = response {
52    ///     if e.is_redirect() {
53    ///         if let Some(final_stop) = e.url() {
54    ///             println!("redirect loop at {final_stop}");
55    ///         }
56    ///     }
57    /// }
58    /// # }
59    /// ```
60    pub fn url(&self) -> Option<&Url> {
61        self.inner.url.as_ref()
62    }
63
64    /// Returns a mutable reference to the URL related to this error
65    ///
66    /// This is useful if you need to remove sensitive information from the URL
67    /// (e.g. an API key in the query), but do not want to remove the URL
68    /// entirely.
69    pub fn url_mut(&mut self) -> Option<&mut Url> {
70        self.inner.url.as_mut()
71    }
72
73    /// Add a url related to this error (overwriting any existing)
74    pub fn with_url(mut self, url: Url) -> Self {
75        self.inner.url = Some(url);
76        self
77    }
78
79    /// Strip the related url from this error (if, for example, it contains
80    /// sensitive information)
81    pub fn without_url(mut self) -> Self {
82        self.inner.url = None;
83        self
84    }
85
86    /// Returns true if the error is from a type Builder.
87    pub fn is_builder(&self) -> bool {
88        matches!(self.inner.kind, Kind::Builder)
89    }
90
91    /// Returns true if the error is from a `RedirectPolicy`.
92    pub fn is_redirect(&self) -> bool {
93        matches!(self.inner.kind, Kind::Redirect)
94    }
95
96    /// Returns true if the error is from `Response::error_for_status`.
97    pub fn is_status(&self) -> bool {
98        #[cfg(not(target_arch = "wasm32"))]
99        {
100            matches!(self.inner.kind, Kind::Status(_, _))
101        }
102        #[cfg(target_arch = "wasm32")]
103        {
104            matches!(self.inner.kind, Kind::Status(_))
105        }
106    }
107
108    /// Returns true if the error is related to a timeout.
109    pub fn is_timeout(&self) -> bool {
110        let mut source = self.source();
111
112        while let Some(err) = source {
113            if err.is::<TimedOut>() {
114                return true;
115            }
116            if let Some(io) = err.downcast_ref::<io::Error>() {
117                if io.kind() == io::ErrorKind::TimedOut {
118                    return true;
119                }
120            }
121            source = err.source();
122        }
123
124        false
125    }
126
127    /// Returns true if the error is related to the request
128    pub fn is_request(&self) -> bool {
129        matches!(self.inner.kind, Kind::Request)
130    }
131
132    #[cfg(not(target_arch = "wasm32"))]
133    /// Returns true if the error is related to connect
134    pub fn is_connect(&self) -> bool {
135        let mut source = self.source();
136
137        while let Some(err) = source {
138            if let Some(hyper_err) = err.downcast_ref::<hyper_util::client::legacy::Error>() {
139                if hyper_err.is_connect() {
140                    return true;
141                }
142            }
143
144            source = err.source();
145        }
146
147        false
148    }
149
150    /// Returns true if the error is related to the request or response body
151    pub fn is_body(&self) -> bool {
152        matches!(self.inner.kind, Kind::Body)
153    }
154
155    /// Returns true if the error is related to decoding the response's body
156    pub fn is_decode(&self) -> bool {
157        matches!(self.inner.kind, Kind::Decode)
158    }
159
160    /// Returns the status code, if the error was generated from a response.
161    pub fn status(&self) -> Option<StatusCode> {
162        match self.inner.kind {
163            #[cfg(target_arch = "wasm32")]
164            Kind::Status(code) => Some(code),
165            #[cfg(not(target_arch = "wasm32"))]
166            Kind::Status(code, _) => Some(code),
167            _ => None,
168        }
169    }
170
171    // private
172
173    #[allow(unused)]
174    pub(crate) fn into_io(self) -> io::Error {
175        io::Error::new(io::ErrorKind::Other, self)
176    }
177}
178
179/// Converts from external types to reqwest's
180/// internal equivalents.
181///
182/// Currently only is used for `tower::timeout::error::Elapsed`.
183#[cfg(not(target_arch = "wasm32"))]
184pub(crate) fn cast_to_internal_error(error: BoxError) -> BoxError {
185    if error.is::<tower::timeout::error::Elapsed>() {
186        Box::new(crate::error::TimedOut) as BoxError
187    } else {
188        error
189    }
190}
191
192impl fmt::Debug for Error {
193    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
194        let mut builder = f.debug_struct("reqwest::Error");
195
196        builder.field("kind", &self.inner.kind);
197
198        if let Some(ref url) = self.inner.url {
199            builder.field("url", &url.as_str());
200        }
201        if let Some(ref source) = self.inner.source {
202            builder.field("source", source);
203        }
204
205        builder.finish()
206    }
207}
208
209impl fmt::Display for Error {
210    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
211        match self.inner.kind {
212            Kind::Builder => f.write_str("builder error")?,
213            Kind::Request => f.write_str("error sending request")?,
214            Kind::Body => f.write_str("request or response body error")?,
215            Kind::Decode => f.write_str("error decoding response body")?,
216            Kind::Redirect => f.write_str("error following redirect")?,
217            Kind::Upgrade => f.write_str("error upgrading connection")?,
218            #[cfg(target_arch = "wasm32")]
219            Kind::Status(ref code) => {
220                let prefix = if code.is_client_error() {
221                    "HTTP status client error"
222                } else {
223                    debug_assert!(code.is_server_error());
224                    "HTTP status server error"
225                };
226                write!(f, "{prefix} ({code})")?;
227            }
228            #[cfg(not(target_arch = "wasm32"))]
229            Kind::Status(ref code, ref reason) => {
230                let prefix = if code.is_client_error() {
231                    "HTTP status client error"
232                } else {
233                    debug_assert!(code.is_server_error());
234                    "HTTP status server error"
235                };
236                if let Some(reason) = reason {
237                    write!(
238                        f,
239                        "{prefix} ({} {})",
240                        code.as_str(),
241                        Escape::new(reason.as_bytes())
242                    )?;
243                } else {
244                    write!(f, "{prefix} ({code})")?;
245                }
246            }
247        };
248
249        if let Some(url) = &self.inner.url {
250            write!(f, " for url ({url})")?;
251        }
252
253        Ok(())
254    }
255}
256
257impl StdError for Error {
258    fn source(&self) -> Option<&(dyn StdError + 'static)> {
259        self.inner.source.as_ref().map(|e| &**e as _)
260    }
261}
262
263#[cfg(target_arch = "wasm32")]
264impl From<crate::error::Error> for wasm_bindgen::JsValue {
265    fn from(err: Error) -> wasm_bindgen::JsValue {
266        js_sys::Error::from(err).into()
267    }
268}
269
270#[cfg(target_arch = "wasm32")]
271impl From<crate::error::Error> for js_sys::Error {
272    fn from(err: Error) -> js_sys::Error {
273        js_sys::Error::new(&format!("{err}"))
274    }
275}
276
277#[derive(Debug)]
278pub(crate) enum Kind {
279    Builder,
280    Request,
281    Redirect,
282    #[cfg(not(target_arch = "wasm32"))]
283    Status(StatusCode, Option<hyper::ext::ReasonPhrase>),
284    #[cfg(target_arch = "wasm32")]
285    Status(StatusCode),
286    Body,
287    Decode,
288    Upgrade,
289}
290
291// constructors
292
293pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
294    Error::new(Kind::Builder, Some(e))
295}
296
297pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
298    Error::new(Kind::Body, Some(e))
299}
300
301pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
302    Error::new(Kind::Decode, Some(e))
303}
304
305pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
306    Error::new(Kind::Request, Some(e))
307}
308
309pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
310    Error::new(Kind::Redirect, Some(e)).with_url(url)
311}
312
313pub(crate) fn status_code(
314    url: Url,
315    status: StatusCode,
316    #[cfg(not(target_arch = "wasm32"))] reason: Option<hyper::ext::ReasonPhrase>,
317) -> Error {
318    Error::new(
319        Kind::Status(
320            status,
321            #[cfg(not(target_arch = "wasm32"))]
322            reason,
323        ),
324        None::<Error>,
325    )
326    .with_url(url)
327}
328
329pub(crate) fn url_bad_scheme(url: Url) -> Error {
330    Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
331}
332
333pub(crate) fn url_invalid_uri(url: Url) -> Error {
334    Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
335}
336
337if_wasm! {
338    pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
339        format!("{js_val:?}").into()
340    }
341}
342
343pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
344    Error::new(Kind::Upgrade, Some(e))
345}
346
347// io::Error helpers
348
349#[cfg(any(
350    feature = "gzip",
351    feature = "zstd",
352    feature = "brotli",
353    feature = "deflate",
354    feature = "blocking",
355))]
356pub(crate) fn into_io(e: BoxError) -> io::Error {
357    io::Error::new(io::ErrorKind::Other, e)
358}
359
360#[allow(unused)]
361pub(crate) fn decode_io(e: io::Error) -> Error {
362    if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
363        *e.into_inner()
364            .expect("io::Error::get_ref was Some(_)")
365            .downcast::<Error>()
366            .expect("StdError::is() was true")
367    } else {
368        decode(e)
369    }
370}
371
372// internal Error "sources"
373
374#[derive(Debug)]
375pub(crate) struct TimedOut;
376
377impl fmt::Display for TimedOut {
378    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
379        f.write_str("operation timed out")
380    }
381}
382
383impl StdError for TimedOut {}
384
385#[derive(Debug)]
386pub(crate) struct BadScheme;
387
388impl fmt::Display for BadScheme {
389    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
390        f.write_str("URL scheme is not allowed")
391    }
392}
393
394impl StdError for BadScheme {}
395
396#[cfg(test)]
397mod tests {
398    use super::*;
399
400    fn assert_send<T: Send>() {}
401    fn assert_sync<T: Sync>() {}
402
403    #[test]
404    fn test_source_chain() {
405        let root = Error::new(Kind::Request, None::<Error>);
406        assert!(root.source().is_none());
407
408        let link = super::body(root);
409        assert!(link.source().is_some());
410        assert_send::<Error>();
411        assert_sync::<Error>();
412    }
413
414    #[test]
415    fn mem_size_of() {
416        use std::mem::size_of;
417        assert_eq!(size_of::<Error>(), size_of::<usize>());
418    }
419
420    #[test]
421    fn roundtrip_io_error() {
422        let orig = super::request("orig");
423        // Convert reqwest::Error into an io::Error...
424        let io = orig.into_io();
425        // Convert that io::Error back into a reqwest::Error...
426        let err = super::decode_io(io);
427        // It should have pulled out the original, not nested it...
428        match err.inner.kind {
429            Kind::Request => (),
430            _ => panic!("{err:?}"),
431        }
432    }
433
434    #[test]
435    fn from_unknown_io_error() {
436        let orig = io::Error::new(io::ErrorKind::Other, "orly");
437        let err = super::decode_io(orig);
438        match err.inner.kind {
439            Kind::Decode => (),
440            _ => panic!("{err:?}"),
441        }
442    }
443
444    #[test]
445    fn is_timeout() {
446        let err = super::request(super::TimedOut);
447        assert!(err.is_timeout());
448
449        let io = io::Error::new(io::ErrorKind::Other, err);
450        let nested = super::request(io);
451        assert!(nested.is_timeout());
452    }
453}