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:
Sean McArthur
2018-04-10 14:29:34 -07:00
parent 33874f9a75
commit 5d3c472228
22 changed files with 519 additions and 407 deletions

View File

@@ -8,7 +8,7 @@ extern crate tokio;
extern crate tokio_io;
extern crate pretty_env_logger;
use std::io::{self, Read, Write};
use std::io::{Read, Write};
use std::net::{SocketAddr, TcpListener};
use std::thread;
use std::time::Duration;
@@ -142,7 +142,7 @@ macro_rules! test {
let _ = pretty_env_logger::try_init();
let runtime = Runtime::new().expect("runtime new");
let err = test! {
let err: ::hyper::Error = test! {
INNER;
name: $name,
runtime: &runtime,
@@ -157,7 +157,11 @@ macro_rules! test {
headers: { $($request_header_name => $request_header_val,)* },
body: $request_body,
}.unwrap_err();
if !$err(&err) {
fn infer_closure<F: FnOnce(&::hyper::Error) -> bool>(f: F) -> F { f }
let closure = infer_closure($err);
if !closure(&err) {
panic!("expected error, unexpected variant: {:?}", err)
}
}
@@ -228,7 +232,7 @@ macro_rules! test {
let _ = tx.send(());
}).expect("thread spawn");
let rx = rx.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx.expect("thread panicked");
res.join(rx).map(|r| r.0).wait()
});
@@ -485,10 +489,7 @@ test! {
url: "http://{addr}/err",
headers: {},
body: None,
error: |err| match err {
&hyper::Error::Incomplete => true,
_ => false,
},
error: |err| err.to_string() == "message is incomplete",
}
test! {
@@ -511,10 +512,8 @@ test! {
url: "http://{addr}/err",
headers: {},
body: None,
error: |err| match err {
&hyper::Error::Version => true,
_ => false,
},
// should get a Parse(Version) error
error: |err| err.is_parse(),
}
@@ -574,10 +573,7 @@ test! {
url: "http://{addr}/upgrade",
headers: {},
body: None,
error: |err| match err {
&hyper::Error::Upgrade => true,
_ => false,
},
error: |err| err.to_string() == "unsupported protocol upgrade",
}
@@ -599,10 +595,7 @@ test! {
url: "http://{addr}/",
headers: {},
body: None,
error: |err| match err {
&hyper::Error::Method => true,
_ => false,
},
error: |err| err.is_user(),
}
@@ -689,9 +682,9 @@ mod dispatch_impl {
let res = client.request(req).and_then(move |res| {
assert_eq!(res.status(), hyper::StatusCode::OK);
Delay::new(Duration::from_secs(1))
.from_err()
.expect("timeout")
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
res.join(rx).map(|r| r.0).wait().unwrap();
closes.into_future().wait().unwrap().0.expect("closes");
@@ -736,11 +729,11 @@ mod dispatch_impl {
res.into_body().into_stream().concat2()
}).and_then(|_| {
Delay::new(Duration::from_secs(1))
.from_err()
.expect("timeout")
})
};
// client is dropped
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
res.join(rx).map(|r| r.0).wait().unwrap();
closes.into_future().wait().unwrap().0.expect("closes");
@@ -788,7 +781,7 @@ mod dispatch_impl {
assert_eq!(res.status(), hyper::StatusCode::OK);
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
res.join(rx).map(|r| r.0).wait().unwrap();
// not closed yet, just idle
@@ -904,7 +897,7 @@ mod dispatch_impl {
client.request(req)
};
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
res.join(rx).map(|r| r.0).wait().unwrap();
let t = Delay::new(Duration::from_millis(100))
@@ -955,7 +948,7 @@ mod dispatch_impl {
assert_eq!(res.status(), hyper::StatusCode::OK);
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
res.join(rx).map(|r| r.0).wait().unwrap();
let t = Delay::new(Duration::from_millis(100))
@@ -1003,10 +996,9 @@ mod dispatch_impl {
assert_eq!(res.status(), hyper::StatusCode::OK);
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
res.join(rx).map(|r| r.0).wait().unwrap();
let t = Delay::new(Duration::from_millis(100))
.map(|_| panic!("time out"));
let close = closes.into_future()
@@ -1049,13 +1041,12 @@ mod dispatch_impl {
assert_eq!(res.status(), hyper::StatusCode::OK);
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let timeout = Delay::new(Duration::from_millis(200));
let rx = rx.and_then(move |_| timeout.map_err(|e| e.into()));
let rx = rx.and_then(move |_| timeout.expect("timeout"));
res.join(rx).map(|r| r.0).wait().unwrap();
let t = Delay::new(Duration::from_millis(100))
.map(|_| panic!("time out"));
let close = closes.into_future()
@@ -1129,7 +1120,7 @@ mod dispatch_impl {
assert_eq!(connects.load(Ordering::SeqCst), 0);
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let req = Request::builder()
.uri(&*format!("http://{}/a", addr))
.body(Body::empty())
@@ -1143,7 +1134,7 @@ mod dispatch_impl {
// state and back into client pool
thread::sleep(Duration::from_millis(50));
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx2.expect("thread panicked");
let req = Request::builder()
.uri(&*format!("http://{}/b", addr))
.body(Body::empty())
@@ -1194,7 +1185,7 @@ mod dispatch_impl {
assert_eq!(connects.load(Ordering::Relaxed), 0);
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let req = Request::builder()
.method("HEAD")
.uri(&*format!("http://{}/a", addr))
@@ -1205,7 +1196,7 @@ mod dispatch_impl {
assert_eq!(connects.load(Ordering::Relaxed), 1);
let rx = rx2.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx2.expect("thread panicked");
let req = Request::builder()
.uri(&*format!("http://{}/b", addr))
.body(Body::empty())
@@ -1246,7 +1237,7 @@ mod dispatch_impl {
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let req = Request::builder()
.uri(&*format!("http://{}/foo/bar", addr))
.body(Body::empty())
@@ -1354,7 +1345,7 @@ mod conn {
use hyper::{self, Request};
use hyper::client::conn;
use super::{s, tcp_connect};
use super::{s, tcp_connect, FutureHyperExt};
#[test]
fn get() {
@@ -1395,10 +1386,10 @@ mod conn {
assert_eq!(res.status(), hyper::StatusCode::OK);
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let timeout = Delay::new(Duration::from_millis(200));
let rx = rx.and_then(move |_| timeout.map_err(|e| e.into()));
let rx = rx.and_then(move |_| timeout.expect("timeout"));
res.join(rx).map(|r| r.0).wait().unwrap();
}
@@ -1441,10 +1432,10 @@ mod conn {
assert_eq!(res.status(), hyper::StatusCode::OK);
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let timeout = Delay::new(Duration::from_millis(200));
let rx = rx.and_then(move |_| timeout.map_err(|e| e.into()));
let rx = rx.and_then(move |_| timeout.expect("timeout"));
res.join(rx).map(|r| r.0).wait().unwrap();
}
@@ -1490,17 +1481,14 @@ mod conn {
let res2 = client.send_request(req)
.then(|result| {
let err = result.expect_err("res2");
match err {
hyper::Error::Cancel(..) => (),
other => panic!("expected Cancel, found {:?}", other),
}
assert!(err.is_canceled(), "err not canceled, {:?}", err);
Ok(())
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let timeout = Delay::new(Duration::from_millis(200));
let rx = rx.and_then(move |_| timeout.map_err(|e| e.into()));
let rx = rx.and_then(move |_| timeout.expect("timeout"));
res1.join(res2).join(rx).map(|r| r.0).wait().unwrap();
}
@@ -1558,10 +1546,10 @@ mod conn {
res.into_body().into_stream().concat2()
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let timeout = Delay::new(Duration::from_millis(200));
let rx = rx.and_then(move |_| timeout.map_err(|e| e.into()));
let rx = rx.and_then(move |_| timeout.expect("timeout"));
until_upgrade.join(res).join(rx).map(|r| r.0).wait().unwrap();
// should not be ready now
@@ -1641,10 +1629,10 @@ mod conn {
assert_eq!(body.as_ref(), b"");
});
let rx = rx1.map_err(|_| hyper::Error::Io(io::Error::new(io::ErrorKind::Other, "thread panicked")));
let rx = rx1.expect("thread panicked");
let timeout = Delay::new(Duration::from_millis(200));
let rx = rx.and_then(move |_| timeout.map_err(|e| e.into()));
let rx = rx.and_then(move |_| timeout.expect("timeout"));
until_tunneled.join(res).join(rx).map(|r| r.0).wait().unwrap();
// should not be ready now
@@ -1697,3 +1685,17 @@ mod conn {
impl AsyncRead for DebugStream {}
}
trait FutureHyperExt: Future {
fn expect<E>(self, msg: &'static str) -> Box<Future<Item=Self::Item, Error=E>>;
}
impl<F> FutureHyperExt for F
where
F: Future + 'static,
F::Error: ::std::fmt::Debug,
{
fn expect<E>(self, msg: &'static str) -> Box<Future<Item=Self::Item, Error=E>> {
Box::new(self.map_err(move |e| panic!("expect: {}; error={:?}", msg, e)))
}
}

View File

@@ -935,7 +935,7 @@ fn returning_1xx_response_is_error() {
let socket = item.unwrap();
Http::<hyper::Chunk>::new()
.serve_connection(socket, service_fn(|_| {
Ok(Response::builder()
Ok::<_, hyper::Error>(Response::builder()
.status(StatusCode::CONTINUE)
.body(Body::empty())
.unwrap())
@@ -988,7 +988,7 @@ fn upgrades() {
.header("upgrade", "foobar")
.body(hyper::Body::empty())
.unwrap();
Ok(res)
Ok::<_, hyper::Error>(res)
}));
let mut conn_opt = Some(conn);
@@ -1144,10 +1144,10 @@ fn streaming_body() {
.keep_alive(false)
.serve_connection(socket, service_fn(|_| {
static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _;
let b = ::futures::stream::iter_ok(S.into_iter())
let b = ::futures::stream::iter_ok::<_, String>(S.into_iter())
.map(|&s| s);
let b = hyper::Body::wrap_stream(b);
Ok(Response::new(b))
Ok::<_, hyper::Error>(Response::new(b))
}))
.map(|_| ())
});