Rewrite the Error type. (#234)
This adds a source field to keep track of upstream errors and allow
backtraces, plus a URL field to indicate what URL an error was
associated with.
The enum variants we used to use for Error are now part of a new
ErrorKind type. For convenience within ureq, ErrorKinds can be turned
into an Error with `.new()` or `.msg("some additional information")`.
Error acts as a builder, so additional information can be added after
initial construction. For instance, we return a DnsFailed error when
name resolution fails. When that error bubbles up to Request's
`do_call`, Request adds the URL.
Fixes #232.
This commit is contained in:
committed by
GitHub
parent
dac517e30e
commit
fade03b54e
175
src/error.rs
175
src/error.rs
@@ -1,17 +1,108 @@
|
||||
use crate::response::Response;
|
||||
use std::fmt;
|
||||
use std::io::{self, ErrorKind};
|
||||
use url::Url;
|
||||
|
||||
use std::error;
|
||||
use std::fmt::{self, Display};
|
||||
use std::io::{self};
|
||||
|
||||
use crate::Response;
|
||||
|
||||
/// An error that may occur when processing a Request.
|
||||
#[derive(Debug)]
|
||||
pub enum Error {
|
||||
pub struct Error {
|
||||
kind: ErrorKind,
|
||||
message: Option<String>,
|
||||
url: Option<Url>,
|
||||
source: Option<Box<dyn error::Error>>,
|
||||
response: Option<Box<Response>>,
|
||||
}
|
||||
|
||||
impl Display for Error {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if let Some(url) = &self.url {
|
||||
write!(f, "{}: ", url)?;
|
||||
}
|
||||
if let Some(response) = &self.response {
|
||||
write!(f, "status code {}", response.status())?;
|
||||
} else {
|
||||
write!(f, "{:?}", self.kind)?;
|
||||
}
|
||||
if let Some(message) = &self.message {
|
||||
write!(f, ": {}", message)?;
|
||||
}
|
||||
if let Some(source) = &self.source {
|
||||
write!(f, ": {}", source)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for Error {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
self.source.as_deref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Error {
|
||||
pub(crate) fn new(kind: ErrorKind, message: Option<String>) -> Self {
|
||||
Error {
|
||||
kind,
|
||||
message,
|
||||
url: None,
|
||||
source: None,
|
||||
response: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn url(mut self, url: Url) -> Self {
|
||||
self.url = Some(url);
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn src(mut self, e: impl error::Error + 'static) -> Self {
|
||||
self.source = Some(Box::new(e));
|
||||
self
|
||||
}
|
||||
|
||||
pub(crate) fn response(mut self, response: Response) -> Self {
|
||||
self.response = Some(Box::new(response));
|
||||
self
|
||||
}
|
||||
pub(crate) fn kind(&self) -> ErrorKind {
|
||||
self.kind
|
||||
}
|
||||
|
||||
/// Return true iff the error was due to a connection closing.
|
||||
pub(crate) fn connection_closed(&self) -> bool {
|
||||
if self.kind() != ErrorKind::Io {
|
||||
return false;
|
||||
}
|
||||
let source = match self.source.as_ref() {
|
||||
Some(e) => e,
|
||||
None => return false,
|
||||
};
|
||||
let ioe: &Box<io::Error> = match source.downcast_ref() {
|
||||
Some(e) => e,
|
||||
None => return false,
|
||||
};
|
||||
match ioe.kind() {
|
||||
io::ErrorKind::ConnectionAborted => true,
|
||||
io::ErrorKind::ConnectionReset => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// One of the types of error the can occur when processing a Request.
|
||||
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||
pub enum ErrorKind {
|
||||
/// The url could not be understood.
|
||||
BadUrl(String),
|
||||
BadUrl,
|
||||
/// The url scheme could not be understood.
|
||||
UnknownScheme(String),
|
||||
UnknownScheme,
|
||||
/// DNS lookup failed.
|
||||
DnsFailed(String),
|
||||
DnsFailed,
|
||||
/// Connection to server failed.
|
||||
ConnectionFailed(String),
|
||||
ConnectionFailed,
|
||||
/// Too many redirects.
|
||||
TooManyRedirects,
|
||||
/// A status line we don't understand `HTTP/1.1 200 OK`.
|
||||
@@ -19,7 +110,7 @@ pub enum Error {
|
||||
/// A header line that couldn't be parsed.
|
||||
BadHeader,
|
||||
/// Some unspecified `std::io::Error`.
|
||||
Io(io::Error),
|
||||
Io,
|
||||
/// Proxy information was not properly formatted
|
||||
BadProxy,
|
||||
/// Proxy credentials were not properly formatted
|
||||
@@ -31,44 +122,60 @@ pub enum Error {
|
||||
/// HTTP status code indicating an error (e.g. 4xx, 5xx)
|
||||
/// Read the inner response body for details and to return
|
||||
/// the connection to the pool.
|
||||
HTTP(Box<Response>),
|
||||
HTTP,
|
||||
}
|
||||
|
||||
impl Error {
|
||||
// Return true iff the error was due to a connection closing.
|
||||
pub(crate) fn connection_closed(&self) -> bool {
|
||||
match self {
|
||||
Error::Io(e) if e.kind() == ErrorKind::ConnectionAborted => true,
|
||||
Error::Io(e) if e.kind() == ErrorKind::ConnectionReset => true,
|
||||
_ => false,
|
||||
}
|
||||
impl ErrorKind {
|
||||
pub(crate) fn new(self) -> Error {
|
||||
Error::new(self, None)
|
||||
}
|
||||
|
||||
pub(crate) fn msg(self, s: &str) -> Error {
|
||||
Error::new(self, Some(s.to_string()))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<io::Error> for Error {
|
||||
fn from(err: io::Error) -> Error {
|
||||
Error::Io(err)
|
||||
ErrorKind::Io.new().src(err)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Error {
|
||||
impl fmt::Display for ErrorKind {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
match self {
|
||||
Error::BadUrl(url) => write!(f, "Bad URL: {}", url),
|
||||
Error::UnknownScheme(scheme) => write!(f, "Unknown Scheme: {}", scheme),
|
||||
Error::DnsFailed(err) => write!(f, "Dns Failed: {}", err),
|
||||
Error::ConnectionFailed(err) => write!(f, "Connection Failed: {}", err),
|
||||
Error::TooManyRedirects => write!(f, "Too Many Redirects"),
|
||||
Error::BadStatus => write!(f, "Bad Status"),
|
||||
Error::BadHeader => write!(f, "Bad Header"),
|
||||
Error::Io(ioe) => write!(f, "Network Error: {}", ioe),
|
||||
Error::BadProxy => write!(f, "Malformed proxy"),
|
||||
Error::BadProxyCreds => write!(f, "Failed to parse proxy credentials"),
|
||||
Error::ProxyConnect => write!(f, "Proxy failed to connect"),
|
||||
Error::InvalidProxyCreds => write!(f, "Provided proxy credentials are incorrect"),
|
||||
Error::HTTP(response) => write!(f, "HTTP status {}", response.status()),
|
||||
ErrorKind::BadUrl => write!(f, "Bad URL"),
|
||||
ErrorKind::UnknownScheme => write!(f, "Unknown Scheme"),
|
||||
ErrorKind::DnsFailed => write!(f, "Dns Failed"),
|
||||
ErrorKind::ConnectionFailed => write!(f, "Connection Failed"),
|
||||
ErrorKind::TooManyRedirects => write!(f, "Too Many Redirects"),
|
||||
ErrorKind::BadStatus => write!(f, "Bad Status"),
|
||||
ErrorKind::BadHeader => write!(f, "Bad Header"),
|
||||
ErrorKind::Io => write!(f, "Network Error"),
|
||||
ErrorKind::BadProxy => write!(f, "Malformed proxy"),
|
||||
ErrorKind::BadProxyCreds => write!(f, "Failed to parse proxy credentials"),
|
||||
ErrorKind::ProxyConnect => write!(f, "Proxy failed to connect"),
|
||||
ErrorKind::InvalidProxyCreds => write!(f, "Provided proxy credentials are incorrect"),
|
||||
ErrorKind::HTTP => write!(f, "HTTP status error"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for Error {}
|
||||
#[test]
|
||||
fn status_code_error() {
|
||||
let mut err = Error::new(ErrorKind::HTTP, None);
|
||||
err = err.response(Response::new(500, "Internal Server Error", "too much going on").unwrap());
|
||||
assert_eq!(err.to_string(), "status code 500");
|
||||
|
||||
err = err.url("http://example.com/".parse().unwrap());
|
||||
assert_eq!(err.to_string(), "http://example.com/: status code 500");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn io_error() {
|
||||
let ioe = io::Error::new(io::ErrorKind::TimedOut, "too slow");
|
||||
let mut err = Error::new(ErrorKind::Io, Some("oops".to_string())).src(ioe);
|
||||
|
||||
err = err.url("http://example.com/".parse().unwrap());
|
||||
assert_eq!(err.to_string(), "http://example.com/: Io: oops: too slow");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user