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:
		
							
								
								
									
										437
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										437
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -1,188 +1,316 @@ | ||||
| //! Error and Result module. | ||||
| use std::error::Error as StdError; | ||||
| use std::fmt; | ||||
| use std::io::Error as IoError; | ||||
| use std::str::Utf8Error; | ||||
| use std::string::FromUtf8Error; | ||||
| use std::io; | ||||
|  | ||||
| use httparse; | ||||
| use http; | ||||
|  | ||||
| use self::Error::{ | ||||
|     Method, | ||||
|     Version, | ||||
|     Uri, | ||||
|     Header, | ||||
|     Status, | ||||
|     Timeout, | ||||
|     Upgrade, | ||||
|     Closed, | ||||
|     Cancel, | ||||
|     Io, | ||||
|     TooLarge, | ||||
|     Incomplete, | ||||
|     Utf8 | ||||
| }; | ||||
|  | ||||
| /// Result type often returned from methods that can have hyper `Error`s. | ||||
| pub type Result<T> = ::std::result::Result<T, Error>; | ||||
|  | ||||
| /// A set of errors that can occur parsing HTTP streams. | ||||
| #[derive(Debug)] | ||||
| pub enum Error { | ||||
|     /// An invalid `Method`, such as `GE,T`. | ||||
|     Method, | ||||
|     /// An invalid `HttpVersion`, such as `HTP/1.1` | ||||
|     Version, | ||||
|     /// Uri Errors | ||||
|     Uri, | ||||
|     /// An invalid `Header`. | ||||
|     Header, | ||||
|     /// A message head is too large to be reasonable. | ||||
|     TooLarge, | ||||
| type Cause = Box<StdError + Send + Sync>; | ||||
|  | ||||
| /// Represents errors that can occur handling HTTP streams. | ||||
| pub struct Error { | ||||
|     inner: Box<ErrorImpl>, | ||||
| } | ||||
|  | ||||
| struct ErrorImpl { | ||||
|     kind: Kind, | ||||
|     cause: Option<Cause>, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq)] | ||||
| pub(crate) enum Kind { | ||||
|     Parse(Parse), | ||||
|     /// A message reached EOF, but is not complete. | ||||
|     Incomplete, | ||||
|     /// An invalid `Status`, such as `1337 ELITE`. | ||||
|     Status, | ||||
|     /// A timeout occurred waiting for an IO event. | ||||
|     Timeout, | ||||
|     /// A protocol upgrade was encountered, but not yet supported in hyper. | ||||
|     Upgrade, | ||||
|     /// A client connection received a response when not waiting for one. | ||||
|     MismatchedResponse, | ||||
|     /// A pending item was dropped before ever being processed. | ||||
|     Cancel(Canceled), | ||||
|     Canceled, | ||||
|     /// Indicates a connection is closed. | ||||
|     Closed, | ||||
|     /// An `io::Error` that occurred while trying to read or write to a network stream. | ||||
|     Io(IoError), | ||||
|     /// Parsing a field as string failed | ||||
|     Utf8(Utf8Error), | ||||
|     Io, | ||||
|     /// Error occurred while connecting. | ||||
|     Connect, | ||||
|     /// Error creating a TcpListener. | ||||
|     Listen, | ||||
|     /// Error accepting on an Incoming stream. | ||||
|     Accept, | ||||
|     /// Error calling user's NewService::new_service(). | ||||
|     NewService, | ||||
|     /// Error from future of user's Service::call(). | ||||
|     Service, | ||||
|     /// Error while reading a body from connection. | ||||
|     Body, | ||||
|     /// Error while writing a body to connection. | ||||
|     BodyWrite, | ||||
|     /// Error calling user's Entity::poll_data(). | ||||
|     BodyUser, | ||||
|     /// Error calling AsyncWrite::shutdown() | ||||
|     Shutdown, | ||||
|  | ||||
|     #[doc(hidden)] | ||||
|     __Nonexhaustive(Void) | ||||
|     /// User tried to create a Request with bad version. | ||||
|     UnsupportedVersion, | ||||
|     /// User tried to create a CONNECT Request with the Client. | ||||
|     UnsupportedRequestMethod, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, PartialEq)] | ||||
| pub(crate) enum Parse { | ||||
|     Method, | ||||
|     Version, | ||||
|     Uri, | ||||
|     Header, | ||||
|     TooLarge, | ||||
|     Status, | ||||
| } | ||||
|  | ||||
| /* | ||||
| #[derive(Debug)] | ||||
| pub(crate) enum User { | ||||
|     VersionNotSupported, | ||||
|     MethodNotSupported, | ||||
|     InvalidRequestUri, | ||||
| } | ||||
| */ | ||||
|  | ||||
| impl Error { | ||||
|     pub(crate) fn new_canceled<E: Into<Box<StdError + Send + Sync>>>(cause: Option<E>) -> Error { | ||||
|         Error::Cancel(Canceled { | ||||
|             cause: cause.map(Into::into), | ||||
|         }) | ||||
|     //TODO(error): should there be these kinds of inspection methods? | ||||
|     // | ||||
|     // - is_io() | ||||
|     // - is_connect() | ||||
|     // - is_closed() | ||||
|     // - etc? | ||||
|  | ||||
|     /// Returns true if this was an HTTP parse error. | ||||
|     pub fn is_parse(&self) -> bool { | ||||
|         match self.inner.kind { | ||||
|             Kind::Parse(_) => true, | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Returns true if this error was caused by user code. | ||||
|     pub fn is_user(&self) -> bool { | ||||
|         match self.inner.kind { | ||||
|             Kind::BodyUser | | ||||
|             Kind::NewService | | ||||
|             Kind::Service | | ||||
|             Kind::Closed | | ||||
|             Kind::UnsupportedVersion | | ||||
|             Kind::UnsupportedRequestMethod => true, | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Returns true if this was about a `Request` that was canceled. | ||||
|     pub fn is_canceled(&self) -> bool { | ||||
|         self.inner.kind == Kind::Canceled | ||||
|     } | ||||
|  | ||||
|     /// Returns true if a sender's channel is closed. | ||||
|     pub fn is_closed(&self) -> bool { | ||||
|         self.inner.kind == Kind::Closed | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new(kind: Kind, cause: Option<Cause>) -> Error { | ||||
|         Error { | ||||
|             inner: Box::new(ErrorImpl { | ||||
|                 kind, | ||||
|                 cause, | ||||
|             }), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn kind(&self) -> &Kind { | ||||
|         &self.inner.kind | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_canceled<E: Into<Cause>>(cause: Option<E>) -> Error { | ||||
|         Error::new(Kind::Canceled, cause.map(Into::into)) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_upgrade() -> Error { | ||||
|         Error::new(Kind::Upgrade, None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_incomplete() -> Error { | ||||
|         Error::new(Kind::Incomplete, None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_too_large() -> Error { | ||||
|         Error::new(Kind::Parse(Parse::TooLarge), None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_header() -> Error { | ||||
|         Error::new(Kind::Parse(Parse::Header), None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_status() -> Error { | ||||
|         Error::new(Kind::Parse(Parse::Status), None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_version() -> Error { | ||||
|         Error::new(Kind::Parse(Parse::Version), None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_mismatched_response() -> Error { | ||||
|         Error::new(Kind::MismatchedResponse, None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_io(cause: io::Error) -> Error { | ||||
|         Error::new(Kind::Io, Some(cause.into())) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_listen(err: io::Error) -> Error { | ||||
|         Error::new(Kind::Listen, Some(err.into())) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_accept(err: io::Error) -> Error { | ||||
|         Error::new(Kind::Accept, Some(Box::new(err))) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_connect<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::Connect, Some(cause.into())) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_closed() -> Error { | ||||
|         Error::new(Kind::Closed, None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_body<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::Body, Some(cause.into())) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_body_write(cause: io::Error) -> Error { | ||||
|         Error::new(Kind::BodyWrite, Some(Box::new(cause))) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_user_unsupported_version() -> Error { | ||||
|         Error::new(Kind::UnsupportedVersion, None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_user_unsupported_request_method() -> Error { | ||||
|         Error::new(Kind::UnsupportedRequestMethod, None) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_user_new_service(err: io::Error) -> Error { | ||||
|         Error::new(Kind::NewService, Some(Box::new(err))) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_user_service<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::Service, Some(cause.into())) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_user_body<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::BodyUser, Some(cause.into())) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn new_shutdown(cause: io::Error) -> Error { | ||||
|         Error::new(Kind::Shutdown, Some(Box::new(cause))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A pending item was dropped before ever being processed. | ||||
| /// | ||||
| /// For example, a `Request` could be queued in the `Client`, *just* | ||||
| /// as the related connection gets closed by the remote. In that case, | ||||
| /// when the connection drops, the pending response future will be | ||||
| /// fulfilled with this error, signaling the `Request` was never started. | ||||
| #[derive(Debug)] | ||||
| pub struct Canceled { | ||||
|     cause: Option<Box<StdError + Send + Sync>>, | ||||
| } | ||||
|  | ||||
| impl Canceled { | ||||
|     fn description(&self) -> &str { | ||||
|         "an operation was canceled internally before starting" | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Display for Canceled { | ||||
| impl fmt::Debug for Error { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         f.pad(self.description()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[doc(hidden)] | ||||
| pub struct Void(()); | ||||
|  | ||||
| impl fmt::Debug for Void { | ||||
|     fn fmt(&self, _: &mut fmt::Formatter) -> fmt::Result { | ||||
|         unreachable!() | ||||
|         f.debug_struct("Error") | ||||
|             .field("kind", &self.inner.kind) | ||||
|             .field("cause", &self.inner.cause) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Display for Error { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { | ||||
|         match *self { | ||||
|             Io(ref e) => fmt::Display::fmt(e, f), | ||||
|             Utf8(ref e) => fmt::Display::fmt(e, f), | ||||
|             ref e => f.write_str(e.description()), | ||||
|         if let Some(ref cause) = self.inner.cause { | ||||
|             write!(f, "{}: {}", self.description(), cause) | ||||
|         } else { | ||||
|             f.write_str(self.description()) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl StdError for Error { | ||||
|     fn description(&self) -> &str { | ||||
|         match *self { | ||||
|             Method => "invalid Method specified", | ||||
|             Version => "invalid HTTP version specified", | ||||
|             Uri => "invalid URI", | ||||
|             Header => "invalid Header provided", | ||||
|             TooLarge => "message head is too large", | ||||
|             Status => "invalid Status provided", | ||||
|             Incomplete => "message is incomplete", | ||||
|             Timeout => "timeout", | ||||
|             Upgrade => "unsupported protocol upgrade", | ||||
|             Closed => "connection is closed", | ||||
|             Cancel(ref e) => e.description(), | ||||
|             Io(ref e) => e.description(), | ||||
|             Utf8(ref e) => e.description(), | ||||
|             Error::__Nonexhaustive(..) =>  unreachable!(), | ||||
|         match self.inner.kind { | ||||
|             Kind::Parse(Parse::Method) => "invalid Method specified", | ||||
|             Kind::Parse(Parse::Version) => "invalid HTTP version specified", | ||||
|             Kind::Parse(Parse::Uri) => "invalid URI", | ||||
|             Kind::Parse(Parse::Header) => "invalid Header provided", | ||||
|             Kind::Parse(Parse::TooLarge) => "message head is too large", | ||||
|             Kind::Parse(Parse::Status) => "invalid Status provided", | ||||
|             Kind::Incomplete => "message is incomplete", | ||||
|             Kind::Upgrade => "unsupported protocol upgrade", | ||||
|             Kind::MismatchedResponse => "response received without matching request", | ||||
|             Kind::Closed => "connection closed", | ||||
|             Kind::Connect => "an error occurred trying to connect", | ||||
|             Kind::Canceled => "an operation was canceled internally before starting", | ||||
|             Kind::Listen => "error creating server listener", | ||||
|             Kind::Accept => "error accepting connection", | ||||
|             Kind::NewService => "calling user's new_service failed", | ||||
|             Kind::Service => "error from user's server service", | ||||
|             Kind::Body => "error reading a body from connection", | ||||
|             Kind::BodyWrite => "error write a body to connection", | ||||
|             Kind::BodyUser => "error from user's Entity stream", | ||||
|             Kind::Shutdown => "error shutting down connection", | ||||
|             Kind::UnsupportedVersion => "request has unsupported HTTP version", | ||||
|             Kind::UnsupportedRequestMethod => "request has unsupported HTTP method", | ||||
|  | ||||
|             Kind::Io => "an IO error occurred", | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn cause(&self) -> Option<&StdError> { | ||||
|         match *self { | ||||
|             Io(ref error) => Some(error), | ||||
|             Utf8(ref error) => Some(error), | ||||
|             Cancel(ref e) => e.cause.as_ref().map(|e| &**e as &StdError), | ||||
|             Error::__Nonexhaustive(..) =>  unreachable!(), | ||||
|             _ => None, | ||||
|         } | ||||
|         self | ||||
|             .inner | ||||
|             .cause | ||||
|             .as_ref() | ||||
|             .map(|cause| &**cause as &StdError) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<IoError> for Error { | ||||
|     fn from(err: IoError) -> Error { | ||||
|         Io(err) | ||||
| #[doc(hidden)] | ||||
| impl From<Parse> for Error { | ||||
|     fn from(err: Parse) -> Error { | ||||
|         Error::new(Kind::Parse(err), None) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Utf8Error> for Error { | ||||
|     fn from(err: Utf8Error) -> Error { | ||||
|         Utf8(err) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<FromUtf8Error> for Error { | ||||
|     fn from(err: FromUtf8Error) -> Error { | ||||
|         Utf8(err.utf8_error()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<httparse::Error> for Error { | ||||
|     fn from(err: httparse::Error) -> Error { | ||||
| impl From<httparse::Error> for Parse { | ||||
|     fn from(err: httparse::Error) -> Parse { | ||||
|         match err { | ||||
|             httparse::Error::HeaderName | | ||||
|             httparse::Error::HeaderValue | | ||||
|             httparse::Error::NewLine | | ||||
|             httparse::Error::Token => Header, | ||||
|             httparse::Error::Status => Status, | ||||
|             httparse::Error::TooManyHeaders => TooLarge, | ||||
|             httparse::Error::Version => Version, | ||||
|             httparse::Error::Token => Parse::Header, | ||||
|             httparse::Error::Status => Parse::Status, | ||||
|             httparse::Error::TooManyHeaders => Parse::TooLarge, | ||||
|             httparse::Error::Version => Parse::Version, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<http::method::InvalidMethod> for Error { | ||||
|     fn from(_: http::method::InvalidMethod) -> Error { | ||||
|         Error::Method | ||||
| impl From<http::method::InvalidMethod> for Parse { | ||||
|     fn from(_: http::method::InvalidMethod) -> Parse { | ||||
|         Parse::Method | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<http::uri::InvalidUriBytes> for Error { | ||||
|     fn from(_: http::uri::InvalidUriBytes) -> Error { | ||||
|         Error::Uri | ||||
| impl From<http::status::InvalidStatusCode> for Parse { | ||||
|     fn from(_: http::status::InvalidStatusCode) -> Parse { | ||||
|         Parse::Status | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<http::uri::InvalidUriBytes> for Parse { | ||||
|     fn from(_: http::uri::InvalidUriBytes) -> Parse { | ||||
|         Parse::Uri | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -191,58 +319,3 @@ trait AssertSendSync: Send + Sync + 'static {} | ||||
| #[doc(hidden)] | ||||
| impl AssertSendSync for Error {} | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use std::error::Error as StdError; | ||||
|     use std::io; | ||||
|     use httparse; | ||||
|     use super::Error; | ||||
|     use super::Error::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_cause() { | ||||
|         let orig = io::Error::new(io::ErrorKind::Other, "other"); | ||||
|         let desc = orig.description().to_owned(); | ||||
|         let e = Io(orig); | ||||
|         assert_eq!(e.cause().unwrap().description(), desc); | ||||
|     } | ||||
|  | ||||
|     macro_rules! from { | ||||
|         ($from:expr => $error:pat) => { | ||||
|             match Error::from($from) { | ||||
|                 e @ $error => { | ||||
|                     assert!(e.description().len() >= 5); | ||||
|                 } , | ||||
|                 e => panic!("{:?}", e) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     macro_rules! from_and_cause { | ||||
|         ($from:expr => $error:pat) => { | ||||
|             match Error::from($from) { | ||||
|                 e @ $error => { | ||||
|                     let desc = e.cause().unwrap().description(); | ||||
|                     assert_eq!(desc, $from.description().to_owned()); | ||||
|                     assert_eq!(desc, e.description()); | ||||
|                 }, | ||||
|                 _ => panic!("{:?}", $from) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_from() { | ||||
|  | ||||
|         from_and_cause!(io::Error::new(io::ErrorKind::Other, "other") => Io(..)); | ||||
|  | ||||
|         from!(httparse::Error::HeaderName => Header); | ||||
|         from!(httparse::Error::HeaderName => Header); | ||||
|         from!(httparse::Error::HeaderValue => Header); | ||||
|         from!(httparse::Error::NewLine => Header); | ||||
|         from!(httparse::Error::Status => Status); | ||||
|         from!(httparse::Error::Token => Header); | ||||
|         from!(httparse::Error::TooManyHeaders => TooLarge); | ||||
|         from!(httparse::Error::Version => Version); | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user