feat(error): revamp hyper::Error type
**The `Error` is now an opaque struct**, which allows for more variants to be added freely, and the internal representation to change without being breaking changes. For inspecting an `Error`, there are several `is_*` methods to check for certain classes of errors, such as `Error::is_parse()`. The `cause` can also be inspected, like before. This likely seems like a downgrade, but more inspection can be added as needed! The `Error` now knows about more states, which gives much more context around when a certain error occurs. This is also expressed in the description and `fmt` messages. **Most places where a user would provide an error to hyper can now pass any error type** (`E: Into<Box<std::error::Error>>`). This error is passed back in relevant places, and can be useful for logging. This should make it much clearer about what error a user should provide to hyper: any it feels is relevant! Closes #1128 Closes #1130 Closes #1431 Closes #1338 BREAKING CHANGE: `Error` is no longer an enum to pattern match over, or to construct. Code will need to be updated accordingly. For body streams or `Service`s, inference might be unable to determine what error type you mean to return. Starting in Rust 1.26, you could just label that as `!` if you never return an error.
This commit is contained in:
@@ -45,7 +45,7 @@ pub struct SendRequest<B> {
|
||||
pub struct Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
inner: proto::dispatch::Dispatcher<
|
||||
proto::dispatch::Client<B>,
|
||||
@@ -138,7 +138,7 @@ impl<B> SendRequest<B>
|
||||
|
||||
impl<B> SendRequest<B>
|
||||
where
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
/// Sends a `Request` on the associated connection.
|
||||
///
|
||||
@@ -262,7 +262,7 @@ impl<B> fmt::Debug for SendRequest<B> {
|
||||
impl<T, B> Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
/// Return the inner IO object, and additional information.
|
||||
pub fn into_parts(self) -> Parts<T> {
|
||||
@@ -289,7 +289,7 @@ where
|
||||
impl<T, B> Future for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
type Item = ();
|
||||
type Error = ::Error;
|
||||
@@ -302,7 +302,7 @@ where
|
||||
impl<T, B> fmt::Debug for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + fmt::Debug,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
f.debug_struct("Connection")
|
||||
@@ -331,7 +331,7 @@ impl Builder {
|
||||
pub fn handshake<T, B>(&self, io: T) -> Handshake<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
Handshake {
|
||||
inner: HandshakeInner {
|
||||
@@ -345,7 +345,7 @@ impl Builder {
|
||||
pub(super) fn handshake_no_upgrades<T, B>(&self, io: T) -> HandshakeNoUpgrades<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
HandshakeNoUpgrades {
|
||||
inner: HandshakeInner {
|
||||
@@ -362,7 +362,7 @@ impl Builder {
|
||||
impl<T, B> Future for Handshake<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
type Item = (SendRequest<B>, Connection<T, B>);
|
||||
type Error = ::Error;
|
||||
@@ -387,7 +387,7 @@ impl<T, B> fmt::Debug for Handshake<T, B> {
|
||||
impl<T, B> Future for HandshakeNoUpgrades<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
{
|
||||
type Item = (SendRequest<B>, proto::dispatch::Dispatcher<
|
||||
proto::dispatch::Client<B>,
|
||||
@@ -406,7 +406,7 @@ where
|
||||
impl<T, B, R> Future for HandshakeInner<T, B, R>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
R: proto::Http1Transaction<
|
||||
Incoming=StatusCode,
|
||||
Outgoing=proto::RequestLine,
|
||||
@@ -470,7 +470,7 @@ impl<B: Send> AssertSendSync for SendRequest<B> {}
|
||||
impl<T: Send, B: Send> AssertSend for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
B::Data: Send + 'static,
|
||||
{}
|
||||
|
||||
@@ -478,7 +478,7 @@ where
|
||||
impl<T: Send + Sync, B: Send + Sync> AssertSendSync for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite,
|
||||
B: Entity<Error=::Error> + 'static,
|
||||
B: Entity + 'static,
|
||||
B::Data: Send + Sync + 'static,
|
||||
{}
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ pub trait Connect: Send + Sync {
|
||||
/// The connected IO Stream.
|
||||
type Transport: AsyncRead + AsyncWrite + Send + 'static;
|
||||
/// An error occured when trying to connect.
|
||||
type Error;
|
||||
type Error: Into<Box<StdError + Send + Sync>>;
|
||||
/// A Future that will resolve to the connected Transport.
|
||||
type Future: Future<Item=(Self::Transport, Connected), Error=Self::Error> + Send;
|
||||
/// Connect to a destination.
|
||||
|
||||
@@ -39,10 +39,10 @@ impl<T, U> Sender<T, U> {
|
||||
// there's room in the queue, but does the Connection
|
||||
// want a message yet?
|
||||
self.giver.poll_want()
|
||||
.map_err(|_| ::Error::Closed)
|
||||
.map_err(|_| ::Error::new_closed())
|
||||
},
|
||||
Ok(Async::NotReady) => Ok(Async::NotReady),
|
||||
Err(_) => Err(::Error::Closed),
|
||||
Err(_) => Err(::Error::new_closed()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,9 +184,12 @@ mod tests {
|
||||
drop(rx);
|
||||
|
||||
promise.then(|fulfilled| {
|
||||
let res = fulfilled.expect("fulfilled");
|
||||
match res.unwrap_err() {
|
||||
(::Error::Cancel(_), Some(_)) => (),
|
||||
let err = fulfilled
|
||||
.expect("fulfilled")
|
||||
.expect_err("promise should error");
|
||||
|
||||
match (err.0.kind(), err.1) {
|
||||
(&::error::Kind::Canceled, Some(_)) => (),
|
||||
e => panic!("expected Error::Cancel(_), found {:?}", e),
|
||||
}
|
||||
|
||||
|
||||
@@ -96,10 +96,10 @@ impl<C, B> Client<C, B> {
|
||||
}
|
||||
|
||||
impl<C, B> Client<C, B>
|
||||
where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
where C: Connect + Sync + 'static,
|
||||
C::Transport: 'static,
|
||||
C::Future: 'static,
|
||||
B: Entity<Error=::Error> + Send + 'static,
|
||||
B: Entity + Send + 'static,
|
||||
B::Data: Send,
|
||||
{
|
||||
|
||||
@@ -139,13 +139,14 @@ where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
Version::HTTP_11 => (),
|
||||
other => {
|
||||
error!("Request has unsupported version \"{:?}\"", other);
|
||||
return FutureResponse(Box::new(future::err(::Error::Version)));
|
||||
//TODO: replace this with a proper variant
|
||||
return FutureResponse(Box::new(future::err(::Error::new_user_unsupported_version())));
|
||||
}
|
||||
}
|
||||
|
||||
if req.method() == &Method::CONNECT {
|
||||
debug!("Client does not support CONNECT requests");
|
||||
return FutureResponse(Box::new(future::err(::Error::Method)));
|
||||
return FutureResponse(Box::new(future::err(::Error::new_user_unsupported_request_method())));
|
||||
}
|
||||
|
||||
let uri = req.uri().clone();
|
||||
@@ -154,7 +155,8 @@ where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
format!("{}://{}", scheme, auth)
|
||||
}
|
||||
_ => {
|
||||
return FutureResponse(Box::new(future::err(::Error::Io(
|
||||
//TODO: replace this with a proper variant
|
||||
return FutureResponse(Box::new(future::err(::Error::new_io(
|
||||
io::Error::new(
|
||||
io::ErrorKind::InvalidInput,
|
||||
"invalid URI for Client Request"
|
||||
@@ -203,13 +205,13 @@ where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
};
|
||||
future::lazy(move || {
|
||||
connector.connect(dst)
|
||||
.from_err()
|
||||
.map_err(::Error::new_connect)
|
||||
.and_then(move |(io, connected)| {
|
||||
conn::Builder::new()
|
||||
.h1_writev(h1_writev)
|
||||
.handshake_no_upgrades(io)
|
||||
.and_then(move |(tx, conn)| {
|
||||
executor.execute(conn.map_err(|e| debug!("client connection error: {}", e)))?;
|
||||
executor.execute(conn.map_err(|e| debug!("client connection error: {}", e)));
|
||||
Ok(pool.pooled(pool_key, PoolClient {
|
||||
is_proxied: connected.is_proxied,
|
||||
tx: tx,
|
||||
@@ -260,9 +262,7 @@ where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
} else if !res.body().is_empty() {
|
||||
let (delayed_tx, delayed_rx) = oneshot::channel();
|
||||
res.body_mut().delayed_eof(delayed_rx);
|
||||
// If the executor doesn't have room, oh well. Things will likely
|
||||
// be blowing up soon, but this specific task isn't required.
|
||||
let _ = executor.execute(
|
||||
executor.execute(
|
||||
future::poll_fn(move || {
|
||||
pooled.tx.poll_ready()
|
||||
})
|
||||
@@ -277,7 +277,6 @@ where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
Ok(res)
|
||||
});
|
||||
|
||||
|
||||
fut
|
||||
});
|
||||
|
||||
@@ -290,9 +289,9 @@ where C: Connect<Error=io::Error> + Sync + 'static,
|
||||
}
|
||||
|
||||
impl<C, B> Service for Client<C, B>
|
||||
where C: Connect<Error=io::Error> + 'static,
|
||||
where C: Connect + 'static,
|
||||
C::Future: 'static,
|
||||
B: Entity<Error=::Error> + Send + 'static,
|
||||
B: Entity + Send + 'static,
|
||||
B::Data: Send,
|
||||
{
|
||||
type Request = Request<B>;
|
||||
@@ -353,9 +352,9 @@ struct RetryableSendRequest<C, B> {
|
||||
|
||||
impl<C, B> Future for RetryableSendRequest<C, B>
|
||||
where
|
||||
C: Connect<Error=io::Error> + 'static,
|
||||
C: Connect + 'static,
|
||||
C::Future: 'static,
|
||||
B: Entity<Error=::Error> + Send + 'static,
|
||||
B: Entity + Send + 'static,
|
||||
B::Data: Send,
|
||||
{
|
||||
type Item = Response<Body>;
|
||||
@@ -564,10 +563,10 @@ impl<C, B> Config<C, B> {
|
||||
}
|
||||
|
||||
impl<C, B> Config<C, B>
|
||||
where C: Connect<Error=io::Error>,
|
||||
where C: Connect,
|
||||
C::Transport: 'static,
|
||||
C::Future: 'static,
|
||||
B: Entity<Error=::Error> + Send,
|
||||
B: Entity + Send,
|
||||
B::Data: Send,
|
||||
{
|
||||
/// Construct the Client with this configuration.
|
||||
@@ -590,7 +589,7 @@ where C: Connect<Error=io::Error>,
|
||||
|
||||
impl<B> Config<UseDefaultConnector, B>
|
||||
where
|
||||
B: Entity<Error=::Error> + Send,
|
||||
B: Entity + Send,
|
||||
B::Data: Send,
|
||||
{
|
||||
/// Construct the Client with this configuration.
|
||||
@@ -640,7 +639,6 @@ impl<C: Clone, B> Clone for Config<C, B> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// ===== impl Exec =====
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -655,24 +653,19 @@ impl Exec {
|
||||
Exec::Executor(Arc::new(executor))
|
||||
}
|
||||
|
||||
fn execute<F>(&self, fut: F) -> io::Result<()>
|
||||
fn execute<F>(&self, fut: F)
|
||||
where
|
||||
F: Future<Item=(), Error=()> + Send + 'static,
|
||||
{
|
||||
match *self {
|
||||
Exec::Default => spawn(fut),
|
||||
Exec::Executor(ref e) => {
|
||||
e.execute(bg(Box::new(fut)))
|
||||
let _ = e.execute(bg(Box::new(fut)))
|
||||
.map_err(|err| {
|
||||
debug!("executor error: {:?}", err.kind());
|
||||
io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"executor error",
|
||||
)
|
||||
})?
|
||||
panic!("executor error: {:?}", err.kind());
|
||||
});
|
||||
},
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -245,7 +245,7 @@ impl<T: Closed + Send + 'static> Pool<T> {
|
||||
interval: interval,
|
||||
pool: Arc::downgrade(&self.inner),
|
||||
pool_drop_notifier: rx,
|
||||
}).unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ fn retryable_request() {
|
||||
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||
Ok(Async::Ready(()))
|
||||
});
|
||||
}).map_err(|e: ::std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||
res1.join(srv1).wait().expect("res1");
|
||||
}
|
||||
drop(sock1);
|
||||
@@ -52,7 +52,7 @@ fn retryable_request() {
|
||||
try_ready!(sock2.read(&mut [0u8; 512]));
|
||||
try_ready!(sock2.write(b"HTTP/1.1 222 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||
Ok(Async::Ready(()))
|
||||
});
|
||||
}).map_err(|e: ::std::io::Error| panic!("srv2 poll_fn error: {}", e));
|
||||
|
||||
res2.join(srv2).wait().expect("res2");
|
||||
}
|
||||
@@ -82,7 +82,7 @@ fn conn_reset_after_write() {
|
||||
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||
Ok(Async::Ready(()))
|
||||
});
|
||||
}).map_err(|e: ::std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||
res1.join(srv1).wait().expect("res1");
|
||||
}
|
||||
|
||||
@@ -105,10 +105,10 @@ fn conn_reset_after_write() {
|
||||
try_ready!(sock1.as_mut().unwrap().read(&mut [0u8; 512]));
|
||||
sock1.take();
|
||||
Ok(Async::Ready(()))
|
||||
});
|
||||
}).map_err(|e: ::std::io::Error| panic!("srv2 poll_fn error: {}", e));
|
||||
let err = res2.join(srv2).wait().expect_err("res2");
|
||||
match err {
|
||||
::Error::Incomplete => (),
|
||||
match err.kind() {
|
||||
&::error::Kind::Incomplete => (),
|
||||
other => panic!("expected Incomplete, found {:?}", other)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user