use std::error::Error as StdError; use std::fmt; use std::io; use {StatusCode, Url}; /// The Errors that may occur when processing a `Request`. /// /// # Examples /// /// ``` /// extern crate serde; /// extern crate reqwest; /// /// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Simple { /// key: String /// } /// # fn main() { } /// /// fn run() { /// match make_request() { /// Err(e) => handler(e), /// Ok(_) => return, /// } /// } /// // Response is not a json object conforming to the Simple struct /// fn make_request() -> Result { /// reqwest::get("http://httpbin.org/ip")?.json() /// } /// /// fn handler(e: reqwest::Error) { /// if e.is_http() { /// match e.url() { /// None => println!("No Url given"), /// Some(url) => println!("Problem making request to: {}", url), /// } /// } /// // Inspect the internal error and output it /// if e.is_serialization() { /// let serde_error = match e.get_ref() { /// None => return, /// Some(err) => err, /// }; /// println!("problem parsing information {}", serde_error); /// } /// if e.is_redirect() { /// println!("server redirecting too many times or making loop"); /// } /// } /// ``` pub struct Error { inner: Box, } struct Inner { kind: Kind, url: Option, } /// A `Result` alias where the `Err` case is `reqwest::Error`. pub type Result = ::std::result::Result; impl Error { fn new(kind: Kind, url: Option) -> Error { Error { inner: Box::new(Inner { kind, url, }), } } /// Returns a possible URL related to this error. /// /// # Examples /// /// ``` /// # fn run() { /// // displays last stop of a redirect loop /// let response = reqwest::get("http://site.with.redirect.loop"); /// if let Err(e) = response { /// if e.is_redirect() { /// if let Some(final_stop) = e.url() { /// println!("redirect loop at {}", final_stop); /// } /// } /// } /// # } /// ``` #[inline] pub fn url(&self) -> Option<&Url> { self.inner.url.as_ref() } pub(crate) fn with_url(mut self, url: Url) -> Error { debug_assert_eq!(self.inner.url, None, "with_url overriding existing url"); self.inner.url = Some(url); self } /// Returns a reference to the internal error, if available. /// /// The `'static` bounds allows using `downcast_ref` to check the /// details of the error. /// /// # Examples /// /// ``` /// extern crate url; /// # extern crate reqwest; /// // retries requests with no host on localhost /// # fn run() { /// let invalid_request = "http://"; /// let mut response = reqwest::get(invalid_request); /// if let Err(e) = response { /// match e.get_ref().and_then(|e| e.downcast_ref::()) { /// Some(&url::ParseError::EmptyHost) => { /// let valid_request = format!("{}{}",invalid_request, "localhost"); /// response = reqwest::get(&valid_request); /// }, /// _ => (), /// } /// } /// # } /// # fn main() {} /// ``` #[inline] pub fn get_ref(&self) -> Option<&(dyn StdError + Send + Sync + 'static)> { match self.inner.kind { Kind::Http(ref e) => Some(e), Kind::Hyper(ref e) => Some(e), Kind::Mime(ref e) => Some(e), Kind::Url(ref e) => Some(e), #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] Kind::TlsIncompatible => None, #[cfg(feature = "default-tls")] Kind::NativeTls(ref e) => Some(e), #[cfg(feature = "rustls-tls")] Kind::Rustls(ref e) => Some(e), #[cfg(feature = "trust-dns")] Kind::DnsSystemConf(ref e) => Some(e), Kind::Io(ref e) => Some(e), Kind::UrlEncoded(ref e) => Some(e), Kind::Json(ref e) => Some(e), Kind::UrlBadScheme | Kind::TooManyRedirects | Kind::RedirectLoop | Kind::Status(_) | Kind::UnknownProxyScheme | Kind::Timer => None, } } /// Returns true if the error is related to HTTP. #[inline] pub fn is_http(&self) -> bool { match self.inner.kind { Kind::Http(_) => true, Kind::Hyper(_) => true, _ => false, } } /// Returns true if the error is related to a timeout. pub fn is_timeout(&self) -> bool { match self.inner.kind { Kind::Io(ref io) => io.kind() == io::ErrorKind::TimedOut, Kind::Hyper(ref error) => { error .source() .and_then(|cause| { cause.downcast_ref::() }) .map(|io| io.kind() == io::ErrorKind::TimedOut) .unwrap_or(false) }, _ => false, } } /// Returns true if the error is serialization related. #[inline] pub fn is_serialization(&self) -> bool { match self.inner.kind { Kind::Json(_) | Kind::UrlEncoded(_) => true, _ => false, } } /// Returns true if the error is from a `RedirectPolicy`. #[inline] pub fn is_redirect(&self) -> bool { match self.inner.kind { Kind::TooManyRedirects | Kind::RedirectLoop => true, _ => false, } } /// Returns true if the error is from a request returning a 4xx error. #[inline] pub fn is_client_error(&self) -> bool { match self.inner.kind { Kind::Status(code) => code.is_client_error(), _ => false, } } /// Returns true if the error is from a request returning a 5xx error. #[inline] pub fn is_server_error(&self) -> bool { match self.inner.kind { Kind::Status(code) => code.is_server_error(), _ => false, } } /// Returns the status code, if the error was generated from a response. #[inline] pub fn status(&self) -> Option { match self.inner.kind { Kind::Status(code) => Some(code), _ => None, } } } impl fmt::Debug for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref url) = self.inner.url { f.debug_tuple("Error") .field(&self.inner.kind) .field(url) .finish() } else { f.debug_tuple("Error") .field(&self.inner.kind) .finish() } } } impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref url) = self.inner.url { try!(fmt::Display::fmt(url, f)); try!(f.write_str(": ")); } match self.inner.kind { Kind::Http(ref e) => fmt::Display::fmt(e, f), Kind::Hyper(ref e) => fmt::Display::fmt(e, f), Kind::Mime(ref e) => fmt::Display::fmt(e, f), Kind::Url(ref e) => fmt::Display::fmt(e, f), Kind::UrlBadScheme => f.write_str("URL scheme is not allowed"), #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] Kind::TlsIncompatible => f.write_str("Incompatible TLS identity type"), #[cfg(feature = "default-tls")] Kind::NativeTls(ref e) => fmt::Display::fmt(e, f), #[cfg(feature = "rustls-tls")] Kind::Rustls(ref e) => fmt::Display::fmt(e, f), #[cfg(feature = "trust-dns")] Kind::DnsSystemConf(ref e) => { write!(f, "failed to load DNS system conf: {}", e) }, Kind::Io(ref e) => fmt::Display::fmt(e, f), Kind::UrlEncoded(ref e) => fmt::Display::fmt(e, f), Kind::Json(ref e) => fmt::Display::fmt(e, f), Kind::TooManyRedirects => f.write_str("Too many redirects"), Kind::RedirectLoop => f.write_str("Infinite redirect loop"), Kind::Status(ref code) => { let prefix = if code.is_client_error() { "Client Error" } else if code.is_server_error() { "Server Error" } else { unreachable!("non-error status code: {:?}", code); }; write!(f, "{}: {}", prefix, code) } Kind::UnknownProxyScheme => f.write_str("Unknown proxy scheme"), Kind::Timer => f.write_str("timer unavailable"), } } } impl StdError for Error { fn description(&self) -> &str { match self.inner.kind { Kind::Http(ref e) => e.description(), Kind::Hyper(ref e) => e.description(), Kind::Mime(ref e) => e.description(), Kind::Url(ref e) => e.description(), Kind::UrlBadScheme => "URL scheme is not allowed", #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] Kind::TlsIncompatible => "Incompatible TLS identity type", #[cfg(feature = "default-tls")] Kind::NativeTls(ref e) => e.description(), #[cfg(feature = "rustls-tls")] Kind::Rustls(ref e) => e.description(), #[cfg(feature = "trust-dns")] Kind::DnsSystemConf(_) => "failed to load DNS system conf", Kind::Io(ref e) => e.description(), Kind::UrlEncoded(ref e) => e.description(), Kind::Json(ref e) => e.description(), Kind::TooManyRedirects => "Too many redirects", Kind::RedirectLoop => "Infinite redirect loop", Kind::Status(code) => { if code.is_client_error() { "Client Error" } else if code.is_server_error() { "Server Error" } else { unreachable!("non-error status code: {:?}", code); } } Kind::UnknownProxyScheme => "Unknown proxy scheme", Kind::Timer => "timer unavailable", } } // Keep this for now, as std::io::Error didn't support source() until 1.35 #[allow(deprecated)] fn cause(&self) -> Option<&dyn StdError> { match self.inner.kind { Kind::Http(ref e) => e.cause(), Kind::Hyper(ref e) => e.cause(), Kind::Mime(ref e) => e.cause(), Kind::Url(ref e) => e.cause(), #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] Kind::TlsIncompatible => None, #[cfg(feature = "default-tls")] Kind::NativeTls(ref e) => e.cause(), #[cfg(feature = "rustls-tls")] Kind::Rustls(ref e) => e.cause(), #[cfg(feature = "trust-dns")] Kind::DnsSystemConf(ref e) => e.cause(), Kind::Io(ref e) => e.cause(), Kind::UrlEncoded(ref e) => e.cause(), Kind::Json(ref e) => e.cause(), Kind::UrlBadScheme | Kind::TooManyRedirects | Kind::RedirectLoop | Kind::Status(_) | Kind::UnknownProxyScheme | Kind::Timer => None, } } fn source(&self) -> Option<&(dyn StdError + 'static)> { match self.inner.kind { Kind::Http(ref e) => e.source(), Kind::Hyper(ref e) => e.source(), Kind::Mime(ref e) => e.source(), Kind::Url(ref e) => e.source(), #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] Kind::TlsIncompatible => None, #[cfg(feature = "default-tls")] Kind::NativeTls(ref e) => e.source(), #[cfg(feature = "rustls-tls")] Kind::Rustls(ref e) => e.source(), #[cfg(feature = "trust-dns")] Kind::DnsSystemConf(ref e) => e.source(), Kind::Io(ref e) => e.source(), Kind::UrlEncoded(ref e) => e.source(), Kind::Json(ref e) => e.source(), Kind::UrlBadScheme | Kind::TooManyRedirects | Kind::RedirectLoop | Kind::Status(_) | Kind::UnknownProxyScheme | Kind::Timer => None, } } } #[derive(Debug)] pub(crate) enum Kind { Http(::http::Error), Hyper(::hyper::Error), Mime(::mime::FromStrError), Url(::url::ParseError), UrlBadScheme, #[cfg(all(feature = "default-tls", feature = "rustls-tls"))] TlsIncompatible, #[cfg(feature = "default-tls")] NativeTls(::native_tls::Error), #[cfg(feature = "rustls-tls")] Rustls(::rustls::TLSError), #[cfg(feature = "trust-dns")] DnsSystemConf(io::Error), Io(io::Error), UrlEncoded(::serde_urlencoded::ser::Error), Json(::serde_json::Error), TooManyRedirects, RedirectLoop, Status(StatusCode), UnknownProxyScheme, Timer, } impl From<::http::Error> for Kind { #[inline] fn from(err: ::http::Error) -> Kind { Kind::Http(err) } } impl From<::hyper::Error> for Kind { #[inline] fn from(err: ::hyper::Error) -> Kind { Kind::Hyper(err) } } impl From<::mime::FromStrError> for Kind { #[inline] fn from(err: ::mime::FromStrError) -> Kind { Kind::Mime(err) } } impl From for Kind { #[inline] fn from(err: io::Error) -> Kind { Kind::Io(err) } } impl From<::url::ParseError> for Kind { #[inline] fn from(err: ::url::ParseError) -> Kind { Kind::Url(err) } } impl From<::serde_urlencoded::ser::Error> for Kind { #[inline] fn from(err: ::serde_urlencoded::ser::Error) -> Kind { Kind::UrlEncoded(err) } } impl From<::serde_json::Error> for Kind { #[inline] fn from(err: ::serde_json::Error) -> Kind { Kind::Json(err) } } #[cfg(feature = "default-tls")] impl From<::native_tls::Error> for Kind { fn from(err: ::native_tls::Error) -> Kind { Kind::NativeTls(err) } } #[cfg(feature = "rustls-tls")] impl From<::rustls::TLSError> for Kind { fn from(err: ::rustls::TLSError) -> Kind { Kind::Rustls(err) } } impl From<::wait::Waited> for Kind where T: Into { fn from(err: ::wait::Waited) -> Kind { match err { ::wait::Waited::TimedOut => io_timeout().into(), ::wait::Waited::Err(e) => e.into(), } } } impl From<::tokio::timer::Error> for Kind { fn from(_err: ::tokio::timer::Error) -> Kind { Kind::Timer } } fn io_timeout() -> io::Error { io::Error::new(io::ErrorKind::TimedOut, "timed out") } #[allow(missing_debug_implementations)] pub(crate) struct InternalFrom(pub T, pub Option); #[doc(hidden)] // https://github.com/rust-lang/rust/issues/42323 impl From> for Error { #[inline] fn from(other: InternalFrom) -> Error { other.0 } } #[doc(hidden)] // https://github.com/rust-lang/rust/issues/42323 impl From> for Error where T: Into, { #[inline] fn from(other: InternalFrom) -> Error { Error::new(other.0.into(), other.1) } } pub(crate) fn from(err: T) -> Error where T: Into, { InternalFrom(err, None).into() } pub(crate) fn into_io(e: Error) -> io::Error { match e.inner.kind { Kind::Io(io) => io, _ => io::Error::new(io::ErrorKind::Other, e), } } pub(crate) fn from_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 { from(e) } } macro_rules! try_ { ($e:expr) => ( match $e { Ok(v) => v, Err(err) => { return Err(::error::from(err)); } } ); ($e:expr, $url:expr) => ( match $e { Ok(v) => v, Err(err) => { return Err(::Error::from(::error::InternalFrom(err, Some($url.clone())))); } } ) } macro_rules! try_io { ($e:expr) => ( match $e { Ok(v) => v, Err(ref err) if err.kind() == ::std::io::ErrorKind::WouldBlock => { return Ok(::futures::Async::NotReady); } Err(err) => { return Err(::error::from_io(err)); } } ) } pub(crate) fn loop_detected(url: Url) -> Error { Error::new(Kind::RedirectLoop, Some(url)) } pub(crate) fn too_many_redirects(url: Url) -> Error { Error::new(Kind::TooManyRedirects, Some(url)) } pub(crate) fn timedout(url: Option) -> Error { Error::new(Kind::Io(io_timeout()), url) } pub(crate) fn status_code(url: Url, status: StatusCode) -> Error { Error::new(Kind::Status(status), Some(url)) } pub(crate) fn url_bad_scheme(url: Url) -> Error { Error::new(Kind::UrlBadScheme, Some(url)) } #[cfg(feature = "trust-dns")] pub(crate) fn dns_system_conf(io: io::Error) -> Error { Error::new(Kind::DnsSystemConf(io), None) } pub(crate) fn unknown_proxy_scheme() -> Error { Error::new(Kind::UnknownProxyScheme, None) } #[cfg(test)] mod tests { use super::*; #[allow(deprecated)] #[test] fn test_cause_chain() { #[derive(Debug)] struct Chain(Option); impl fmt::Display for Chain { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(ref link) = self.0 { write!(f, "chain: {}", link) } else { f.write_str("root") } } } impl StdError for Chain { fn description(&self) -> &str { if self.0.is_some() { "chain" } else { "root" } } fn cause(&self) -> Option<&dyn StdError> { if let Some(ref e) = self.0 { Some(e) } else { None } } } let root = Chain(None::); let io = ::std::io::Error::new(::std::io::ErrorKind::Other, root); let err = Error::new(Kind::Io(io), None); assert!(err.cause().is_none()); assert_eq!(err.to_string(), "root"); let root = ::std::io::Error::new(::std::io::ErrorKind::Other, Chain(None::)); let link = Chain(Some(root)); let io = ::std::io::Error::new(::std::io::ErrorKind::Other, link); let err = Error::new(Kind::Io(io), None); assert!(err.cause().is_some()); assert_eq!(err.to_string(), "chain: root"); } #[test] fn mem_size_of() { use std::mem::size_of; assert_eq!(size_of::(), size_of::()); } #[test] fn roundtrip_io_error() { let orig = unknown_proxy_scheme(); // Convert reqwest::Error into an io::Error... let io = into_io(orig); // Convert that io::Error back into a reqwest::Error... let err = from_io(io); // It should have pulled out the original, not nested it... match err.inner.kind { Kind::UnknownProxyScheme => (), _ => panic!("{:?}", err), } } }