Make Error an enum again.

This commit is contained in:
Jacob Hoffman-Andrews
2020-11-26 18:29:24 -08:00
committed by Martin Algesten
parent b20c4fc3be
commit 219185f73f
2 changed files with 73 additions and 76 deletions

View File

@@ -31,11 +31,11 @@ use crate::Response;
/// Ok(r) => return Ok(r), /// Ok(r) => return Ok(r),
/// Err(e) => e, /// Err(e) => e,
/// }; /// };
/// match err.http() { /// match err {
/// // Retry 500s after waiting for two seconds. /// // Retry 500s after waiting for two seconds.
/// Ok((500, _)) => thread::sleep(Duration::from_secs(2)), /// Error::Status(500, _) => thread::sleep(Duration::from_secs(2)),
/// Ok((_, r)) => return Err(r.into()), /// Error::Status(_, r) => return Err(r.into()),
/// Err(e) => return Err(e), /// Error::Transport(e) => return Err(e.into()),
/// } /// }
/// result = fetch(); /// result = fetch();
/// } /// }
@@ -44,7 +44,7 @@ use crate::Response;
/// } /// }
/// ``` /// ```
#[derive(Debug)] #[derive(Debug)]
pub struct Error { pub struct Transport {
kind: ErrorKind, kind: ErrorKind,
message: Option<String>, message: Option<String>,
url: Option<Url>, url: Option<Url>,
@@ -52,21 +52,30 @@ pub struct Error {
response: Option<Response>, response: Option<Response>,
} }
#[derive(Debug)]
pub enum Error {
Status(u16, Response),
Transport(Transport),
}
impl Display for Error { impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(url) = &self.url { match self {
write!(f, "{}: ", url)?; Error::Status(status, response) => {
} write!(f, "{}: status code {}", response.get_url(), status)?;
if let Some(response) = &self.response { }
write!(f, "status code {}", response.status())?; Error::Transport(err) => {
} else { if let Some(url) = &err.url {
write!(f, "{:?}", self.kind)?; write!(f, "{}: ", url)?;
} }
if let Some(message) = &self.message { write!(f, "{}", err.kind)?;
write!(f, ": {}", message)?; if let Some(message) = &err.message {
} write!(f, ": {}", message)?;
if let Some(source) = &self.source { }
write!(f, ": {}", source)?; if let Some(source) = &err.source {
write!(f, ": {}", source)?;
}
}
} }
Ok(()) Ok(())
} }
@@ -74,37 +83,42 @@ impl Display for Error {
impl error::Error for Error { impl error::Error for Error {
fn source(&self) -> Option<&(dyn error::Error + 'static)> { fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match &self.source { match &self {
Some(s) => Some(s.as_ref()), Error::Transport(Transport {
None => None, source: Some(s), ..
}) => Some(s.as_ref()),
_ => None,
} }
} }
} }
impl Error { impl Error {
pub(crate) fn new(kind: ErrorKind, message: Option<String>) -> Self { pub(crate) fn new(kind: ErrorKind, message: Option<String>) -> Self {
Error { Error::Transport(Transport {
kind, kind,
message, message,
url: None, url: None,
source: None, source: None,
response: None, response: None,
})
}
pub(crate) fn url(self, url: Url) -> Self {
if let Error::Transport(mut e) = self {
e.url = Some(url);
Error::Transport(e)
} else {
self
} }
} }
pub(crate) fn url(mut self, url: Url) -> Self { pub(crate) fn src(self, e: impl error::Error + Send + Sync + 'static) -> Self {
self.url = Some(url); if let Error::Transport(mut oe) = self {
self oe.source = Some(Box::new(e));
} Error::Transport(oe)
} else {
pub(crate) fn src(mut self, e: impl error::Error + Send + Sync + 'static) -> Self { self
self.source = Some(Box::new(e)); }
self
}
pub(crate) fn response(mut self, response: Response) -> Self {
self.response = Some(response);
self
} }
/// The type of this error. /// The type of this error.
@@ -116,27 +130,9 @@ impl Error {
/// assert_eq!(err.kind(), ureq::ErrorKind::HTTP); /// assert_eq!(err.kind(), ureq::ErrorKind::HTTP);
/// ``` /// ```
pub fn kind(&self) -> ErrorKind { pub fn kind(&self) -> ErrorKind {
self.kind match self {
} Error::Status(_, _) => ErrorKind::HTTP,
Error::Transport(Transport { kind: k, .. }) => k.clone(),
/// For an Error of type HTTP (i.e. those that result from an HTTP status code),
/// turn the error into a tuple containing the status code and the underlying
/// Response, and return Ok. For other errors, return Err(self).
///
/// This allows the caller to match on certain status codes, while retaining
/// ownership of non-HTTP errors. You'll need to use the `From<Response>` impl
/// to get back an Error for the status codes you want to treat as errors.
///
/// ```
/// # ureq::is_test(true);
/// let err = ureq::get("http://httpbin.org/status/500")
/// .call().unwrap_err();
/// assert!(matches!(err.http(), Ok((500, _))));
/// ```
pub fn http(self) -> Result<(u16, Response), Self> {
match self.response {
Some(r) => Ok((r.status(), r)),
None => Err(self),
} }
} }
@@ -145,7 +141,11 @@ impl Error {
if self.kind() != ErrorKind::Io { if self.kind() != ErrorKind::Io {
return false; return false;
} }
let source = match self.source.as_ref() { let other_err = match self {
Error::Status(_, _) => return false,
Error::Transport(e) => e,
};
let source = match other_err.source.as_ref() {
Some(e) => e, Some(e) => e,
None => return false, None => return false,
}; };
@@ -206,16 +206,7 @@ impl ErrorKind {
impl From<Response> for Error { impl From<Response> for Error {
fn from(resp: Response) -> Error { fn from(resp: Response) -> Error {
Error { Error::Status(resp.status(), resp)
kind: ErrorKind::HTTP,
message: None,
// Note: This will be the URL of the last response in a redirect chain,
// while the normal URL in an error corresponds to the first response in
// a redirect chain.
url: resp.get_url().parse().ok(),
source: None,
response: Some(resp),
}
} }
} }
@@ -225,6 +216,12 @@ impl From<io::Error> for Error {
} }
} }
impl From<Transport> for Error {
fn from(err: Transport) -> Error {
Error::Transport(err)
}
}
impl fmt::Display for ErrorKind { impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self { match self {
@@ -245,15 +242,15 @@ impl fmt::Display for ErrorKind {
} }
} }
#[test] // #[test]
fn status_code_error() { // fn status_code_error() {
let mut err = Error::new(ErrorKind::HTTP, None); // let mut err = Error::new(ErrorKind::HTTP, None);
err = err.response(Response::new(500, "Internal Server Error", "too much going on").unwrap()); // err = err.response(Response::new(500, "Internal Server Error", "too much going on").unwrap());
assert_eq!(err.to_string(), "status code 500"); // assert_eq!(err.to_string(), "status code 500");
err = err.url("http://example.com/".parse().unwrap()); // err = err.url("http://example.com/".parse().unwrap());
assert_eq!(err.to_string(), "http://example.com/: status code 500"); // assert_eq!(err.to_string(), "http://example.com/: status code 500");
} // }
#[test] #[test]
fn io_error() { fn io_error() {

View File

@@ -122,7 +122,7 @@ impl Request {
unit::connect(unit, true, 0, reader, false).map_err(|e| e.url(url.clone()))?; unit::connect(unit, true, 0, reader, false).map_err(|e| e.url(url.clone()))?;
if self.error_on_non_2xx && response.status() >= 400 { if self.error_on_non_2xx && response.status() >= 400 {
Err(ErrorKind::HTTP.new().url(url.clone()).response(response)) Err(Error::Status(response.status(), response))
} else { } else {
Ok(response) Ok(response)
} }