#![cfg_attr(target_arch = "wasm32", allow(unused))] use std::error::Error as StdError; use std::fmt; use std::io; use crate::{StatusCode, Url}; /// A `Result` alias where the `Err` case is `reqwest::Error`. pub type Result = std::result::Result; /// The Errors that may occur when processing a `Request`. /// /// Note: Errors may include the full URL used to make the `Request`. If the URL /// contains sensitive information (e.g. an API key as a query parameter), be /// sure to remove it ([`without_url`](Error::without_url)) pub struct Error { inner: Box, } pub(crate) type BoxError = Box; struct Inner { kind: Kind, source: Option, url: Option, } impl Error { pub(crate) fn new(kind: Kind, source: Option) -> Error where E: Into, { Error { inner: Box::new(Inner { kind, source: source.map(Into::into), url: None, }), } } /// Returns a possible URL related to this error. /// /// # Examples /// /// ``` /// # async fn run() { /// // displays last stop of a redirect loop /// let response = reqwest::get("http://site.with.redirect.loop").await; /// if let Err(e) = response { /// if e.is_redirect() { /// if let Some(final_stop) = e.url() { /// println!("redirect loop at {}", final_stop); /// } /// } /// } /// # } /// ``` pub fn url(&self) -> Option<&Url> { self.inner.url.as_ref() } /// Returns a mutable reference to the URL related to this error /// /// This is useful if you need to remove sensitive information from the URL /// (e.g. an API key in the query), but do not want to remove the URL /// entirely. pub fn url_mut(&mut self) -> Option<&mut Url> { self.inner.url.as_mut() } /// Add a url related to this error (overwriting any existing) pub fn with_url(mut self, url: Url) -> Self { self.inner.url = Some(url); self } /// Strip the related url from this error (if, for example, it contains /// sensitive information) pub fn without_url(mut self) -> Self { self.inner.url = None; self } /// Returns true if the error is from a type Builder. pub fn is_builder(&self) -> bool { matches!(self.inner.kind, Kind::Builder) } /// Returns true if the error is from a `RedirectPolicy`. pub fn is_redirect(&self) -> bool { matches!(self.inner.kind, Kind::Redirect) } /// Returns true if the error is from `Response::error_for_status`. pub fn is_status(&self) -> bool { matches!(self.inner.kind, Kind::Status(_)) } /// Returns true if the error is related to a timeout. pub fn is_timeout(&self) -> bool { let mut source = self.source(); while let Some(err) = source { if err.is::() { return true; } source = err.source(); } false } /// Returns true if the error is related to the request pub fn is_request(&self) -> bool { matches!(self.inner.kind, Kind::Request) } #[cfg(not(target_arch = "wasm32"))] /// Returns true if the error is related to connect pub fn is_connect(&self) -> bool { let mut source = self.source(); while let Some(err) = source { if let Some(hyper_err) = err.downcast_ref::() { if hyper_err.is_connect() { return true; } } source = err.source(); } false } /// Returns true if the error is related to the request or response body pub fn is_body(&self) -> bool { matches!(self.inner.kind, Kind::Body) } /// Returns true if the error is related to decoding the response's body pub fn is_decode(&self) -> bool { matches!(self.inner.kind, Kind::Decode) } /// Returns the status code, if the error was generated from a response. pub fn status(&self) -> Option { match self.inner.kind { Kind::Status(code) => Some(code), _ => None, } } // private #[allow(unused)] pub(crate) fn into_io(self) -> io::Error { io::Error::new(io::ErrorKind::Other, self) } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { let mut builder = f.debug_struct("reqwest::Error"); builder.field("kind", &self.inner.kind); if let Some(ref url) = self.inner.url { builder.field("url", url); } if let Some(ref source) = self.inner.source { builder.field("source", source); } builder.finish() } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self.inner.kind { Kind::Builder => f.write_str("builder error")?, Kind::Request => f.write_str("error sending request")?, Kind::Body => f.write_str("request or response body error")?, Kind::Decode => f.write_str("error decoding response body")?, Kind::Redirect => f.write_str("error following redirect")?, Kind::Upgrade => f.write_str("error upgrading connection")?, Kind::Status(ref code) => { let prefix = if code.is_client_error() { "HTTP status client error" } else { debug_assert!(code.is_server_error()); "HTTP status server error" }; write!(f, "{} ({})", prefix, code)?; } }; if let Some(url) = &self.inner.url { write!(f, " for url ({})", url.as_str())?; } if let Some(e) = &self.inner.source { write!(f, ": {}", e)?; } Ok(()) } } impl StdError for Error { fn source(&self) -> Option<&(dyn StdError + 'static)> { self.inner.source.as_ref().map(|e| &**e as _) } } #[cfg(target_arch = "wasm32")] impl From for wasm_bindgen::JsValue { fn from(err: Error) -> wasm_bindgen::JsValue { js_sys::Error::from(err).into() } } #[cfg(target_arch = "wasm32")] impl From for js_sys::Error { fn from(err: Error) -> js_sys::Error { js_sys::Error::new(&format!("{}", err)) } } #[derive(Debug)] pub(crate) enum Kind { Builder, Request, Redirect, Status(StatusCode), Body, Decode, Upgrade, } // constructors pub(crate) fn builder>(e: E) -> Error { Error::new(Kind::Builder, Some(e)) } pub(crate) fn body>(e: E) -> Error { Error::new(Kind::Body, Some(e)) } pub(crate) fn decode>(e: E) -> Error { Error::new(Kind::Decode, Some(e)) } pub(crate) fn request>(e: E) -> Error { Error::new(Kind::Request, Some(e)) } pub(crate) fn redirect>(e: E, url: Url) -> Error { Error::new(Kind::Redirect, Some(e)).with_url(url) } pub(crate) fn status_code(url: Url, status: StatusCode) -> Error { Error::new(Kind::Status(status), None::).with_url(url) } pub(crate) fn url_bad_scheme(url: Url) -> Error { Error::new(Kind::Builder, Some(BadScheme)).with_url(url) } if_wasm! { pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError { format!("{:?}", js_val).into() } } pub(crate) fn upgrade>(e: E) -> Error { Error::new(Kind::Upgrade, Some(e)) } // io::Error helpers #[allow(unused)] pub(crate) fn into_io(e: Error) -> io::Error { e.into_io() } #[allow(unused)] pub(crate) fn decode_io(e: io::Error) -> Error { if e.get_ref().map(|r| r.is::()).unwrap_or(false) { *e.into_inner() .expect("io::Error::get_ref was Some(_)") .downcast::() .expect("StdError::is() was true") } else { decode(e) } } // internal Error "sources" #[derive(Debug)] pub(crate) struct TimedOut; impl fmt::Display for TimedOut { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("operation timed out") } } impl StdError for TimedOut {} #[derive(Debug)] pub(crate) struct BadScheme; impl fmt::Display for BadScheme { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("URL scheme is not allowed") } } impl StdError for BadScheme {} #[cfg(test)] mod tests { use super::*; fn assert_send() {} fn assert_sync() {} #[test] fn test_source_chain() { let root = Error::new(Kind::Request, None::); assert!(root.source().is_none()); let link = super::body(root); assert!(link.source().is_some()); assert_send::(); assert_sync::(); } #[test] fn mem_size_of() { use std::mem::size_of; assert_eq!(size_of::(), size_of::()); } #[test] fn roundtrip_io_error() { let orig = super::request("orig"); // Convert reqwest::Error into an io::Error... let io = orig.into_io(); // Convert that io::Error back into a reqwest::Error... let err = super::decode_io(io); // It should have pulled out the original, not nested it... match err.inner.kind { Kind::Request => (), _ => panic!("{:?}", err), } } #[test] fn from_unknown_io_error() { let orig = io::Error::new(io::ErrorKind::Other, "orly"); let err = super::decode_io(orig); match err.inner.kind { Kind::Decode => (), _ => panic!("{:?}", err), } } #[test] fn is_timeout() { let err = super::request(super::TimedOut); assert!(err.is_timeout()); let io = io::Error::new(io::ErrorKind::Other, err); let nested = super::request(io); assert!(nested.is_timeout()); } }