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:
		| @@ -18,8 +18,7 @@ pub trait Entity { | ||||
|     type Data: AsRef<[u8]>; | ||||
|  | ||||
|     /// The error type of this stream. | ||||
|     //TODO: add bounds Into<::error::User> (or whatever it is called) | ||||
|     type Error; | ||||
|     type Error: Into<Box<::std::error::Error + Send + Sync>>; | ||||
|  | ||||
|     /// Poll for a `Data` buffer. | ||||
|     /// | ||||
| @@ -141,7 +140,7 @@ enum Kind { | ||||
|         _close_tx: oneshot::Sender<()>, | ||||
|         rx: mpsc::Receiver<Result<Chunk, ::Error>>, | ||||
|     }, | ||||
|     Wrapped(Box<Stream<Item=Chunk, Error=::Error> + Send>), | ||||
|     Wrapped(Box<Stream<Item=Chunk, Error=Box<::std::error::Error + Send + Sync>> + Send>), | ||||
|     Once(Option<Chunk>), | ||||
|     Empty, | ||||
| } | ||||
| @@ -212,17 +211,22 @@ impl Body { | ||||
|     ///     " ", | ||||
|     ///     "world", | ||||
|     /// ]; | ||||
|     /// let stream = futures::stream::iter_ok(chunks); | ||||
|     /// | ||||
|     /// let stream = futures::stream::iter_ok::<_, ::std::io::Error>(chunks); | ||||
|     /// | ||||
|     /// let body = Body::wrap_stream(stream); | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn wrap_stream<S>(stream: S) -> Body | ||||
|     where | ||||
|         S: Stream<Error=::Error> + Send + 'static, | ||||
|         S: Stream + Send + 'static, | ||||
|         S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|         Chunk: From<S::Item>, | ||||
|     { | ||||
|         Body::new(Kind::Wrapped(Box::new(stream.map(Chunk::from)))) | ||||
|         let mapped = stream | ||||
|             .map(Chunk::from) | ||||
|             .map_err(Into::into); | ||||
|         Body::new(Kind::Wrapped(Box::new(mapped))) | ||||
|     } | ||||
|  | ||||
|     /// Convert this `Body` into a `Stream<Item=Chunk, Error=hyper::Error>`. | ||||
| @@ -327,7 +331,7 @@ impl Body { | ||||
|                 Async::Ready(None) => Ok(Async::Ready(None)), | ||||
|                 Async::NotReady => Ok(Async::NotReady), | ||||
|             }, | ||||
|             Kind::Wrapped(ref mut s) => s.poll(), | ||||
|             Kind::Wrapped(ref mut s) => s.poll().map_err(::Error::new_body), | ||||
|             Kind::Once(ref mut val) => Ok(Async::Ready(val.take())), | ||||
|             Kind::Empty => Ok(Async::Ready(None)), | ||||
|         } | ||||
|   | ||||
| @@ -20,7 +20,7 @@ use super::{EncodedBuf, Encoder, Decoder}; | ||||
| /// The connection will determine when a message begins and ends as well as | ||||
| /// determine if this connection can be kept alive after the message, | ||||
| /// or if it is complete. | ||||
| pub struct Conn<I, B, T> { | ||||
| pub(crate) struct Conn<I, B, T> { | ||||
|     io: Buffered<I, EncodedBuf<Cursor<B>>>, | ||||
|     state: State, | ||||
|     _marker: PhantomData<T> | ||||
| @@ -146,7 +146,8 @@ where I: AsyncRead + AsyncWrite, | ||||
|                 _ => { | ||||
|                     error!("unimplemented HTTP Version = {:?}", version); | ||||
|                     self.state.close_read(); | ||||
|                     return Err(::Error::Version); | ||||
|                     //TODO: replace this with a more descriptive error | ||||
|                     return Err(::Error::new_version()); | ||||
|                 } | ||||
|             }; | ||||
|             self.state.version = version; | ||||
| @@ -245,7 +246,7 @@ where I: AsyncRead + AsyncWrite, | ||||
|         if self.is_mid_message() { | ||||
|             self.maybe_park_read(); | ||||
|         } else { | ||||
|             self.require_empty_read()?; | ||||
|             self.require_empty_read().map_err(::Error::new_io)?; | ||||
|         } | ||||
|         Ok(()) | ||||
|     } | ||||
|   | ||||
| @@ -1,5 +1,3 @@ | ||||
| use std::io; | ||||
|  | ||||
| use bytes::Bytes; | ||||
| use futures::{Async, Future, Poll, Stream}; | ||||
| use http::{Request, Response, StatusCode}; | ||||
| @@ -9,7 +7,7 @@ use tokio_service::Service; | ||||
| use proto::body::Entity; | ||||
| use proto::{Body, BodyLength, Conn, Http1Transaction, MessageHead, RequestHead, RequestLine, ResponseHead}; | ||||
|  | ||||
| pub struct Dispatcher<D, Bs, I, B, T> { | ||||
| pub(crate) struct Dispatcher<D, Bs, I, B, T> { | ||||
|     conn: Conn<I, B, T>, | ||||
|     dispatch: D, | ||||
|     body_tx: Option<::proto::body::Sender>, | ||||
| @@ -17,7 +15,7 @@ pub struct Dispatcher<D, Bs, I, B, T> { | ||||
|     is_closing: bool, | ||||
| } | ||||
|  | ||||
| pub trait Dispatch { | ||||
| pub(crate) trait Dispatch { | ||||
|     type PollItem; | ||||
|     type PollBody; | ||||
|     type RecvItem; | ||||
| @@ -47,7 +45,7 @@ where | ||||
|     I: AsyncRead + AsyncWrite, | ||||
|     B: AsRef<[u8]>, | ||||
|     T: Http1Transaction, | ||||
|     Bs: Entity<Data=B, Error=::Error>, | ||||
|     Bs: Entity<Data=B>, | ||||
| { | ||||
|     pub fn new(dispatch: D, conn: Conn<I, B, T>) -> Self { | ||||
|         Dispatcher { | ||||
| @@ -98,7 +96,7 @@ where | ||||
|  | ||||
|         if self.is_done() { | ||||
|             if should_shutdown { | ||||
|                 try_ready!(self.conn.shutdown()); | ||||
|                 try_ready!(self.conn.shutdown().map_err(::Error::new_shutdown)); | ||||
|             } | ||||
|             self.conn.take_error()?; | ||||
|             Ok(Async::Ready(())) | ||||
| @@ -152,7 +150,7 @@ where | ||||
|                             return Ok(Async::NotReady); | ||||
|                         } | ||||
|                         Err(e) => { | ||||
|                             body.send_error(::Error::Io(e)); | ||||
|                             body.send_error(::Error::new_body(e)); | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
| @@ -225,14 +223,14 @@ where | ||||
|             } else if !self.conn.can_buffer_body() { | ||||
|                 try_ready!(self.poll_flush()); | ||||
|             } else if let Some(mut body) = self.body_rx.take() { | ||||
|                 let chunk = match body.poll_data()? { | ||||
|                 let chunk = match body.poll_data().map_err(::Error::new_user_body)? { | ||||
|                     Async::Ready(Some(chunk)) => { | ||||
|                         self.body_rx = Some(body); | ||||
|                         chunk | ||||
|                     }, | ||||
|                     Async::Ready(None) => { | ||||
|                         if self.conn.can_write_body() { | ||||
|                             self.conn.write_body(None)?; | ||||
|                             self.conn.write_body(None).map_err(::Error::new_body_write)?; | ||||
|                         } | ||||
|                         continue; | ||||
|                     }, | ||||
| @@ -243,7 +241,7 @@ where | ||||
|                 }; | ||||
|  | ||||
|                 if self.conn.can_write_body() { | ||||
|                     assert!(self.conn.write_body(Some(chunk))?.is_ready()); | ||||
|                     self.conn.write_body(Some(chunk)).map_err(::Error::new_body_write)?; | ||||
|                 // This allows when chunk is `None`, or `Some([])`. | ||||
|                 } else if chunk.as_ref().len() == 0 { | ||||
|                     // ok | ||||
| @@ -259,7 +257,7 @@ where | ||||
|     fn poll_flush(&mut self) -> Poll<(), ::Error> { | ||||
|         self.conn.flush().map_err(|err| { | ||||
|             debug!("error writing: {}", err); | ||||
|             err.into() | ||||
|             ::Error::new_body_write(err) | ||||
|         }) | ||||
|     } | ||||
|  | ||||
| @@ -294,7 +292,7 @@ where | ||||
|     I: AsyncRead + AsyncWrite, | ||||
|     B: AsRef<[u8]>, | ||||
|     T: Http1Transaction, | ||||
|     Bs: Entity<Data=B, Error=::Error>, | ||||
|     Bs: Entity<Data=B>, | ||||
| { | ||||
|     type Item = (); | ||||
|     type Error = ::Error; | ||||
| @@ -318,8 +316,9 @@ impl<S> Server<S> where S: Service { | ||||
|  | ||||
| impl<S, Bs> Dispatch for Server<S> | ||||
| where | ||||
|     S: Service<Request=Request<Body>, Response=Response<Bs>, Error=::Error>, | ||||
|     Bs: Entity<Error=::Error>, | ||||
|     S: Service<Request=Request<Body>, Response=Response<Bs>>, | ||||
|     S::Error: Into<Box<::std::error::Error + Send + Sync>>, | ||||
|     Bs: Entity, | ||||
| { | ||||
|     type PollItem = MessageHead<StatusCode>; | ||||
|     type PollBody = Bs; | ||||
| @@ -327,7 +326,7 @@ where | ||||
|  | ||||
|     fn poll_msg(&mut self) -> Poll<Option<(Self::PollItem, Option<Self::PollBody>)>, ::Error> { | ||||
|         if let Some(mut fut) = self.in_flight.take() { | ||||
|             let resp = match fut.poll()? { | ||||
|             let resp = match fut.poll().map_err(::Error::new_user_service)? { | ||||
|                 Async::Ready(res) => res, | ||||
|                 Async::NotReady => { | ||||
|                     self.in_flight = Some(fut); | ||||
| @@ -389,7 +388,7 @@ impl<B> Client<B> { | ||||
|  | ||||
| impl<B> Dispatch for Client<B> | ||||
| where | ||||
|     B: Entity<Error=::Error>, | ||||
|     B: Entity, | ||||
| { | ||||
|     type PollItem = RequestHead; | ||||
|     type PollBody = B; | ||||
| @@ -443,7 +442,7 @@ where | ||||
|                     let _ = cb.send(Ok(res)); | ||||
|                     Ok(()) | ||||
|                 } else { | ||||
|                     Err(::Error::Io(io::Error::new(io::ErrorKind::InvalidData, "response received without matching request"))) | ||||
|                     Err(::Error::new_mismatched_response()) | ||||
|                 } | ||||
|             }, | ||||
|             Err(err) => { | ||||
| @@ -507,8 +506,15 @@ mod tests { | ||||
|                 .expect("callback poll") | ||||
|                 .expect_err("callback response"); | ||||
|  | ||||
|             match err { | ||||
|                 (::Error::Cancel(_), Some(_)) => (), | ||||
|             /* | ||||
|             let err = match async { | ||||
|                 Async::Ready(result) => result.unwrap_err(), | ||||
|                 Async::Pending => panic!("callback should be ready"), | ||||
|             }; | ||||
|             */ | ||||
|  | ||||
|             match (err.0.kind(), err.1) { | ||||
|                 (&::error::Kind::Canceled, Some(_)) => (), | ||||
|                 other => panic!("expected Canceled, got {:?}", other), | ||||
|             } | ||||
|             Ok::<(), ()>(()) | ||||
|   | ||||
| @@ -108,7 +108,7 @@ where | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn parse<S: Http1Transaction>(&mut self) -> Poll<MessageHead<S::Incoming>, ::Error> { | ||||
|     pub(super) fn parse<S: Http1Transaction>(&mut self) -> Poll<MessageHead<S::Incoming>, ::Error> { | ||||
|         loop { | ||||
|             match try!(S::parse(&mut self.read_buf)) { | ||||
|                 Some((head, len)) => { | ||||
| @@ -118,14 +118,14 @@ where | ||||
|                 None => { | ||||
|                     if self.read_buf.capacity() >= self.max_buf_size { | ||||
|                         debug!("max_buf_size ({}) reached, closing", self.max_buf_size); | ||||
|                         return Err(::Error::TooLarge); | ||||
|                         return Err(::Error::new_too_large()); | ||||
|                     } | ||||
|                 }, | ||||
|             } | ||||
|             match try_ready!(self.read_from_io()) { | ||||
|             match try_ready!(self.read_from_io().map_err(::Error::new_io)) { | ||||
|                 0 => { | ||||
|                     trace!("parse eof"); | ||||
|                     return Err(::Error::Incomplete); | ||||
|                     return Err(::Error::new_incomplete()); | ||||
|                 } | ||||
|                 _ => {}, | ||||
|             } | ||||
|   | ||||
| @@ -1,11 +1,11 @@ | ||||
| pub use self::conn::Conn; | ||||
| pub(crate) use self::conn::Conn; | ||||
| pub use self::decode::Decoder; | ||||
| pub use self::encode::{EncodedBuf, Encoder}; | ||||
|  | ||||
| mod conn; | ||||
| mod date; | ||||
| mod decode; | ||||
| pub mod dispatch; | ||||
| pub(crate) mod dispatch; | ||||
| mod encode; | ||||
| mod io; | ||||
| pub mod role; | ||||
|   | ||||
| @@ -40,7 +40,7 @@ where | ||||
|             let mut headers = [httparse::EMPTY_HEADER; MAX_HEADERS]; | ||||
|             trace!("Request.parse([Header; {}], [u8; {}])", headers.len(), buf.len()); | ||||
|             let mut req = httparse::Request::new(&mut headers); | ||||
|             match try!(req.parse(&buf)) { | ||||
|             match req.parse(&buf)? { | ||||
|                 httparse::Status::Complete(len) => { | ||||
|                     trace!("Request.parse Complete({})", len); | ||||
|                     let method = Method::from_bytes(req.method.unwrap().as_bytes())?; | ||||
| @@ -104,18 +104,18 @@ where | ||||
|             // mal-formed. A server should respond with 400 Bad Request. | ||||
|             if head.version == Version::HTTP_10 { | ||||
|                 debug!("HTTP/1.0 cannot have Transfer-Encoding header"); | ||||
|                 Err(::Error::Header) | ||||
|                 Err(::Error::new_header()) | ||||
|             } else if headers::transfer_encoding_is_chunked(&head.headers) { | ||||
|                 Ok(Decode::Normal(Decoder::chunked())) | ||||
|             } else { | ||||
|                 debug!("request with transfer-encoding header, but not chunked, bad request"); | ||||
|                 Err(::Error::Header) | ||||
|                 Err(::Error::new_header()) | ||||
|             } | ||||
|         } else if let Some(len) = headers::content_length_parse(&head.headers) { | ||||
|             Ok(Decode::Normal(Decoder::length(len))) | ||||
|         } else if head.headers.contains_key(CONTENT_LENGTH) { | ||||
|             debug!("illegal Content-Length header"); | ||||
|             Err(::Error::Header) | ||||
|             Err(::Error::new_header()) | ||||
|         } else { | ||||
|             Ok(Decode::Normal(Decoder::length(0))) | ||||
|         } | ||||
| @@ -146,7 +146,8 @@ where | ||||
|             head = MessageHead::default(); | ||||
|             head.subject = StatusCode::INTERNAL_SERVER_ERROR; | ||||
|             headers::content_length_zero(&mut head.headers); | ||||
|             Err(::Error::Status) | ||||
|             //TODO: change this to a more descriptive error than just a parse error | ||||
|             Err(::Error::new_status()) | ||||
|         } else { | ||||
|             Ok(Server::set_length(&mut head, body, method.as_ref())) | ||||
|         }; | ||||
| @@ -184,14 +185,15 @@ where | ||||
|     } | ||||
|  | ||||
|     fn on_error(err: &::Error) -> Option<MessageHead<Self::Outgoing>> { | ||||
|         let status = match err { | ||||
|             &::Error::Method | | ||||
|             &::Error::Version | | ||||
|             &::Error::Header /*| | ||||
|             &::Error::Uri(_)*/ => { | ||||
|         use ::error::{Kind, Parse}; | ||||
|         let status = match *err.kind() { | ||||
|             Kind::Parse(Parse::Method) | | ||||
|             Kind::Parse(Parse::Version) | | ||||
|             Kind::Parse(Parse::Header) | | ||||
|             Kind::Parse(Parse::Uri) => { | ||||
|                 StatusCode::BAD_REQUEST | ||||
|             }, | ||||
|             &::Error::TooLarge => { | ||||
|             Kind::Parse(Parse::TooLarge) => { | ||||
|                 StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE | ||||
|             } | ||||
|             _ => return None, | ||||
| @@ -271,7 +273,7 @@ where | ||||
|             match try!(res.parse(bytes)) { | ||||
|                 httparse::Status::Complete(len) => { | ||||
|                     trace!("Response.parse Complete({})", len); | ||||
|                     let status = try!(StatusCode::from_u16(res.code.unwrap()).map_err(|_| ::Error::Status)); | ||||
|                     let status = StatusCode::from_u16(res.code.unwrap())?; | ||||
|                     let version = if res.version.unwrap() == 1 { | ||||
|                         Version::HTTP_11 | ||||
|                     } else { | ||||
| @@ -343,7 +345,7 @@ where | ||||
|             // mal-formed. A server should respond with 400 Bad Request. | ||||
|             if inc.version == Version::HTTP_10 { | ||||
|                 debug!("HTTP/1.0 cannot have Transfer-Encoding header"); | ||||
|                 Err(::Error::Header) | ||||
|                 Err(::Error::new_header()) | ||||
|             } else if headers::transfer_encoding_is_chunked(&inc.headers) { | ||||
|                 Ok(Decode::Normal(Decoder::chunked())) | ||||
|             } else { | ||||
| @@ -354,7 +356,7 @@ where | ||||
|             Ok(Decode::Normal(Decoder::length(len))) | ||||
|         } else if inc.headers.contains_key(CONTENT_LENGTH) { | ||||
|             debug!("illegal Content-Length header"); | ||||
|             Err(::Error::Header) | ||||
|             Err(::Error::new_header()) | ||||
|         } else { | ||||
|             trace!("neither Transfer-Encoding nor Content-Length"); | ||||
|             Ok(Decode::Normal(Decoder::eof())) | ||||
| @@ -577,12 +579,13 @@ impl OnUpgrade for NoUpgrades { | ||||
|         *head = MessageHead::default(); | ||||
|         head.subject = ::StatusCode::INTERNAL_SERVER_ERROR; | ||||
|         headers::content_length_zero(&mut head.headers); | ||||
|         Err(::Error::Status) | ||||
|         //TODO: replace with more descriptive error | ||||
|         return Err(::Error::new_status()); | ||||
|     } | ||||
|  | ||||
|     fn on_decode_upgrade() -> ::Result<Decoder> { | ||||
|         debug!("received 101 upgrade response, not supported"); | ||||
|         return Err(::Error::Upgrade); | ||||
|         return Err(::Error::new_upgrade()); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -6,7 +6,7 @@ use headers; | ||||
|  | ||||
| pub use self::body::Body; | ||||
| pub use self::chunk::Chunk; | ||||
| pub use self::h1::{dispatch, Conn}; | ||||
| pub(crate) use self::h1::{dispatch, Conn}; | ||||
|  | ||||
| pub mod body; | ||||
| mod chunk; | ||||
| @@ -60,14 +60,14 @@ pub fn expecting_continue(version: Version, headers: &HeaderMap) -> bool { | ||||
|     version == Version::HTTP_11 && headers::expect_continue(headers) | ||||
| } | ||||
|  | ||||
| pub type ServerTransaction = h1::role::Server<h1::role::YesUpgrades>; | ||||
| pub(crate) type ServerTransaction = h1::role::Server<h1::role::YesUpgrades>; | ||||
| //pub type ServerTransaction = h1::role::Server<h1::role::NoUpgrades>; | ||||
| //pub type ServerUpgradeTransaction = h1::role::Server<h1::role::YesUpgrades>; | ||||
|  | ||||
| pub type ClientTransaction = h1::role::Client<h1::role::NoUpgrades>; | ||||
| pub type ClientUpgradeTransaction = h1::role::Client<h1::role::YesUpgrades>; | ||||
| pub(crate) type ClientTransaction = h1::role::Client<h1::role::NoUpgrades>; | ||||
| pub(crate) type ClientUpgradeTransaction = h1::role::Client<h1::role::YesUpgrades>; | ||||
|  | ||||
| pub trait Http1Transaction { | ||||
| pub(crate) trait Http1Transaction { | ||||
|     type Incoming; | ||||
|     type Outgoing: Default; | ||||
|     fn parse(bytes: &mut BytesMut) -> ParseResult<Self::Incoming>; | ||||
| @@ -84,7 +84,7 @@ pub trait Http1Transaction { | ||||
|     fn should_read_first() -> bool; | ||||
| } | ||||
|  | ||||
| pub type ParseResult<T> = ::Result<Option<(MessageHead<T>, usize)>>; | ||||
| pub(crate) type ParseResult<T> = Result<Option<(MessageHead<T>, usize)>, ::error::Parse>; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub enum BodyLength { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user