Skip to content

Commit e4f47ce

Browse files
committed
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.
1 parent 21ba669 commit e4f47ce

22 files changed

+538
-428
lines changed

benches/server.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ fn throughput_fixedsize_large_payload(b: &mut test::Bencher) {
7878
fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) {
7979
bench_server!(b, ("content-length", "1000000"), || {
8080
static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
81-
Body::wrap_stream(stream::iter_ok(S.iter()).map(|&s| s))
81+
Body::wrap_stream(stream::iter_ok::<_, String>(S.iter()).map(|&s| s))
8282
})
8383
}
8484

@@ -96,7 +96,7 @@ fn throughput_chunked_large_payload(b: &mut test::Bencher) {
9696
fn throughput_chunked_many_chunks(b: &mut test::Bencher) {
9797
bench_server!(b, ("transfer-encoding", "chunked"), || {
9898
static S: &'static [&'static [u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
99-
Body::wrap_stream(stream::iter_ok(S.iter()).map(|&s| s))
99+
Body::wrap_stream(stream::iter_ok::<_, String>(S.iter()).map(|&s| s))
100100
})
101101
}
102102

examples/client.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,9 @@ fn main() {
4040
println!("Response: {}", res.status());
4141
println!("Headers: {:#?}", res.headers());
4242

43-
res.into_parts().1.into_stream().for_each(|chunk| {
44-
io::stdout().write_all(&chunk).map_err(From::from)
43+
res.into_body().into_stream().for_each(|chunk| {
44+
io::stdout().write_all(&chunk)
45+
.map_err(|e| panic!("example expects stdout is open, error={}", e))
4546
})
4647
}).then(|result| {
4748
if let Some(err) = result.err() {

examples/hello.rs

+2-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,8 @@ fn main() {
1717
let addr = ([127, 0, 0, 1], 3000).into();
1818

1919
let new_service = const_service(service_fn(|_| {
20-
Ok(Response::new(Body::from(PHRASE)))
20+
//TODO: when `!` is stable, replace error type
21+
Ok::<_, hyper::Error>(Response::new(Body::from(PHRASE)))
2122
}));
2223

2324
tokio::runtime::run2(lazy(move |_| {

examples/send_file.rs

+4-5
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ use futures::future::lazy;
99
use futures::channel::oneshot;
1010

1111
use hyper::{Body, /*Chunk,*/ Method, Request, Response, StatusCode};
12-
use hyper::error::Error;
1312
use hyper::server::{Http, Service};
1413

1514
use std::fs::File;
@@ -19,7 +18,7 @@ use std::thread;
1918
static NOTFOUND: &[u8] = b"Not Found";
2019
static INDEX: &str = "examples/send_file_index.html";
2120

22-
fn simple_file_send(f: &str) -> Box<Future<Item = Response<Body>, Error = hyper::Error> + Send> {
21+
fn simple_file_send(f: &str) -> Box<Future<Item = Response<Body>, Error = io::Error> + Send> {
2322
// Serve a file by reading it entirely into memory. As a result
2423
// this is limited to serving small files, but it is somewhat
2524
// simpler with a little less overhead.
@@ -56,15 +55,15 @@ fn simple_file_send(f: &str) -> Box<Future<Item = Response<Body>, Error = hyper:
5655
};
5756
});
5857

59-
Box::new(rx.map_err(|e| Error::from(io::Error::new(io::ErrorKind::Other, e))))
58+
Box::new(rx.map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
6059
}
6160

6261
struct ResponseExamples;
6362

6463
impl Service for ResponseExamples {
6564
type Request = Request<Body>;
6665
type Response = Response<Body>;
67-
type Error = hyper::Error;
66+
type Error = io::Error;
6867
type Future = Box<Future<Item = Self::Response, Error = Self::Error> + Send>;
6968

7069
fn call(&self, req: Request<Body>) -> Self::Future {
@@ -119,7 +118,7 @@ impl Service for ResponseExamples {
119118
*/
120119
});
121120

122-
Box::new(rx.map_err(|e| Error::from(io::Error::new(io::ErrorKind::Other, e))))
121+
Box::new(rx.map_err(|e| io::Error::new(io::ErrorKind::Other, e)))
123122
},
124123
(&Method::GET, "/no_file.html") => {
125124
// Test what happens when file cannot be be found

src/client/conn.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ pub struct SendRequest<B> {
4646
pub struct Connection<T, B>
4747
where
4848
T: AsyncRead + AsyncWrite,
49-
B: Entity<Error=::Error> + 'static,
49+
B: Entity + 'static,
5050
{
5151
inner: proto::dispatch::Dispatcher<
5252
proto::dispatch::Client<B>,
@@ -139,7 +139,7 @@ impl<B> SendRequest<B>
139139

140140
impl<B> SendRequest<B>
141141
where
142-
B: Entity<Error=::Error> + 'static,
142+
B: Entity + 'static,
143143
{
144144
/// Sends a `Request` on the associated connection.
145145
///
@@ -263,7 +263,7 @@ impl<B> fmt::Debug for SendRequest<B> {
263263
impl<T, B> Connection<T, B>
264264
where
265265
T: AsyncRead + AsyncWrite,
266-
B: Entity<Error=::Error> + 'static,
266+
B: Entity + 'static,
267267
{
268268
/// Return the inner IO object, and additional information.
269269
pub fn into_parts(self) -> Parts<T> {
@@ -290,7 +290,7 @@ where
290290
impl<T, B> Future for Connection<T, B>
291291
where
292292
T: AsyncRead + AsyncWrite,
293-
B: Entity<Error=::Error> + 'static,
293+
B: Entity + 'static,
294294
{
295295
type Item = ();
296296
type Error = ::Error;
@@ -303,7 +303,7 @@ where
303303
impl<T, B> fmt::Debug for Connection<T, B>
304304
where
305305
T: AsyncRead + AsyncWrite + fmt::Debug,
306-
B: Entity<Error=::Error> + 'static,
306+
B: Entity + 'static,
307307
{
308308
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
309309
f.debug_struct("Connection")
@@ -332,7 +332,7 @@ impl Builder {
332332
pub fn handshake<T, B>(&self, io: T) -> Handshake<T, B>
333333
where
334334
T: AsyncRead + AsyncWrite,
335-
B: Entity<Error=::Error> + 'static,
335+
B: Entity + 'static,
336336
{
337337
Handshake {
338338
inner: HandshakeInner {
@@ -346,7 +346,7 @@ impl Builder {
346346
pub(super) fn handshake_no_upgrades<T, B>(&self, io: T) -> HandshakeNoUpgrades<T, B>
347347
where
348348
T: AsyncRead + AsyncWrite,
349-
B: Entity<Error=::Error> + 'static,
349+
B: Entity + 'static,
350350
{
351351
HandshakeNoUpgrades {
352352
inner: HandshakeInner {
@@ -363,7 +363,7 @@ impl Builder {
363363
impl<T, B> Future for Handshake<T, B>
364364
where
365365
T: AsyncRead + AsyncWrite,
366-
B: Entity<Error=::Error> + 'static,
366+
B: Entity + 'static,
367367
{
368368
type Item = (SendRequest<B>, Connection<T, B>);
369369
type Error = ::Error;
@@ -388,7 +388,7 @@ impl<T, B> fmt::Debug for Handshake<T, B> {
388388
impl<T, B> Future for HandshakeNoUpgrades<T, B>
389389
where
390390
T: AsyncRead + AsyncWrite,
391-
B: Entity<Error=::Error> + 'static,
391+
B: Entity + 'static,
392392
{
393393
type Item = (SendRequest<B>, proto::dispatch::Dispatcher<
394394
proto::dispatch::Client<B>,
@@ -407,7 +407,7 @@ where
407407
impl<T, B, R> Future for HandshakeInner<T, B, R>
408408
where
409409
T: AsyncRead + AsyncWrite,
410-
B: Entity<Error=::Error> + 'static,
410+
B: Entity + 'static,
411411
R: proto::Http1Transaction<
412412
Incoming=StatusCode,
413413
Outgoing=proto::RequestLine,
@@ -471,15 +471,15 @@ impl<B: Send> AssertSendSync for SendRequest<B> {}
471471
impl<T: Send, B: Send> AssertSend for Connection<T, B>
472472
where
473473
T: AsyncRead + AsyncWrite,
474-
B: Entity<Error=::Error> + 'static,
474+
B: Entity + 'static,
475475
B::Data: Send + 'static,
476476
{}
477477

478478
#[doc(hidden)]
479479
impl<T: Send + Sync, B: Send + Sync> AssertSendSync for Connection<T, B>
480480
where
481481
T: AsyncRead + AsyncWrite,
482-
B: Entity<Error=::Error> + 'static,
482+
B: Entity + 'static,
483483
B::Data: Send + Sync + 'static,
484484
{}
485485

src/client/connect.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ pub trait Connect: Send + Sync {
3333
/// The connected IO Stream.
3434
type Transport: AsyncRead + AsyncWrite + Send + 'static;
3535
/// An error occured when trying to connect.
36-
type Error;
36+
type Error: Into<Box<StdError + Send + Sync>>;
3737
/// A Future that will resolve to the connected Transport.
3838
type Future: Future<Item=(Self::Transport, Connected), Error=Self::Error> + Send;
3939
/// Connect to a destination.

src/client/dispatch.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,10 @@ impl<T, U> Sender<T, U> {
3838
// there's room in the queue, but does the Connection
3939
// want a message yet?
4040
self.giver.poll_want(cx)
41-
.map_err(|_| ::Error::Closed)
41+
.map_err(|_| ::Error::new_closed())
4242
},
4343
Ok(Async::Pending) => Ok(Async::Pending),
44-
Err(_) => Err(::Error::Closed),
44+
Err(_) => Err(::Error::new_closed()),
4545
}
4646
}
4747

@@ -183,9 +183,12 @@ mod tests {
183183
drop(rx);
184184

185185
promise.then(|fulfilled| {
186-
let res = fulfilled.expect("fulfilled");
187-
match res.unwrap_err() {
188-
(::Error::Cancel(_), Some(_)) => (),
186+
let err = fulfilled
187+
.expect("fulfilled")
188+
.expect_err("promise should error");
189+
190+
match (err.0.kind(), err.1) {
191+
(&::error::Kind::Canceled, Some(_)) => (),
189192
e => panic!("expected Error::Cancel(_), found {:?}", e),
190193
}
191194

src/client/mod.rs

+19-32
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use std::marker::PhantomData;
66
use std::sync::Arc;
77
use std::time::Duration;
88

9-
use futures::{Async, Future, FutureExt, Never, Poll};
9+
use futures::{Async, Future, FutureExt, Poll};
1010
use futures::channel::oneshot;
1111
use futures::future;
1212
use futures::task;
@@ -93,10 +93,10 @@ impl<C, B> Client<C, B> {
9393
}
9494

9595
impl<C, B> Client<C, B>
96-
where C: Connect<Error=io::Error> + Sync + 'static,
96+
where C: Connect + Sync + 'static,
9797
C::Transport: 'static,
9898
C::Future: 'static,
99-
B: Entity<Error=::Error> + Send + 'static,
99+
B: Entity + Send + 'static,
100100
B::Data: Send,
101101
{
102102

@@ -128,13 +128,14 @@ where C: Connect<Error=io::Error> + Sync + 'static,
128128
Version::HTTP_11 => (),
129129
other => {
130130
error!("Request has unsupported version \"{:?}\"", other);
131-
return FutureResponse(Box::new(future::err(::Error::Version)));
131+
//TODO: replace this with a proper variant
132+
return FutureResponse(Box::new(future::err(::Error::new_user_unsupported_version())));
132133
}
133134
}
134135

135136
if req.method() == &Method::CONNECT {
136137
debug!("Client does not support CONNECT requests");
137-
return FutureResponse(Box::new(future::err(::Error::Method)));
138+
return FutureResponse(Box::new(future::err(::Error::new_user_unsupported_request_method())));
138139
}
139140

140141
let uri = req.uri().clone();
@@ -143,7 +144,8 @@ where C: Connect<Error=io::Error> + Sync + 'static,
143144
format!("{}://{}", scheme, auth)
144145
}
145146
_ => {
146-
return FutureResponse(Box::new(future::err(::Error::Io(
147+
//TODO: replace this with a proper variant
148+
return FutureResponse(Box::new(future::err(::Error::new_io(
147149
io::Error::new(
148150
io::ErrorKind::InvalidInput,
149151
"invalid URI for Client Request"
@@ -190,16 +192,16 @@ where C: Connect<Error=io::Error> + Sync + 'static,
190192
};
191193
future::lazy(move |_| {
192194
connector.connect(dst)
193-
.err_into()
195+
.map_err(::Error::new_connect)
194196
.and_then(move |(io, connected)| {
195197
conn::Builder::new()
196198
.h1_writev(h1_writev)
197199
.handshake_no_upgrades(io)
198200
.and_then(move |(tx, conn)| {
199201
future::lazy(move |cx| {
200-
execute(conn.recover(|e| {
202+
cx.spawn(conn.recover(|e| {
201203
debug!("client connection error: {}", e);
202-
}), cx)?;
204+
}));
203205
Ok(pool.pooled(pool_key, PoolClient {
204206
is_proxied: connected.is_proxied,
205207
tx: tx,
@@ -251,8 +253,6 @@ where C: Connect<Error=io::Error> + Sync + 'static,
251253
} else if !res.body().is_empty() {
252254
let (delayed_tx, delayed_rx) = oneshot::channel();
253255
res.body_mut().delayed_eof(delayed_rx);
254-
// If the executor doesn't have room, oh well. Things will likely
255-
// be blowing up soon, but this specific task isn't required.
256256
let fut = future::poll_fn(move |cx| {
257257
pooled.tx.poll_ready(cx)
258258
})
@@ -262,7 +262,7 @@ where C: Connect<Error=io::Error> + Sync + 'static,
262262
drop(delayed_tx);
263263
Ok(())
264264
});
265-
execute(fut, cx).ok();
265+
cx.spawn(fut);
266266
}
267267
Ok(res)
268268
})
@@ -277,9 +277,9 @@ where C: Connect<Error=io::Error> + Sync + 'static,
277277
}
278278

279279
impl<C, B> Service for Client<C, B>
280-
where C: Connect<Error=io::Error> + 'static,
280+
where C: Connect + 'static,
281281
C::Future: 'static,
282-
B: Entity<Error=::Error> + Send + 'static,
282+
B: Entity + Send + 'static,
283283
B::Data: Send,
284284
{
285285
type Request = Request<B>;
@@ -339,9 +339,9 @@ struct RetryableSendRequest<C, B> {
339339

340340
impl<C, B> Future for RetryableSendRequest<C, B>
341341
where
342-
C: Connect<Error=io::Error> + 'static,
342+
C: Connect + 'static,
343343
C::Future: 'static,
344-
B: Entity<Error=::Error> + Send + 'static,
344+
B: Entity + Send + 'static,
345345
B::Data: Send,
346346
{
347347
type Item = Response<Body>;
@@ -550,10 +550,10 @@ impl<C, B> Config<C, B> {
550550
}
551551

552552
impl<C, B> Config<C, B>
553-
where C: Connect<Error=io::Error>,
553+
where C: Connect,
554554
C::Transport: 'static,
555555
C::Future: 'static,
556-
B: Entity<Error=::Error> + Send,
556+
B: Entity + Send,
557557
B::Data: Send,
558558
{
559559
/// Construct the Client with this configuration.
@@ -565,7 +565,7 @@ where C: Connect<Error=io::Error>,
565565

566566
impl<B> Config<UseDefaultConnector, B>
567567
where
568-
B: Entity<Error=::Error> + Send,
568+
B: Entity + Send,
569569
B::Data: Send,
570570
{
571571
/// Construct the Client with this configuration.
@@ -600,16 +600,3 @@ impl<C: Clone, B> Clone for Config<C, B> {
600600
}
601601
}
602602

603-
604-
fn execute<F>(fut: F, cx: &mut task::Context) -> Result<(), ::Error>
605-
where F: Future<Item=(), Error=Never> + Send + 'static,
606-
{
607-
if let Some(executor) = cx.executor() {
608-
executor.spawn(Box::new(fut)).map_err(|err| {
609-
debug!("executor error: {:?}", err);
610-
::Error::Executor
611-
})
612-
} else {
613-
Err(::Error::Executor)
614-
}
615-
}

0 commit comments

Comments
 (0)