From 855e6615eb553feab5d382c2017ad76e82560768 Mon Sep 17 00:00:00 2001 From: Tom Prince Date: Fri, 19 May 2017 15:11:39 -0600 Subject: [PATCH] Add error_for_status. This makes it it easy to turn error responses into error results. --- src/async_impl/response.rs | 34 +++++++++++++++++++ src/error.rs | 67 +++++++++++++++++++++++++++++++++++--- src/response.rs | 28 ++++++++++++++++ tests/client.rs | 58 +++++++++++++++++++++++++++++++++ 4 files changed, 183 insertions(+), 4 deletions(-) diff --git a/src/async_impl/response.rs b/src/async_impl/response.rs index aaaa3a9..c8e3835 100644 --- a/src/async_impl/response.rs +++ b/src/async_impl/response.rs @@ -59,6 +59,40 @@ impl Response { _marker: PhantomData, } } + + /// Turn a response into an error if the server returned an error. + // XXX: example disabled since rustdoc still tries to run it + // when the 'unstable' feature isn't active, making the import + // fail. + // + // # Example + // + // ``` + // # use reqwest::unstable::async::Response; + // fn on_response(res: Response) { + // match res.error_for_status() { + // Ok(_res) => (), + // Err(err) => { + // // asserting a 400 as an example + // // it could be any status between 400...599 + // assert_eq!( + // err.status(), + // Some(reqwest::StatusCode::BadRequest) + // ); + // } + // } + // } + // ``` + #[inline] + pub fn error_for_status(self) -> ::Result { + if self.status.is_client_error() { + Err(::error::client_error(self.url, self.status)) + } else if self.status.is_server_error() { + Err(::error::server_error(self.url, self.status)) + } else { + Ok(self) + } + } } diff --git a/src/error.rs b/src/error.rs index 22769f2..dfac073 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,7 +2,7 @@ use std::error::Error as StdError; use std::fmt; use std::io; -use Url; +use {StatusCode, Url}; /// The Errors that may occur when processing a `Request`. /// @@ -118,7 +118,9 @@ impl Error { Kind::UrlEncoded(ref e) => Some(e), Kind::Json(ref e) => Some(e), Kind::TooManyRedirects | - Kind::RedirectLoop => None, + Kind::RedirectLoop | + Kind::ClientError(_) | + Kind::ServerError(_) => None, } } @@ -150,6 +152,34 @@ impl Error { _ => false, } } + + /// Returns true if the error is from a request returning a 4xx error. + #[inline] + pub fn is_client_error(&self) -> bool { + match self.kind { + Kind::ClientError(_) => true, + _ => false, + } + } + + /// Returns true if the error is from a request returning a 5xx error. + #[inline] + pub fn is_server_error(&self) -> bool { + match self.kind { + Kind::ServerError(_) => true, + _ => false, + } + } + + /// Returns the status code, if the error was generated from a response. + #[inline] + pub fn status(&self) -> Option { + match self.kind { + Kind::ClientError(code) | + Kind::ServerError(code) => Some(code), + _ => None, + } + } } impl fmt::Display for Error { @@ -167,6 +197,14 @@ impl fmt::Display for Error { 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::ClientError(ref code) => { + f.write_str("Client Error: ")?; + fmt::Display::fmt(code, f) + } + Kind::ServerError(ref code) => { + f.write_str("Server Error: ")?; + fmt::Display::fmt(code, f) + } } } } @@ -182,6 +220,8 @@ impl StdError for Error { Kind::Json(ref e) => e.description(), Kind::TooManyRedirects => "Too many redirects", Kind::RedirectLoop => "Infinite redirect loop", + Kind::ClientError(_) => "Client Error", + Kind::ServerError(_) => "Server Error", } } @@ -194,7 +234,9 @@ impl StdError for Error { Kind::UrlEncoded(ref e) => e.cause(), Kind::Json(ref e) => e.cause(), Kind::TooManyRedirects | - Kind::RedirectLoop => None, + Kind::RedirectLoop | + Kind::ClientError(_) | + Kind::ServerError(_) => None, } } } @@ -211,6 +253,8 @@ pub enum Kind { Json(::serde_json::Error), TooManyRedirects, RedirectLoop, + ClientError(StatusCode), + ServerError(StatusCode), } @@ -374,6 +418,22 @@ pub fn timedout(url: Option) -> Error { } } +#[inline] +pub fn client_error(url: Url, status: StatusCode) -> Error { + Error { + kind: Kind::ClientError(status), + url: Some(url), + } +} + +#[inline] +pub fn server_error(url: Url, status: StatusCode) -> Error { + Error { + kind: Kind::ServerError(status), + url: Some(url), + } +} + #[cfg(test)] mod tests { use super::*; @@ -438,7 +498,6 @@ mod tests { let link = Chain(Some(root)); let io = ::std::io::Error::new(::std::io::ErrorKind::Other, link); let err = Error { kind: Kind::Io(io), url: None }; - assert!(err.cause().is_some()); assert_eq!(err.to_string(), "chain: root"); } diff --git a/src/response.rs b/src/response.rs index 05a9c63..749522e 100644 --- a/src/response.rs +++ b/src/response.rs @@ -156,6 +156,34 @@ impl Response { // Went for easier for now, just to get it working. serde_json::from_reader(self).map_err(::error::from) } + + /// Turn a response into an error if the server returned an error. + /// + /// # Example + /// + /// ```rust,no_run + /// # extern crate reqwest; + /// # fn run() -> Result<(), Box<::std::error::Error>> { + /// let res = reqwest::get("http://httpbin.org/status/400")? + /// .error_for_status(); + /// if let Err(err) = res { + /// assert_eq!(err.status(), Some(reqwest::StatusCode::BadRequest)); + /// } + /// # Ok(()) + /// # } + /// # fn main() {} + /// ``` + #[inline] + pub fn error_for_status(self) -> ::Result { + let Response { body, inner, _thread_handle } = self; + inner.error_for_status().map(move |inner| { + Response { + body: body, + inner: inner, + _thread_handle: _thread_handle, + } + }) + } } impl Read for Response { diff --git a/tests/client.rs b/tests/client.rs index a0012b1..979c29a 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -80,3 +80,61 @@ fn test_post() { let n = res.read(&mut buf).unwrap(); assert_eq!(n, 0) } + +/// Calling `Response::error_for_status`` on a response with status in 4xx +/// returns a error. +#[test] +fn test_error_for_status_4xx() { + let server = server! { + request: b"\ + GET /1 HTTP/1.1\r\n\ + Host: $HOST\r\n\ + User-Agent: $USERAGENT\r\n\ + Accept: */*\r\n\ + Accept-Encoding: gzip\r\n\ + \r\n\ + ", + response: b"\ + HTTP/1.1 400 OK\r\n\ + Server: test\r\n\ + Content-Length: 0\r\n\ + \r\n\ + " + }; + + let url = format!("http://{}/1", server.addr()); + let res = reqwest::get(&url).unwrap(); + + let err = res.error_for_status().err().unwrap(); + assert!(err.is_client_error()); + assert_eq!(err.status(), Some(reqwest::StatusCode::BadRequest)); +} + +/// Calling `Response::error_for_status`` on a response with status in 5xx +/// returns a error. +#[test] +fn test_error_for_status_5xx() { + let server = server! { + request: b"\ + GET /1 HTTP/1.1\r\n\ + Host: $HOST\r\n\ + User-Agent: $USERAGENT\r\n\ + Accept: */*\r\n\ + Accept-Encoding: gzip\r\n\ + \r\n\ + ", + response: b"\ + HTTP/1.1 500 OK\r\n\ + Server: test\r\n\ + Content-Length: 0\r\n\ + \r\n\ + " + }; + + let url = format!("http://{}/1", server.addr()); + let res = reqwest::get(&url).unwrap(); + + let err = res.error_for_status().err().unwrap(); + assert!(err.is_server_error()); + assert_eq!(err.status(), Some(reqwest::StatusCode::InternalServerError)); +}