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:
Jacob Hoffman-Andrews
2020-11-21 16:14:44 -08:00
committed by GitHub
parent dac517e30e
commit fade03b54e
13 changed files with 266 additions and 122 deletions

View File

@@ -1,6 +1,6 @@
use log::debug;
use std::fmt;
use std::io::{self, BufRead, BufReader, Cursor, ErrorKind, Read, Write};
use std::io::{self, BufRead, BufReader, Cursor, Read, Write};
use std::net::SocketAddr;
use std::net::TcpStream;
use std::time::Duration;
@@ -15,10 +15,10 @@ use rustls::StreamOwned;
#[cfg(feature = "socks-proxy")]
use socks::{TargetAddr, ToTargetAddr};
use crate::proxy::Proto;
use crate::proxy::Proxy;
use crate::{error::Error, proxy::Proto};
use crate::error::Error;
use crate::error::ErrorKind;
use crate::unit::Unit;
#[allow(clippy::large_enum_variant)]
@@ -67,7 +67,7 @@ impl Read for DeadlineStream {
// causes ErrorKind::WouldBlock instead of ErrorKind::TimedOut.
// Since the socket most definitely not set_nonblocking(true),
// we can safely normalize WouldBlock to TimedOut
if e.kind() == ErrorKind::WouldBlock {
if e.kind() == io::ErrorKind::WouldBlock {
return io_err_timeout("timed out reading response".to_string());
}
e
@@ -86,7 +86,7 @@ fn time_until_deadline(deadline: Instant) -> io::Result<Duration> {
}
pub(crate) fn io_err_timeout(error: String) -> io::Error {
io::Error::new(ErrorKind::TimedOut, error)
io::Error::new(io::ErrorKind::TimedOut, error)
}
impl fmt::Debug for Stream {
@@ -119,7 +119,7 @@ impl Stream {
let result = match stream.peek(&mut buf) {
Ok(0) => Ok(true),
Ok(_) => Ok(false), // TODO: Maybe this should produce an "unexpected response" error
Err(e) if e.kind() == ErrorKind::WouldBlock => Ok(false),
Err(e) if e.kind() == io::ErrorKind::WouldBlock => Ok(false),
Err(e) => Err(e),
};
stream.set_nonblocking(false)?;
@@ -241,7 +241,7 @@ fn read_https(
#[allow(deprecated)]
#[cfg(feature = "tls")]
fn is_close_notify(e: &std::io::Error) -> bool {
if e.kind() != ErrorKind::ConnectionAborted {
if e.kind() != io::ErrorKind::ConnectionAborted {
return false;
}
@@ -313,7 +313,7 @@ pub(crate) fn connect_https(unit: &Unit, hostname: &str) -> Result<Stream, Error
let port = unit.url.port().unwrap_or(443);
let sni = webpki::DNSNameRef::try_from_ascii_str(hostname)
.map_err(|err| Error::DnsFailed(err.to_string()))?;
.map_err(|err| ErrorKind::DnsFailed.new().src(err))?;
let tls_conf: &Arc<rustls::ClientConfig> = unit
.agent
.config
@@ -347,10 +347,10 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<Tcp
let sock_addrs = unit
.resolver()
.resolve(&netloc)
.map_err(|e| Error::DnsFailed(format!("{}", e)))?;
.map_err(|e| ErrorKind::DnsFailed.new().src(e))?;
if sock_addrs.is_empty() {
return Err(Error::DnsFailed(format!("No ip address for {}", hostname)));
return Err(ErrorKind::DnsFailed.msg(&format!("No ip address for {}", hostname)));
}
let proto = if let Some(ref proxy) = proxy {
@@ -396,9 +396,10 @@ pub(crate) fn connect_host(unit: &Unit, hostname: &str, port: u16) -> Result<Tcp
let mut stream = if let Some(stream) = any_stream {
stream
} else if let Some(e) = any_err {
return Err(ErrorKind::ConnectionFailed.msg("Connect error").src(e));
} else {
let err = Error::ConnectionFailed(format!("{}", any_err.expect("Connect error")));
return Err(err);
panic!("shouldn't happen: failed to connect to all IPs, but no error");
};
if let Some(deadline) = unit.deadline {
@@ -445,11 +446,13 @@ fn socks5_local_nslookup(
let addrs: Vec<SocketAddr> = unit
.resolver()
.resolve(&format!("{}:{}", hostname, port))
.map_err(|e| std::io::Error::new(ErrorKind::NotFound, format!("DNS failure: {}.", e)))?;
.map_err(|e| {
std::io::Error::new(io::ErrorKind::NotFound, format!("DNS failure: {}.", e))
})?;
if addrs.is_empty() {
return Err(std::io::Error::new(
ErrorKind::NotFound,
io::ErrorKind::NotFound,
"DNS failure: no socket addrs found.",
));
}
@@ -458,7 +461,7 @@ fn socks5_local_nslookup(
Ok(addr) => Ok(addr),
Err(err) => {
return Err(std::io::Error::new(
ErrorKind::NotFound,
io::ErrorKind::NotFound,
format!("DNS failure: {}.", err),
))
}
@@ -579,7 +582,7 @@ fn connect_socks5(
_port: u16,
) -> Result<TcpStream, std::io::Error> {
Err(std::io::Error::new(
ErrorKind::Other,
io::ErrorKind::Other,
"SOCKS5 feature disabled.",
))
}
@@ -592,10 +595,12 @@ pub(crate) fn connect_test(unit: &Unit) -> Result<Stream, Error> {
#[cfg(not(test))]
pub(crate) fn connect_test(unit: &Unit) -> Result<Stream, Error> {
Err(Error::UnknownScheme(unit.url.scheme().to_string()))
Err(ErrorKind::UnknownScheme.msg(&format!("unknown scheme '{}'", unit.url.scheme())))
}
#[cfg(not(feature = "tls"))]
pub(crate) fn connect_https(unit: &Unit, _hostname: &str) -> Result<Stream, Error> {
Err(Error::UnknownScheme(unit.url.scheme().to_string()))
Err(ErrorKind::UnknownScheme
.msg("URL has 'https:' scheme but ureq was build without HTTP support")
.url(unit.url.clone()))
}