use url::{ParseError, Url};
use std::error;
use std::fmt::{self, Display};
use std::io;
use crate::Response;
#[derive(Debug)]
pub enum Error {
Status(u16, Response),
Transport(Transport),
}
impl Error {
pub fn into_transport(self) -> Option<Transport> {
match self {
Error::Status(_, _) => None,
Error::Transport(t) => Some(t),
}
}
pub fn into_response(self) -> Option<Response> {
match self {
Error::Status(_, r) => Some(r),
Error::Transport(_) => None,
}
}
}
#[derive(Debug)]
pub struct Transport {
kind: ErrorKind,
message: Option<String>,
url: Option<Url>,
source: Option<Box<dyn error::Error + Send + Sync + 'static>>,
}
impl Transport {
pub fn kind(&self) -> ErrorKind {
self.kind
}
pub fn message(&self) -> Option<&str> {
self.message.as_deref()
}
pub fn url(&self) -> Option<&Url> {
self.url.as_ref()
}
}
pub trait OrAnyStatus {
fn or_any_status(self) -> Result<Response, Transport>;
}
impl OrAnyStatus for Result<Response, Error> {
fn or_any_status(self) -> Result<Response, Transport> {
match self {
Ok(response) => Ok(response),
Err(Error::Status(_, response)) => Ok(response),
Err(Error::Transport(transport)) => Err(transport),
}
}
}
impl Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::Status(status, response) => {
write!(f, "{}: status code {}", response.get_url(), status)?;
if let Some(original) = response.history.get(0) {
write!(f, " (redirected from {})", original)?;
}
}
Error::Transport(err) => {
write!(f, "{}", err)?;
}
}
Ok(())
}
}
impl Display for Transport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(url) = &self.url {
write!(f, "{}: ", url)?;
}
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)> {
match &self {
Error::Transport(Transport {
source: Some(s), ..
}) => Some(s.as_ref()),
_ => None,
}
}
}
impl error::Error for Transport {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
self.source
.as_ref()
.map(|s| s.as_ref() as &(dyn error::Error + 'static))
}
}
impl Error {
pub(crate) fn new(kind: ErrorKind, message: Option<String>) -> Self {
Error::Transport(Transport {
kind,
message,
url: None,
source: 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 src(self, e: impl error::Error + Send + Sync + 'static) -> Self {
if let Error::Transport(mut oe) = self {
oe.source = Some(Box::new(e));
Error::Transport(oe)
} else {
self
}
}
pub fn kind(&self) -> ErrorKind {
match self {
Error::Status(_, _) => ErrorKind::HTTP,
Error::Transport(Transport { kind: k, .. }) => *k,
}
}
pub(crate) fn connection_closed(&self) -> bool {
if self.kind() != ErrorKind::Io {
return false;
}
let other_err = match self {
Error::Status(_, _) => return false,
Error::Transport(e) => e,
};
let source = match other_err.source.as_ref() {
Some(e) => e,
None => return false,
};
let ioe: &io::Error = match source.downcast_ref() {
Some(e) => e,
None => return false,
};
match ioe.kind() {
io::ErrorKind::ConnectionAborted => true,
io::ErrorKind::ConnectionReset => true,
_ => false,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
pub enum ErrorKind {
InvalidUrl,
UnknownScheme,
Dns,
InsecureRequestHttpsOnly,
ConnectionFailed,
TooManyRedirects,
BadStatus,
BadHeader,
Io,
InvalidProxyUrl,
ProxyConnect,
ProxyUnauthorized,
HTTP,
}
impl ErrorKind {
#[allow(clippy::wrong_self_convention)]
#[allow(clippy::new_ret_no_self)]
pub(crate) fn new(self) -> Error {
Error::new(self, None)
}
pub(crate) fn msg(self, s: impl Into<String>) -> Error {
Error::new(self, Some(s.into()))
}
}
impl From<Response> for Error {
fn from(resp: Response) -> Error {
Error::Status(resp.status(), resp)
}
}
impl From<io::Error> for Error {
fn from(err: io::Error) -> Error {
ErrorKind::Io.new().src(err)
}
}
impl From<Transport> for Error {
fn from(err: Transport) -> Error {
Error::Transport(err)
}
}
impl From<ParseError> for Error {
fn from(err: ParseError) -> Self {
ErrorKind::InvalidUrl
.msg(format!("failed to parse URL: {:?}", err))
.src(err)
}
}
impl fmt::Display for ErrorKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
ErrorKind::InvalidUrl => write!(f, "Bad URL"),
ErrorKind::UnknownScheme => write!(f, "Unknown Scheme"),
ErrorKind::Dns => write!(f, "Dns Failed"),
ErrorKind::InsecureRequestHttpsOnly => {
write!(f, "Insecure request attempted with https_only set")
}
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::InvalidProxyUrl => write!(f, "Malformed proxy"),
ErrorKind::ProxyConnect => write!(f, "Proxy failed to connect"),
ErrorKind::ProxyUnauthorized => write!(f, "Provided proxy credentials are incorrect"),
ErrorKind::HTTP => write!(f, "HTTP status error"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn status_code_error() {
let mut response = Response::new(404, "NotFound", "").unwrap();
response.set_url("http://example.org/".parse().unwrap());
let err = Error::Status(response.status(), response);
assert_eq!(err.to_string(), "http://example.org/: status code 404");
}
#[test]
fn status_code_error_redirect() {
use crate::{get, test};
test::set_handler("/redirect_a", |unit| {
assert_eq!(unit.method, "GET");
test::make_response(
302,
"Go here",
vec!["Location: test://example.edu/redirect_b"],
vec![],
)
});
test::set_handler("/redirect_b", |unit| {
assert_eq!(unit.method, "GET");
test::make_response(
302,
"Go here",
vec!["Location: http://example.com/status/500"],
vec![],
)
});
let err = get("test://example.org/redirect_a").call().unwrap_err();
assert_eq!(err.kind(), ErrorKind::HTTP, "{:?}", err);
assert_eq!(
err.to_string(),
"http://example.com/status/500: status code 500 (redirected from test://example.org/redirect_a)"
);
}
#[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/: Network Error: oops: too slow"
);
}
#[test]
fn connection_closed() {
let ioe = io::Error::new(io::ErrorKind::ConnectionReset, "connection reset");
let err = ErrorKind::Io.new().src(ioe);
assert!(err.connection_closed());
let ioe = io::Error::new(io::ErrorKind::ConnectionAborted, "connection aborted");
let err = ErrorKind::Io.new().src(ioe);
assert!(err.connection_closed());
}
#[test]
fn error_implements_send_and_sync() {
let _error: Box<dyn Send> = Box::new(Error::new(ErrorKind::Io, None));
let _error: Box<dyn Sync> = Box::new(Error::new(ErrorKind::Io, None));
}
#[test]
fn ensure_error_size() {
let size = std::mem::size_of::<Error>();
println!("Error size: {}", size);
assert!(size < 500); }
}