diff --git a/src/async_impl/body.rs b/src/async_impl/body.rs index c89c790..dcfbf44 100644 --- a/src/async_impl/body.rs +++ b/src/async_impl/body.rs @@ -154,11 +154,11 @@ impl Stream for ImplStream { } => { if let Some(ref mut timeout) = timeout { if let Poll::Ready(()) = Pin::new(timeout).poll(cx) { - return Poll::Ready(Some(Err(crate::error::timedout(None)))); + return Poll::Ready(Some(Err(crate::error::body(crate::error::TimedOut)))); } } futures_core::ready!(Pin::new(body).poll_data(cx)) - .map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::from)) + .map(|opt_chunk| opt_chunk.map(Into::into).map_err(crate::error::body)) } Inner::Reusable(ref mut bytes) => { if bytes.is_empty() { diff --git a/src/async_impl/client.rs b/src/async_impl/client.rs index 9da4b6d..120b873 100644 --- a/src/async_impl/client.rs +++ b/src/async_impl/client.rs @@ -746,13 +746,17 @@ impl Future for PendingRequest { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if let Some(delay) = self.as_mut().timeout().as_mut().as_pin_mut() { if let Poll::Ready(()) = delay.poll(cx) { - return Poll::Ready(Err(crate::error::timedout(Some(self.url.clone())))); + return Poll::Ready(Err( + crate::error::request(crate::error::TimedOut).with_url(self.url.clone()) + )); } } loop { let res = match self.as_mut().in_flight().as_mut().poll(cx) { - Poll::Ready(Err(e)) => return Poll::Ready(url_error!(e, &self.url)), + Poll::Ready(Err(e)) => { + return Poll::Ready(Err(crate::error::request(e).with_url(self.url.clone()))); + } Poll::Ready(Ok(res)) => res, Poll::Pending => return Poll::Pending, }; diff --git a/src/async_impl/decoder.rs b/src/async_impl/decoder.rs index ece367e..e28049f 100644 --- a/src/async_impl/decoder.rs +++ b/src/async_impl/decoder.rs @@ -137,14 +137,14 @@ impl Stream for Decoder { let new_value = match self.inner { Inner::Pending(ref mut future) => match Pin::new(future).poll(cx) { Poll::Ready(Ok(inner)) => inner, - Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(crate::error::from_io(e)))), + Poll::Ready(Err(e)) => return Poll::Ready(Some(Err(crate::error::decode_io(e)))), Poll::Pending => return Poll::Pending, }, Inner::PlainText(ref mut body) => return Pin::new(body).poll_next(cx), Inner::Gzip(ref mut decoder) => { return match futures_core::ready!(Pin::new(decoder).poll_next(cx)) { Some(Ok(bytes)) => Poll::Ready(Some(Ok(bytes))), - Some(Err(err)) => Poll::Ready(Some(Err(crate::error::from_io(err)))), + Some(Err(err)) => Poll::Ready(Some(Err(crate::error::decode_io(err)))), None => Poll::Ready(None), } } diff --git a/src/async_impl/multipart.rs b/src/async_impl/multipart.rs index 1294385..0287f26 100644 --- a/src/async_impl/multipart.rs +++ b/src/async_impl/multipart.rs @@ -203,7 +203,7 @@ impl Part { /// Tries to set the mime of this part. pub fn mime_str(self, mime: &str) -> crate::Result { - Ok(self.mime(mime.parse().map_err(crate::error::from)?)) + Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) } // Re-export when mime 0.4 is available, with split MediaType/MediaRange. diff --git a/src/async_impl/request.rs b/src/async_impl/request.rs index e81418c..53be49b 100644 --- a/src/async_impl/request.rs +++ b/src/async_impl/request.rs @@ -111,9 +111,9 @@ impl RequestBuilder { Ok(value) => { req.headers_mut().append(key, value); } - Err(e) => error = Some(crate::error::from(e.into())), + Err(e) => error = Some(crate::error::builder(e.into())), }, - Err(e) => error = Some(crate::error::from(e.into())), + Err(e) => error = Some(crate::error::builder(e.into())), }; } if let Some(err) = error { @@ -250,7 +250,7 @@ impl RequestBuilder { let serializer = serde_urlencoded::Serializer::new(&mut pairs); if let Err(err) = query.serialize(serializer) { - error = Some(crate::error::from(err)); + error = Some(crate::error::builder(err)); } } if let Ok(ref mut req) = self.request { @@ -276,7 +276,7 @@ impl RequestBuilder { ); *req.body_mut() = Some(body.into()); } - Err(err) => error = Some(crate::error::from(err)), + Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { @@ -300,7 +300,7 @@ impl RequestBuilder { .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); *req.body_mut() = Some(body.into()); } - Err(err) => error = Some(crate::error::from(err)), + Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { diff --git a/src/async_impl/response.rs b/src/async_impl/response.rs index f9efc06..8617584 100644 --- a/src/async_impl/response.rs +++ b/src/async_impl/response.rs @@ -227,7 +227,7 @@ impl Response { pub async fn json(self) -> crate::Result { let full = self.bytes().await?; - serde_json::from_slice(&full).map_err(crate::error::from) + serde_json::from_slice(&full).map_err(crate::error::decode) } /// Get the full response body as `Bytes`. diff --git a/src/blocking/body.rs b/src/blocking/body.rs index 8519814..4b48823 100644 --- a/src/blocking/body.rs +++ b/src/blocking/body.rs @@ -258,9 +258,8 @@ async fn send_future(sender: Sender) -> Result<(), crate::Error> { buf.advance_mut(n); }, Err(e) => { - let ret = io::Error::new(e.kind(), e.to_string()); tx.take().expect("tx only taken on error").abort(); - return Err(crate::error::from(ret)); + return Err(crate::error::body(e)); } } } @@ -273,7 +272,7 @@ async fn send_future(sender: Sender) -> Result<(), crate::Error> { .expect("tx only taken on error") .send_data(buf.take().freeze().into()) .await - .map_err(crate::error::from)?; + .map_err(crate::error::body)?; written += buf_len; } diff --git a/src/blocking/client.rs b/src/blocking/client.rs index ed47185..ab4379d 100644 --- a/src/blocking/client.rs +++ b/src/blocking/client.rs @@ -550,7 +550,7 @@ impl ClientHandle { .spawn(move || { use tokio::runtime::current_thread::Runtime; - let mut rt = match Runtime::new().map_err(crate::error::from) { + let mut rt = match Runtime::new().map_err(crate::error::builder) { Err(e) => { if let Err(e) = spawn_tx.send(Err(e)) { error!("Failed to communicate runtime creation failure: {:?}", e); @@ -587,7 +587,7 @@ impl ClientHandle { rt.block_on(f) }) - .map_err(crate::error::from)?; + .map_err(crate::error::builder)?; // Wait for the runtime thread to start up... match wait::timeout(spawn_rx, None) { @@ -639,8 +639,8 @@ impl ClientHandle { self.timeout.0, KeepCoreThreadAlive(Some(self.inner.clone())), )), - Err(wait::Waited::TimedOut) => Err(crate::error::timedout(Some(url))), - Err(wait::Waited::Executor(err)) => Err(crate::error::from(err).with_url(url)), + Err(wait::Waited::TimedOut(e)) => Err(crate::error::request(e).with_url(url)), + Err(wait::Waited::Executor(err)) => Err(crate::error::request(err).with_url(url)), Err(wait::Waited::Inner(err)) => Err(err.with_url(url)), } } diff --git a/src/blocking/mod.rs b/src/blocking/mod.rs index 6d54f37..dab76f6 100644 --- a/src/blocking/mod.rs +++ b/src/blocking/mod.rs @@ -60,8 +60,6 @@ mod request; mod response; mod wait; -pub(crate) use self::wait::Waited; - pub use self::body::Body; pub use self::client::{Client, ClientBuilder}; pub use self::request::{Request, RequestBuilder}; diff --git a/src/blocking/multipart.rs b/src/blocking/multipart.rs index 0f1cecf..c0f65a9 100644 --- a/src/blocking/multipart.rs +++ b/src/blocking/multipart.rs @@ -234,7 +234,7 @@ impl Part { /// Tries to set the mime of this part. pub fn mime_str(self, mime: &str) -> crate::Result { - Ok(self.mime(mime.parse().map_err(crate::error::from)?)) + Ok(self.mime(mime.parse().map_err(crate::error::builder)?)) } // Re-export when mime 0.4 is available, with split MediaType/MediaRange. diff --git a/src/blocking/request.rs b/src/blocking/request.rs index 87f3a0d..6d236f6 100644 --- a/src/blocking/request.rs +++ b/src/blocking/request.rs @@ -149,9 +149,9 @@ impl RequestBuilder { Ok(value) => { req.headers_mut().append(key, value); } - Err(e) => error = Some(crate::error::from(e.into())), + Err(e) => error = Some(crate::error::builder(e.into())), }, - Err(e) => error = Some(crate::error::from(e.into())), + Err(e) => error = Some(crate::error::builder(e.into())), }; } if let Some(err) = error { @@ -323,7 +323,7 @@ impl RequestBuilder { let serializer = serde_urlencoded::Serializer::new(&mut pairs); if let Err(err) = query.serialize(serializer) { - error = Some(crate::error::from(err)); + error = Some(crate::error::builder(err)); } } if let Ok(ref mut req) = self.request { @@ -374,7 +374,7 @@ impl RequestBuilder { ); *req.body_mut() = Some(body.into()); } - Err(err) => error = Some(crate::error::from(err)), + Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { @@ -417,7 +417,7 @@ impl RequestBuilder { .insert(CONTENT_TYPE, HeaderValue::from_static("application/json")); *req.body_mut() = Some(body.into()); } - Err(err) => error = Some(crate::error::from(err)), + Err(err) => error = Some(crate::error::builder(err)), } } if let Some(err) = error { @@ -797,8 +797,9 @@ mod tests { #[test] fn add_json_fail() { - use serde::ser::Error; + use serde::ser::Error as _; use serde::{Serialize, Serializer}; + use std::error::Error as _; struct MyStruct; impl Serialize for MyStruct { fn serialize(&self, _serializer: S) -> Result @@ -813,7 +814,9 @@ mod tests { let some_url = "https://google.com/"; let r = client.post(some_url); let json_data = MyStruct; - assert!(r.json(&json_data).build().unwrap_err().is_serialization()); + let err = r.json(&json_data).build().unwrap_err(); + assert!(err.is_builder()); // well, duh ;) + assert!(err.source().unwrap().is::()); } #[test] diff --git a/src/blocking/response.rs b/src/blocking/response.rs index cefc15a..5827b53 100644 --- a/src/blocking/response.rs +++ b/src/blocking/response.rs @@ -205,8 +205,8 @@ impl Response { /// [`serde_json::from_reader`]: https://docs.serde.rs/serde_json/fn.from_reader.html pub fn json(self) -> crate::Result { wait::timeout(self.inner.json(), self.timeout).map_err(|e| match e { - wait::Waited::TimedOut => crate::error::timedout(None), - wait::Waited::Executor(e) => crate::error::from(e), + wait::Waited::TimedOut(e) => crate::error::decode(e), + wait::Waited::Executor(e) => crate::error::decode(e), wait::Waited::Inner(e) => e, }) } @@ -253,8 +253,8 @@ impl Response { pub fn text_with_charset(self, default_encoding: &str) -> crate::Result { wait::timeout(self.inner.text_with_charset(default_encoding), self.timeout).map_err(|e| { match e { - wait::Waited::TimedOut => crate::error::timedout(None), - wait::Waited::Executor(e) => crate::error::from(e), + wait::Waited::TimedOut(e) => crate::error::decode(e), + wait::Waited::Executor(e) => crate::error::decode(e), wait::Waited::Inner(e) => e, } }) @@ -284,7 +284,7 @@ impl Response { where W: io::Write, { - io::copy(self, w).map_err(crate::error::from) + io::copy(self, w).map_err(crate::error::response) } /// Turn a response into an error if the server returned an error. @@ -359,8 +359,8 @@ impl Read for Response { let timeout = self.timeout; wait::timeout(self.body_mut().read(buf), timeout).map_err(|e| match e { - wait::Waited::TimedOut => crate::error::timedout(None).into_io(), - wait::Waited::Executor(e) => crate::error::from(e).into_io(), + wait::Waited::TimedOut(e) => crate::error::response(e).into_io(), + wait::Waited::Executor(e) => crate::error::response(e).into_io(), wait::Waited::Inner(e) => e, }) } diff --git a/src/blocking/wait.rs b/src/blocking/wait.rs index bd00dc1..7698509 100644 --- a/src/blocking/wait.rs +++ b/src/blocking/wait.rs @@ -7,14 +7,14 @@ use tokio::clock; use tokio_executor::{ enter, park::{Park, ParkThread, Unpark, UnparkThread}, - EnterError, }; pub(crate) fn timeout(fut: F, timeout: Option) -> Result> where F: Future>, { - let _entered = enter().map_err(Waited::Executor)?; + let _entered = + enter().map_err(|_| Waited::Executor(crate::error::BlockingClientInAsyncContext))?; let deadline = timeout.map(|d| { log::trace!("wait at most {:?}", d); clock::now() + d @@ -39,7 +39,7 @@ where let now = clock::now(); if now >= deadline { log::trace!("wait timeout exceeded"); - return Err(Waited::TimedOut); + return Err(Waited::TimedOut(crate::error::TimedOut)); } log::trace!("park timeout {:?}", deadline - now); @@ -53,8 +53,8 @@ where #[derive(Debug)] pub(crate) enum Waited { - TimedOut, - Executor(EnterError), + TimedOut(crate::error::TimedOut), + Executor(crate::error::BlockingClientInAsyncContext), Inner(E), } diff --git a/src/connect.rs b/src/connect.rs index 022db1d..950a0b2 100644 --- a/src/connect.rs +++ b/src/connect.rs @@ -75,7 +75,7 @@ impl Connector { where T: Into>, { - let tls = tls.build().map_err(crate::error::from)?; + let tls = tls.build().map_err(crate::error::builder)?; let mut http = http_connector()?; http.set_local_address(local_addr.into()); diff --git a/src/error.rs b/src/error.rs index e18dc1a..89342c6 100644 --- a/src/error.rs +++ b/src/error.rs @@ -4,69 +4,33 @@ 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`. -/// -/// # Examples -/// -/// ``` -/// extern crate serde; -/// extern crate reqwest; -/// -/// use serde::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Simple { -/// key: String -/// } -/// # fn main() { } -/// -/// async fn run() { -/// match make_request().await { -/// Err(e) => handler(e), -/// Ok(_) => return, -/// } -/// } -/// // Response is not a json object conforming to the Simple struct -/// async fn make_request() -> Result { -/// reqwest::get("http://httpbin.org/ip").await?.json().await -/// } -/// -/// 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, } +type BoxError = Box; + struct Inner { kind: Kind, + source: Option, 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 { + pub(crate) fn new(kind: Kind, source: Option) -> Error + where + E: Into, + { Error { - inner: Box::new(Inner { kind, url }), + inner: Box::new(Inner { + kind, + source: source.map(Into::into), + url: None, + }), } } @@ -87,95 +51,14 @@ impl Error { /// } /// # } /// ``` - #[inline] pub fn url(&self) -> Option<&Url> { self.inner.url.as_ref() } - pub(crate) fn with_url(mut self, url: Url) -> Error { - 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 - /// # async fn run() { - /// let invalid_request = "http://"; - /// let mut response = reqwest::get(invalid_request).await; - /// 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).await; - /// }, - /// _ => (), - /// } - /// } - /// # } - /// # fn main() {} - /// ``` - pub fn get_ref(&self) -> Option<&(dyn StdError + Send + Sync + 'static)> { + /// Returns true if the error is from a type Builder. + pub fn is_builder(&self) -> bool { 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 - | Kind::BlockingClientInFutureContext => None, - } - } - - /// Returns true if the error is related to HTTP. - 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. - pub fn is_serialization(&self) -> bool { - match self.inner.kind { - Kind::Json(_) | Kind::UrlEncoded(_) => true, + Kind::Builder => true, _ => false, } } @@ -183,25 +66,22 @@ impl Error { /// Returns true if the error is from a `RedirectPolicy`. pub fn is_redirect(&self) -> bool { match self.inner.kind { - Kind::TooManyRedirects | Kind::RedirectLoop => true, + Kind::Redirect => true, _ => false, } } - /// Returns true if the error is from a request returning a 4xx error. - pub fn is_client_error(&self) -> bool { + /// Returns true if the error is from `Response::error_for_status`. + pub fn is_status(&self) -> bool { match self.inner.kind { - Kind::Status(code) => code.is_client_error(), + Kind::Status(_) => true, _ => false, } } - /// Returns true if the error is from a request returning a 5xx error. - pub fn is_server_error(&self) -> bool { - match self.inner.kind { - Kind::Status(code) => code.is_server_error(), - _ => false, - } + /// Returns true if the error is related to a timeout. + pub fn is_timeout(&self) -> bool { + self.source().map(|e| e.is::()).unwrap_or(false) } /// Returns the status code, if the error was generated from a response. @@ -212,363 +92,172 @@ impl Error { } } + // private + + pub(crate) fn with_url(mut self, url: Url) -> Error { + self.inner.url = Some(url); + self + } + pub(crate) fn into_io(self) -> io::Error { - match self.inner.kind { - Kind::Io(io) => io, - _ => io::Error::new(io::ErrorKind::Other, self), - } + 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 { - f.debug_tuple("Error") - .field(&self.inner.kind) - .field(url) - .finish() - } else { - f.debug_tuple("Error").field(&self.inner.kind).finish() + builder.field("url", url); } + if let Some(ref source) = self.inner.source { + builder.field("source", source); + } + + builder.finish() } } -static BLOCK_IN_FUTURE: &str = "blocking Client used inside a Future context"; - impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(ref url) = self.inner.url { - fmt::Display::fmt(url, f)?; - f.write_str(": ")?; + struct ForUrl<'a>(Option<&'a Url>); + + impl fmt::Display for ForUrl<'_> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + if let Some(url) = self.0 { + write!(f, " for url ({})", url.as_str()) + } else { + Ok(()) + } + } } + 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::Builder => f.write_str("builder error")?, + Kind::Request => f.write_str("error sending request")?, + Kind::Response => f.write_str("error reading response")?, + 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::Status(ref code) => { let prefix = if code.is_client_error() { - "Client Error" - } else if code.is_server_error() { - "Server Error" + "HTTP status client error" } else { - unreachable!("non-error status code: {:?}", code); + debug_assert!(code.is_server_error()); + "HTTP status server error" }; - write!(f, "{}: {}", prefix, code) + write!(f, "{} ({})", prefix, code)?; } - Kind::UnknownProxyScheme => f.write_str("Unknown proxy scheme"), - Kind::Timer => f.write_str("timer unavailable"), - Kind::BlockingClientInFutureContext => f.write_str(BLOCK_IN_FUTURE), + }; + + ForUrl(self.inner.url.as_ref()).fmt(f)?; + + if let Some(ref e) = self.inner.source { + write!(f, ": {}", e)?; } + + Ok(()) } } 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", - Kind::BlockingClientInFutureContext => BLOCK_IN_FUTURE, - } - } - - // 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 - | Kind::BlockingClientInFutureContext => 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 - | Kind::BlockingClientInFutureContext => None, - } + self.inner.source.as_ref().map(|e| &**e as _) } } #[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, + Builder, + Request, + Response, + Redirect, Status(StatusCode), - UnknownProxyScheme, - Timer, - BlockingClientInFutureContext, + Body, + Decode, } -impl From for Kind { - #[inline] - fn from(err: http::Error) -> Kind { - Kind::Http(err) - } +// constructors + +pub(crate) fn builder>(e: E) -> Error { + Error::new(Kind::Builder, Some(e)) } -impl From for Kind { - #[inline] - fn from(err: hyper::Error) -> Kind { - Kind::Hyper(err) - } +pub(crate) fn body>(e: E) -> Error { + Error::new(Kind::Body, Some(e)) } -impl From for Kind { - #[inline] - fn from(err: mime::FromStrError) -> Kind { - Kind::Mime(err) - } +pub(crate) fn decode>(e: E) -> Error { + Error::new(Kind::Decode, Some(e)) } -impl From for Kind { - #[inline] - fn from(err: io::Error) -> Kind { - Kind::Io(err) - } +pub(crate) fn request>(e: E) -> Error { + Error::new(Kind::Request, Some(e)) } -impl From for Kind { - #[inline] - fn from(err: url::ParseError) -> Kind { - Kind::Url(err) - } +pub(crate) fn response>(e: E) -> Error { + Error::new(Kind::Response, Some(e)) } -impl From for Kind { - #[inline] - fn from(err: serde_urlencoded::ser::Error) -> Kind { - Kind::UrlEncoded(err) - } +pub(crate) fn loop_detected(url: Url) -> Error { + Error::new(Kind::Redirect, Some("infinite redirect loop detected")).with_url(url) } -impl From for Kind { - #[inline] - fn from(err: serde_json::Error) -> Kind { - Kind::Json(err) - } +pub(crate) fn too_many_redirects(url: Url) -> Error { + Error::new(Kind::Redirect, Some("too many redirects")).with_url(url) } -#[cfg(feature = "default-tls")] -impl From for Kind { - fn from(err: native_tls::Error) -> Kind { - Kind::NativeTls(err) - } +pub(crate) fn status_code(url: Url, status: StatusCode) -> Error { + Error::new(Kind::Status(status), None::).with_url(url) } -#[cfg(feature = "rustls-tls")] -impl From for Kind { - fn from(err: rustls::TLSError) -> Kind { - Kind::Rustls(err) - } +pub(crate) fn url_bad_scheme(url: Url) -> Error { + Error::new(Kind::Builder, Some("URL scheme is not allowed")).with_url(url) } -impl From> for Kind -where - T: Into, -{ - fn from(err: crate::blocking::Waited) -> Kind { - match err { - crate::blocking::Waited::TimedOut => io_timeout().into(), - crate::blocking::Waited::Executor(e) => e.into(), - crate::blocking::Waited::Inner(e) => e.into(), - } - } -} - -impl From for Kind { - fn from(_err: tokio_executor::EnterError) -> Kind { - Kind::BlockingClientInFutureContext - } -} - -impl From 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() -} +// io::Error helpers pub(crate) fn into_io(e: Error) -> io::Error { e.into_io() } -pub(crate) fn from_io(e: io::Error) -> Error { +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 { - from(e) + decode(e) } } -macro_rules! url_error { - ($e:expr, $url:expr) => { - Err(crate::Error::from(crate::error::InternalFrom( - $e, - Some($url.clone()), - ))) - }; +// internal Error "sources" + +#[derive(Debug)] +pub(crate) struct TimedOut; + +#[derive(Debug)] +pub(crate) struct BlockingClientInAsyncContext; + +impl fmt::Display for TimedOut { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("operation timed out") + } } -pub(crate) fn loop_detected(url: Url) -> Error { - Error::new(Kind::RedirectLoop, Some(url)) +impl StdError for TimedOut {} + +impl fmt::Display for BlockingClientInAsyncContext { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("blocking Client used inside a Future context") + } } -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) -} +impl StdError for BlockingClientInAsyncContext {} #[cfg(test)] mod tests { @@ -577,51 +266,13 @@ mod tests { fn assert_send() {} fn assert_sync() {} - #[allow(deprecated)] #[test] - fn test_cause_chain() { - #[derive(Debug)] - struct Chain(Option); + fn test_source_chain() { + let root = Error::new(Kind::Request, None::); + assert!(root.source().is_none()); - 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"); + let link = super::response(root); + assert!(link.source().is_some()); assert_send::(); assert_sync::(); } @@ -634,14 +285,24 @@ mod tests { #[test] fn roundtrip_io_error() { - let orig = unknown_proxy_scheme(); + let orig = super::request("orig"); // Convert reqwest::Error into an io::Error... - let io = into_io(orig); + let io = super::into_io(orig); // Convert that io::Error back into a reqwest::Error... - let err = from_io(io); + let err = super::decode_io(io); // It should have pulled out the original, not nested it... match err.inner.kind { - Kind::UnknownProxyScheme => (), + 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), } } diff --git a/src/into_url.rs b/src/into_url.rs index 0a53ac7..3d57d32 100644 --- a/src/into_url.rs +++ b/src/into_url.rs @@ -27,7 +27,7 @@ impl PolyfillTryInto for Url { impl<'a> PolyfillTryInto for &'a str { fn into_url(self) -> crate::Result { - Url::parse(self).map_err(crate::error::from)?.into_url() + Url::parse(self).map_err(crate::error::builder)?.into_url() } } @@ -56,7 +56,7 @@ mod tests { let err = "file:///etc/hosts".into_url().unwrap_err(); assert_eq!( err.to_string(), - "file:///etc/hosts: URL scheme is not allowed" + "builder error for url (file:///etc/hosts): URL scheme is not allowed" ); } } diff --git a/src/proxy.rs b/src/proxy.rs index f2818ef..8bb56bb 100644 --- a/src/proxy.rs +++ b/src/proxy.rs @@ -350,7 +350,7 @@ impl ProxyScheme { "socks5" => Self::socks5(to_addr()?)?, #[cfg(feature = "socks")] "socks5h" => Self::socks5h(to_addr()?)?, - _ => return Err(crate::error::unknown_proxy_scheme()), + _ => return Err(crate::error::builder("unknown proxy scheme")), }; if let Some(pwd) = url.password() { diff --git a/src/tls.rs b/src/tls.rs index b844d1f..c53d010 100644 --- a/src/tls.rs +++ b/src/tls.rs @@ -55,7 +55,7 @@ impl Certificate { pub fn from_der(der: &[u8]) -> crate::Result { Ok(Certificate { #[cfg(feature = "default-tls")] - native: native_tls::Certificate::from_der(der).map_err(crate::error::from)?, + native: native_tls::Certificate::from_der(der).map_err(crate::error::builder)?, #[cfg(feature = "rustls-tls")] original: Cert::Der(der.to_owned()), }) @@ -80,7 +80,7 @@ impl Certificate { pub fn from_pem(pem: &[u8]) -> crate::Result { Ok(Certificate { #[cfg(feature = "default-tls")] - native: native_tls::Certificate::from_pem(pem).map_err(crate::error::from)?, + native: native_tls::Certificate::from_pem(pem).map_err(crate::error::builder)?, #[cfg(feature = "rustls-tls")] original: Cert::Pem(pem.to_owned()), }) @@ -100,18 +100,18 @@ impl Certificate { Cert::Der(buf) => tls .root_store .add(&::rustls::Certificate(buf)) - .map_err(|e| crate::error::from(TLSError::WebPKIError(e)))?, + .map_err(|e| crate::error::builder(TLSError::WebPKIError(e)))?, Cert::Pem(buf) => { let mut pem = Cursor::new(buf); let certs = pemfile::certs(&mut pem).map_err(|_| { - crate::error::from(TLSError::General(String::from( + crate::error::builder(TLSError::General(String::from( "No valid certificate was found", ))) })?; for c in certs { tls.root_store .add(&c) - .map_err(|e| crate::error::from(TLSError::WebPKIError(e)))?; + .map_err(|e| crate::error::builder(TLSError::WebPKIError(e)))?; } } } @@ -151,7 +151,7 @@ impl Identity { pub fn from_pkcs12_der(der: &[u8], password: &str) -> crate::Result { Ok(Identity { inner: ClientCert::Pkcs12( - native_tls::Identity::from_pkcs12(der, password).map_err(crate::error::from)?, + native_tls::Identity::from_pkcs12(der, password).map_err(crate::error::builder)?, ), }) } @@ -184,7 +184,7 @@ impl Identity { let mut pem = Cursor::new(buf); let certs = pemfile::certs(&mut pem) .map_err(|_| TLSError::General(String::from("No valid certificate was found"))) - .map_err(crate::error::from)?; + .map_err(crate::error::builder)?; pem.set_position(0); let mut sk = pemfile::pkcs8_private_keys(&mut pem) .and_then(|pkcs8_keys| { @@ -199,11 +199,11 @@ impl Identity { pemfile::rsa_private_keys(&mut pem) }) .map_err(|_| TLSError::General(String::from("No valid private key was found"))) - .map_err(crate::error::from)?; + .map_err(crate::error::builder)?; if let (Some(sk), false) = (sk.pop(), certs.is_empty()) { (sk, certs) } else { - return Err(crate::error::from(TLSError::General(String::from( + return Err(crate::error::builder(TLSError::General(String::from( "private key or certificate not found", )))); } @@ -225,7 +225,7 @@ impl Identity { Ok(()) } #[cfg(feature = "rustls-tls")] - ClientCert::Pem { .. } => Err(crate::error::from(crate::error::Kind::TlsIncompatible)), + ClientCert::Pem { .. } => Err(crate::error::builder("incompatible TLS identity type")), } } @@ -237,7 +237,7 @@ impl Identity { Ok(()) } #[cfg(feature = "default-tls")] - ClientCert::Pkcs12(..) => Err(crate::error::from(crate::error::Kind::TlsIncompatible)), + ClientCert::Pkcs12(..) => Err(crate::error::builder("incompatible TLS identity type")), } } } diff --git a/tests/blocking.rs b/tests/blocking.rs index 646cd6e..17ca03d 100644 --- a/tests/blocking.rs +++ b/tests/blocking.rs @@ -270,8 +270,8 @@ fn test_error_for_status_4xx() { let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); - let err = res.error_for_status().err().unwrap(); - assert!(err.is_client_error()); + let err = res.error_for_status().unwrap_err(); + assert!(err.is_status()); assert_eq!(err.status(), Some(reqwest::StatusCode::BAD_REQUEST)); } @@ -299,8 +299,8 @@ fn test_error_for_status_5xx() { let url = format!("http://{}/1", server.addr()); let res = reqwest::blocking::get(&url).unwrap(); - let err = res.error_for_status().err().unwrap(); - assert!(err.is_server_error()); + let err = res.error_for_status().unwrap_err(); + assert!(err.is_status()); assert_eq!( err.status(), Some(reqwest::StatusCode::INTERNAL_SERVER_ERROR) diff --git a/tests/timeouts.rs b/tests/timeouts.rs index 94d714c..320796f 100644 --- a/tests/timeouts.rs +++ b/tests/timeouts.rs @@ -38,7 +38,7 @@ fn timeout_closes_connection() { let url = format!("http://{}/closes", server.addr()); let err = client.get(&url).send().unwrap_err(); - assert_eq!(err.get_ref().unwrap().to_string(), "timed out"); + assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } @@ -84,7 +84,7 @@ fn write_timeout_large_body() { .send() .unwrap_err(); - assert_eq!(err.get_ref().unwrap().to_string(), "timed out"); + assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } @@ -116,7 +116,7 @@ fn test_response_timeout() { .send() .unwrap_err(); - assert_eq!(err.get_ref().unwrap().to_string(), "timed out"); + assert!(err.is_timeout()); assert_eq!(err.url().map(|u| u.as_str()), Some(url.as_str())); } @@ -158,5 +158,5 @@ fn test_read_timeout() { ); let err = res.text().unwrap_err(); - assert_eq!(err.to_string(), "timed out"); + assert!(err.is_timeout()); }