diff --git a/src/body/length.rs b/src/body/length.rs index aa9cf3dcd5..1599a74e8b 100644 --- a/src/body/length.rs +++ b/src/body/length.rs @@ -43,7 +43,7 @@ impl DecodedLength { Ok(DecodedLength(len)) } else { warn!("content-length bigger than maximum: {} > {}", len, MAX_LEN); - Err(crate::error::Parse::TooLarge) + Err(crate::error::Parse::new_too_large()) } } diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index 484cb04f4b..f4b84a014f 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -317,7 +317,7 @@ mod tests { .expect("fulfilled") .expect_err("promise should error"); match (err.0.kind(), err.1) { - (&crate::error::Kind::Canceled, Some(_)) => (), + (&crate::error::Kind::Canceled(_), Some(_)) => (), e => panic!("expected Error::Cancel(_), found {:?}", e), } } diff --git a/src/client/service.rs b/src/client/service.rs index 4013c5e54e..176555a4e5 100644 --- a/src/client/service.rs +++ b/src/client/service.rs @@ -55,7 +55,7 @@ where fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { self.inner .poll_ready(cx) - .map_err(|e| crate::Error::new(crate::error::Kind::Connect).with(e.into())) + .map_err(|e| crate::Error::new_connect(e)) } fn call(&mut self, req: T) -> Self::Future { @@ -76,7 +76,7 @@ where Err(e) => Err(e), }, Err(e) => { - let err = crate::Error::new(crate::error::Kind::Connect).with(e.into()); + let err = crate::Error::new_connect(e); Err(err) } } diff --git a/src/error.rs b/src/error.rs index 663156e0a9..1643dea65f 100644 --- a/src/error.rs +++ b/src/error.rs @@ -2,6 +2,8 @@ use std::error::Error as StdError; use std::fmt; +use self::sealed::Sealed; + /// Result type often returned from methods that can have hyper `Error`s. pub type Result = std::result::Result; @@ -17,109 +19,127 @@ struct ErrorImpl { cause: Option, } -#[derive(Debug, PartialEq)] -pub(super) enum Kind { +/// Represents the kind of the error. +/// +/// This enum is non-exhaustive. +#[non_exhaustive] +pub enum Kind { + /// Error occured while parsing. Parse(Parse), + /// Error occured while executing user code. User(User), /// A message reached EOF, but is not complete. - IncompleteMessage, + IncompleteMessage(Sealed), /// A connection received a message (or bytes) when not waiting for one. #[cfg(feature = "http1")] - UnexpectedMessage, + UnexpectedMessage(Sealed), /// A pending item was dropped before ever being processed. - Canceled, + Canceled(Sealed), /// Indicates a channel (client or body sender) is closed. - ChannelClosed, + ChannelClosed(Sealed), /// An `io::Error` that occurred while trying to read or write to a network stream. #[cfg(any(feature = "http1", feature = "http2"))] - Io, + Io(Sealed), /// Error occurred while connecting. - Connect, + Connect(Sealed), /// Error creating a TcpListener. #[cfg(all( any(feature = "http1", feature = "http2"), feature = "tcp", feature = "server" ))] - Listen, + Listen(Sealed), /// Error accepting on an Incoming stream. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] - Accept, + Accept(Sealed), /// Error while reading a body from connection. #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] - Body, + Body(Sealed), /// Error while writing a body to connection. #[cfg(any(feature = "http1", feature = "http2"))] - BodyWrite, + BodyWrite(Sealed), /// The body write was aborted. - BodyWriteAborted, + BodyWriteAborted(Sealed), /// Error calling AsyncWrite::shutdown() #[cfg(feature = "http1")] - Shutdown, + Shutdown(Sealed), /// A general error from h2. #[cfg(feature = "http2")] - Http2, + Http2(Sealed), } -#[derive(Debug, PartialEq)] -pub(super) enum Parse { - Method, - Version, +/// Represents the kind of the parse error. +/// +/// This enum is non-exhaustive. +#[non_exhaustive] +pub enum Parse { + /// Invalid HTTP method parsed. + Method(Sealed), + /// Invalid HTTP version parsed. + Version(Sealed), + /// Found HTTP/2 preface. #[cfg(feature = "http1")] - VersionH2, - Uri, - Header, - TooLarge, - Status, + H2Preface(Sealed), + /// Invalid URI. + Uri(Sealed), + /// Invalid HTTP header parsed. + Header(Sealed), + /// Header section is too large. + HeaderSectionTooLarge(Sealed), + /// Invalid HTTP status-code parsed. + Status(Sealed), } -#[derive(Debug, PartialEq)] -pub(super) enum User { +/// Represents the kind of the user error. +/// +/// This enum is non-exhaustive. +#[non_exhaustive] +pub enum User { /// Error calling user's HttpBody::poll_data(). #[cfg(any(feature = "http1", feature = "http2"))] - Body, + Body(Sealed), /// Error calling user's MakeService. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] - MakeService, + MakeService(Sealed), /// Error from future of user's Service. #[cfg(any(feature = "http1", feature = "http2"))] - Service, + Service(Sealed), /// User tried to send a certain header in an unexpected context. /// /// For example, sending both `content-length` and `transfer-encoding`. #[cfg(feature = "http1")] #[cfg(feature = "server")] - UnexpectedHeader, + UnexpectedHeader(Sealed), /// User tried to create a Request with bad version. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] - UnsupportedVersion, + UnsupportedVersion(Sealed), /// User tried to create a CONNECT Request with the Client. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] - UnsupportedRequestMethod, + UnsupportedRequestMethod(Sealed), /// User tried to respond with a 1xx (not 101) response code. #[cfg(feature = "http1")] #[cfg(feature = "server")] - UnsupportedStatusCode, + UnsupportedStatusCode(Sealed), /// User tried to send a Request with Client with non-absolute URI. #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] - AbsoluteUriRequired, + AbsoluteUriRequired(Sealed), /// User tried polling for an upgrade that doesn't exist. - NoUpgrade, + NoUpgrade(Sealed), /// User polled for an upgrade, but low-level API is not using upgrades. #[cfg(feature = "http1")] - ManualUpgrade, + ManualUpgrade(Sealed), /// User aborted in an FFI callback. #[cfg(feature = "ffi")] - AbortedByCallback, + AbortedByCallback(Sealed), } // Sentinel type to indicate the error was caused by a timeout. @@ -139,27 +159,27 @@ impl Error { /// Returns true if this was about a `Request` that was canceled. pub fn is_canceled(&self) -> bool { - self.inner.kind == Kind::Canceled + matches!(self.inner.kind, Kind::Canceled(_)) } /// Returns true if a sender's channel is closed. pub fn is_closed(&self) -> bool { - self.inner.kind == Kind::ChannelClosed + matches!(self.inner.kind, Kind::ChannelClosed(_)) } /// Returns true if this was an error from `Connect`. pub fn is_connect(&self) -> bool { - self.inner.kind == Kind::Connect + matches!(self.inner.kind, Kind::Connect(_)) } /// Returns true if the connection closed before a message could complete. pub fn is_incomplete_message(&self) -> bool { - self.inner.kind == Kind::IncompleteMessage + matches!(self.inner.kind, Kind::IncompleteMessage(_)) } /// Returns true if the body write was aborted. pub fn is_body_write_aborted(&self) -> bool { - self.inner.kind == Kind::BodyWriteAborted + matches!(self.inner.kind, Kind::BodyWriteAborted(_)) } /// Returns true if the error was caused by a timeout. @@ -183,8 +203,9 @@ impl Error { self } + /// Returns the kind of the error. #[cfg(any(all(feature = "http1", feature = "server"), feature = "ffi"))] - pub(super) fn kind(&self) -> &Kind { + pub fn kind(&self) -> &Kind { &self.inner.kind } @@ -210,69 +231,73 @@ impl Error { .unwrap_or(h2::Reason::INTERNAL_ERROR) } + pub(super) fn new_parse(err: Parse) -> Error { + Error::new(Kind::Parse(err)) + } + pub(super) fn new_canceled() -> Error { - Error::new(Kind::Canceled) + Error::new(Kind::Canceled(Sealed)) } #[cfg(feature = "http1")] pub(super) fn new_incomplete() -> Error { - Error::new(Kind::IncompleteMessage) + Error::new(Kind::IncompleteMessage(Sealed)) } #[cfg(feature = "http1")] pub(super) fn new_too_large() -> Error { - Error::new(Kind::Parse(Parse::TooLarge)) + Error::new(Kind::Parse(Parse::new_header_section_too_large())) } #[cfg(feature = "http1")] - pub(super) fn new_version_h2() -> Error { - Error::new(Kind::Parse(Parse::VersionH2)) + pub(super) fn new_h2_preface() -> Error { + Error::new(Kind::Parse(Parse::new_h2_preface())) } #[cfg(feature = "http1")] pub(super) fn new_unexpected_message() -> Error { - Error::new(Kind::UnexpectedMessage) + Error::new(Kind::UnexpectedMessage(Sealed)) } #[cfg(any(feature = "http1", feature = "http2"))] pub(super) fn new_io(cause: std::io::Error) -> Error { - Error::new(Kind::Io).with(cause) + Error::new(Kind::Io(Sealed)).with(cause) } #[cfg(all(any(feature = "http1", feature = "http2"), feature = "tcp"))] #[cfg(feature = "server")] pub(super) fn new_listen>(cause: E) -> Error { - Error::new(Kind::Listen).with(cause) + Error::new(Kind::Listen(Sealed)).with(cause) } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] pub(super) fn new_accept>(cause: E) -> Error { - Error::new(Kind::Accept).with(cause) + Error::new(Kind::Accept(Sealed)).with(cause) } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] pub(super) fn new_connect>(cause: E) -> Error { - Error::new(Kind::Connect).with(cause) + Error::new(Kind::Connect(Sealed)).with(cause) } pub(super) fn new_closed() -> Error { - Error::new(Kind::ChannelClosed) + Error::new(Kind::ChannelClosed(Sealed)) } #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] pub(super) fn new_body>(cause: E) -> Error { - Error::new(Kind::Body).with(cause) + Error::new(Kind::Body(Sealed)).with(cause) } #[cfg(any(feature = "http1", feature = "http2"))] pub(super) fn new_body_write>(cause: E) -> Error { - Error::new(Kind::BodyWrite).with(cause) + Error::new(Kind::BodyWrite(Sealed)).with(cause) } pub(super) fn new_body_write_aborted() -> Error { - Error::new(Kind::BodyWriteAborted) + Error::new(Kind::BodyWriteAborted(Sealed)) } fn new_user(user: User) -> Error { @@ -282,66 +307,66 @@ impl Error { #[cfg(feature = "http1")] #[cfg(feature = "server")] pub(super) fn new_user_header() -> Error { - Error::new_user(User::UnexpectedHeader) + Error::new_user(User::UnexpectedHeader(Sealed)) } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] pub(super) fn new_user_unsupported_version() -> Error { - Error::new_user(User::UnsupportedVersion) + Error::new_user(User::UnsupportedVersion(Sealed)) } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] pub(super) fn new_user_unsupported_request_method() -> Error { - Error::new_user(User::UnsupportedRequestMethod) + Error::new_user(User::UnsupportedRequestMethod(Sealed)) } #[cfg(feature = "http1")] #[cfg(feature = "server")] pub(super) fn new_user_unsupported_status_code() -> Error { - Error::new_user(User::UnsupportedStatusCode) + Error::new_user(User::UnsupportedStatusCode(Sealed)) } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] pub(super) fn new_user_absolute_uri_required() -> Error { - Error::new_user(User::AbsoluteUriRequired) + Error::new_user(User::AbsoluteUriRequired(Sealed)) } pub(super) fn new_user_no_upgrade() -> Error { - Error::new_user(User::NoUpgrade) + Error::new_user(User::NoUpgrade(Sealed)) } #[cfg(feature = "http1")] pub(super) fn new_user_manual_upgrade() -> Error { - Error::new_user(User::ManualUpgrade) + Error::new_user(User::ManualUpgrade(Sealed)) } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] pub(super) fn new_user_make_service>(cause: E) -> Error { - Error::new_user(User::MakeService).with(cause) + Error::new_user(User::MakeService(Sealed)).with(cause) } #[cfg(any(feature = "http1", feature = "http2"))] pub(super) fn new_user_service>(cause: E) -> Error { - Error::new_user(User::Service).with(cause) + Error::new_user(User::Service(Sealed)).with(cause) } #[cfg(any(feature = "http1", feature = "http2"))] pub(super) fn new_user_body>(cause: E) -> Error { - Error::new_user(User::Body).with(cause) + Error::new_user(User::Body(Sealed)).with(cause) } #[cfg(feature = "http1")] pub(super) fn new_shutdown(cause: std::io::Error) -> Error { - Error::new(Kind::Shutdown).with(cause) + Error::new(Kind::Shutdown(Sealed)).with(cause) } #[cfg(feature = "ffi")] pub(super) fn new_user_aborted_by_callback() -> Error { - Error::new_user(User::AbortedByCallback) + Error::new_user(User::AbortedByCallback(Sealed)) } #[cfg(feature = "http2")] @@ -349,73 +374,80 @@ impl Error { if cause.is_io() { Error::new_io(cause.into_io().expect("h2::Error::is_io")) } else { - Error::new(Kind::Http2).with(cause) + Error::new_fake_h2(cause) } } + #[cfg(feature = "http2")] + pub(super) fn new_fake_h2>(cause: E) -> Error { + Error::new(Kind::Http2(Sealed)).with(cause) + } + fn description(&self) -> &str { match self.inner.kind { - Kind::Parse(Parse::Method) => "invalid HTTP method parsed", - Kind::Parse(Parse::Version) => "invalid HTTP version parsed", + Kind::Parse(Parse::Method(_)) => "invalid HTTP method parsed", + Kind::Parse(Parse::Version(_)) => "invalid HTTP version parsed", #[cfg(feature = "http1")] - Kind::Parse(Parse::VersionH2) => "invalid HTTP version parsed (found HTTP2 preface)", - Kind::Parse(Parse::Uri) => "invalid URI", - Kind::Parse(Parse::Header) => "invalid HTTP header parsed", - Kind::Parse(Parse::TooLarge) => "message head is too large", - Kind::Parse(Parse::Status) => "invalid HTTP status-code parsed", - Kind::IncompleteMessage => "connection closed before message completed", + Kind::Parse(Parse::H2Preface(_)) => "invalid HTTP version parsed (found HTTP2 preface)", + Kind::Parse(Parse::Uri(_)) => "invalid URI", + Kind::Parse(Parse::Header(_)) => "invalid HTTP header parsed", + Kind::Parse(Parse::HeaderSectionTooLarge(_)) => "header section is too large", + Kind::Parse(Parse::Status(_)) => "invalid HTTP status-code parsed", + Kind::IncompleteMessage(_) => "connection closed before message completed", #[cfg(feature = "http1")] - Kind::UnexpectedMessage => "received unexpected message from connection", - Kind::ChannelClosed => "channel closed", - Kind::Connect => "error trying to connect", - Kind::Canceled => "operation was canceled", + Kind::UnexpectedMessage(_) => "received unexpected message from connection", + Kind::ChannelClosed(_) => "channel closed", + Kind::Connect(_) => "error trying to connect", + Kind::Canceled(_) => "operation was canceled", #[cfg(all(any(feature = "http1", feature = "http2"), feature = "tcp"))] #[cfg(feature = "server")] - Kind::Listen => "error creating server listener", + Kind::Listen(_) => "error creating server listener", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] - Kind::Accept => "error accepting connection", + Kind::Accept(_) => "error accepting connection", #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] - Kind::Body => "error reading a body from connection", + Kind::Body(_) => "error reading a body from connection", #[cfg(any(feature = "http1", feature = "http2"))] - Kind::BodyWrite => "error writing a body to connection", - Kind::BodyWriteAborted => "body write aborted", + Kind::BodyWrite(_) => "error writing a body to connection", + Kind::BodyWriteAborted(_) => "body write aborted", #[cfg(feature = "http1")] - Kind::Shutdown => "error shutting down connection", + Kind::Shutdown(_) => "error shutting down connection", #[cfg(feature = "http2")] - Kind::Http2 => "http2 error", + Kind::Http2(_) => "http2 error", #[cfg(any(feature = "http1", feature = "http2"))] - Kind::Io => "connection error", + Kind::Io(_) => "connection error", #[cfg(any(feature = "http1", feature = "http2"))] - Kind::User(User::Body) => "error from user's HttpBody stream", + Kind::User(User::Body(_)) => "error from user's HttpBody stream", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "server")] - Kind::User(User::MakeService) => "error from user's MakeService", + Kind::User(User::MakeService(_)) => "error from user's MakeService", #[cfg(any(feature = "http1", feature = "http2"))] - Kind::User(User::Service) => "error from user's Service", + Kind::User(User::Service(_)) => "error from user's Service", #[cfg(feature = "http1")] #[cfg(feature = "server")] - Kind::User(User::UnexpectedHeader) => "user sent unexpected header", + Kind::User(User::UnexpectedHeader(_)) => "user sent unexpected header", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] - Kind::User(User::UnsupportedVersion) => "request has unsupported HTTP version", + Kind::User(User::UnsupportedVersion(_)) => "request has unsupported HTTP version", #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] - Kind::User(User::UnsupportedRequestMethod) => "request has unsupported HTTP method", + Kind::User(User::UnsupportedRequestMethod(_)) => "request has unsupported HTTP method", #[cfg(feature = "http1")] #[cfg(feature = "server")] - Kind::User(User::UnsupportedStatusCode) => { + Kind::User(User::UnsupportedStatusCode(_)) => { "response has 1xx status code, not supported by server" } #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] - Kind::User(User::AbsoluteUriRequired) => "client requires absolute-form URIs", - Kind::User(User::NoUpgrade) => "no upgrade available", + Kind::User(User::AbsoluteUriRequired(_)) => "client requires absolute-form URIs", + Kind::User(User::NoUpgrade(_)) => "no upgrade available", #[cfg(feature = "http1")] - Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use", + Kind::User(User::ManualUpgrade(_)) => "upgrade expected but low level API in use", #[cfg(feature = "ffi")] - Kind::User(User::AbortedByCallback) => "operation aborted by an application callback", + Kind::User(User::AbortedByCallback(_)) => { + "operation aborted by an application callback" + } } } } @@ -431,6 +463,91 @@ impl fmt::Debug for Error { } } +impl fmt::Debug for Kind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + Self::Parse(ref e) => return f.debug_tuple("Parse").field(e).finish(), + Self::User(ref e) => return f.debug_tuple("User").field(e).finish(), + Self::IncompleteMessage(_) => "IncompleteMessage", + #[cfg(feature = "http1")] + Self::UnexpectedMessage(_) => "UnexpectedMessage", + Self::Canceled(_) => "Canceled", + Self::ChannelClosed(_) => "ChannelClosed", + #[cfg(any(feature = "http1", feature = "http2"))] + Self::Io(_) => "Io", + Self::Connect(_) => "Connect", + #[cfg(all( + any(feature = "http1", feature = "http2"), + feature = "tcp", + feature = "server" + ))] + Self::Listen(_) => "Listen", + #[cfg(any(feature = "http1", feature = "http2"))] + #[cfg(feature = "server")] + Self::Accept(_) => "Accept", + #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] + Self::Body(_) => "Body", + #[cfg(any(feature = "http1", feature = "http2"))] + Self::BodyWrite(_) => "BodyWrite", + Self::BodyWriteAborted(_) => "BodyWriteAborted", + #[cfg(feature = "http1")] + Self::Shutdown(_) => "Shutdown", + + #[cfg(feature = "http2")] + Self::Http2(_) => "Http2", + }) + } +} + +impl fmt::Debug for Parse { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + Self::Method(_) => "Method", + Self::Version(_) => "Version", + #[cfg(feature = "http1")] + Self::H2Preface(_) => "H2Preface", + Self::Uri(_) => "Uri", + Self::Header(_) => "Header", + Self::HeaderSectionTooLarge(_) => "TooLarge", + Self::Status(_) => "Status", + }) + } +} + +impl fmt::Debug for User { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(match *self { + #[cfg(any(feature = "http1", feature = "http2"))] + Self::Body(_) => "Body", + #[cfg(any(feature = "http1", feature = "http2"))] + #[cfg(feature = "server")] + Self::MakeService(_) => "MakeService", + #[cfg(any(feature = "http1", feature = "http2"))] + Self::Service(_) => "Service", + #[cfg(feature = "http1")] + #[cfg(feature = "server")] + Self::UnexpectedHeader(_) => "UnexpectedHeader", + #[cfg(any(feature = "http1", feature = "http2"))] + #[cfg(feature = "client")] + Self::UnsupportedVersion(_) => "UnsupportedVersion", + #[cfg(any(feature = "http1", feature = "http2"))] + #[cfg(feature = "client")] + Self::UnsupportedRequestMethod(_) => "UnsupportedRequestMethod", + #[cfg(feature = "http1")] + #[cfg(feature = "server")] + Self::UnsupportedStatusCode(_) => "UnsupportedStatusCode", + #[cfg(any(feature = "http1", feature = "http2"))] + #[cfg(feature = "client")] + Self::AbsoluteUriRequired(_) => "AbsoluteUriRequired", + Self::NoUpgrade(_) => "NoUpgrade", + #[cfg(feature = "http1")] + Self::ManualUpgrade(_) => "ManualUpgrade", + #[cfg(feature = "ffi")] + Self::AbortedByCallback(_) => "AbortedByCallback", + }) + } +} + impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ref cause) = self.inner.cause { @@ -450,48 +567,58 @@ impl StdError for Error { } } -#[doc(hidden)] -impl From for Error { - fn from(err: Parse) -> Error { - Error::new(Kind::Parse(err)) +impl Parse { + pub(super) fn new_method() -> Self { + Parse::Method(Sealed) } -} -impl From for Parse { - fn from(err: httparse::Error) -> Parse { + pub(super) fn new_version() -> Self { + Parse::Version(Sealed) + } + + #[cfg(feature = "http1")] + pub(super) fn new_h2_preface() -> Self { + Parse::H2Preface(Sealed) + } + + pub(super) fn new_uri() -> Self { + Parse::Uri(Sealed) + } + + pub(super) fn new_header() -> Self { + Parse::Header(Sealed) + } + + pub(super) fn new_header_section_too_large() -> Self { + Parse::HeaderSectionTooLarge(Sealed) + } + + pub(super) fn new_status() -> Self { + Parse::Status(Sealed) + } + + pub(super) fn from_httparse(err: httparse::Error) -> Parse { match err { httparse::Error::HeaderName | httparse::Error::HeaderValue | httparse::Error::NewLine - | httparse::Error::Token => Parse::Header, - httparse::Error::Status => Parse::Status, - httparse::Error::TooManyHeaders => Parse::TooLarge, - httparse::Error::Version => Parse::Version, + | httparse::Error::Token => Parse::new_header(), + httparse::Error::Status => Parse::new_status(), + httparse::Error::TooManyHeaders => Parse::new_header_section_too_large(), + httparse::Error::Version => Parse::new_version(), } } -} - -impl From for Parse { - fn from(_: http::method::InvalidMethod) -> Parse { - Parse::Method - } -} -impl From for Parse { - fn from(_: http::status::InvalidStatusCode) -> Parse { - Parse::Status + pub(super) fn from_invalid_method(_: http::method::InvalidMethod) -> Parse { + Parse::new_method() } -} -impl From for Parse { - fn from(_: http::uri::InvalidUri) -> Parse { - Parse::Uri + pub(super) fn from_invalid_status_code(_: http::status::InvalidStatusCode) -> Parse { + Parse::new_status() } -} -impl From for Parse { - fn from(_: http::uri::InvalidUriParts) -> Parse { - Parse::Uri + pub(super) fn from_invalid_uri(_: http::uri::InvalidUri) -> Parse { + Parse::new_uri() } } @@ -510,6 +637,13 @@ impl fmt::Display for TimedOut { impl StdError for TimedOut {} +mod sealed { + /// Exists solely to be able to extend error types later. + #[allow(unreachable_pub)] + #[derive(Debug, PartialEq)] + pub struct Sealed; +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/ffi/error.rs b/src/ffi/error.rs index 7b85407099..816ef7be3b 100644 --- a/src/ffi/error.rs +++ b/src/ffi/error.rs @@ -35,8 +35,8 @@ impl hyper_error { match self.0.kind() { ErrorKind::Parse(_) => hyper_code::HYPERE_INVALID_PEER_MESSAGE, - ErrorKind::IncompleteMessage => hyper_code::HYPERE_UNEXPECTED_EOF, - ErrorKind::User(User::AbortedByCallback) => hyper_code::HYPERE_ABORTED_BY_CALLBACK, + ErrorKind::IncompleteMessage(_) => hyper_code::HYPERE_UNEXPECTED_EOF, + ErrorKind::User(User::AbortedByCallback(_)) => hyper_code::HYPERE_ABORTED_BY_CALLBACK, // TODO: add more variants _ => hyper_code::HYPERE_ERROR, } diff --git a/src/lib.rs b/src/lib.rs index 059f8821c6..898f7ce192 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -79,7 +79,7 @@ mod cfg; #[macro_use] mod common; pub mod body; -mod error; +pub mod error; #[cfg(test)] mod mock; #[cfg(any(feature = "http1", feature = "http2",))] diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index ce0848ddea..836f2bb6f8 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -651,7 +651,7 @@ where fn on_parse_error(&mut self, err: crate::Error) -> crate::Result<()> { if let Writing::Init = self.state.writing { if self.has_h2_prefix() { - return Err(crate::Error::new_version_h2()); + return Err(crate::Error::new_h2_preface()); } if let Some(msg) = T::on_error(&err) { // Drop the cached headers so as to not trigger a debug diff --git a/src/proto/h1/dispatch.rs b/src/proto/h1/dispatch.rs index 88e641e9a4..c7127b9073 100644 --- a/src/proto/h1/dispatch.rs +++ b/src/proto/h1/dispatch.rs @@ -686,7 +686,7 @@ mod tests { .expect_err("callback should send error"); match (err.0.kind(), err.1) { - (&crate::error::Kind::Canceled, Some(_)) => (), + (&crate::error::Kind::Canceled(_), Some(_)) => (), other => panic!("expected Canceled, got {:?}", other), } }); diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index c7ce48664b..855d428dc6 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -163,7 +163,9 @@ where preserve_header_case: parse_ctx.preserve_header_case, h09_responses: parse_ctx.h09_responses, }, - )? { + ) + .map_err(crate::Error::new_parse)? + { Some(msg) => { debug!("parsed {} headers", msg.head.headers.len()); return Poll::Ready(Ok(msg)); @@ -183,7 +185,10 @@ where } } - pub(crate) fn poll_read_from_io(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub(crate) fn poll_read_from_io( + &mut self, + cx: &mut task::Context<'_>, + ) -> Poll> { self.read_blocked = false; let next = self.read_buf_strategy.next(); if self.read_buf_remaining_mut() < next { @@ -378,7 +383,7 @@ impl ReadStrategy { *decrease_now = false; } } - }, + } #[cfg(feature = "client")] ReadStrategy::Exact(_) => (), } diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index ea9dc96be1..c313ca2399 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -138,8 +138,9 @@ impl Http1Transaction for Server { trace!("Request.parse Complete({})", parsed_len); len = parsed_len; subject = RequestLine( - Method::from_bytes(req.method.unwrap().as_bytes())?, - req.path.unwrap().parse()?, + Method::from_bytes(req.method.unwrap().as_bytes()) + .map_err(Parse::from_invalid_method)?, + req.path.unwrap().parse().map_err(Parse::from_invalid_uri)?, ); version = if req.version.unwrap() == 1 { keep_alive = true; @@ -161,13 +162,13 @@ impl Http1Transaction for Server { // if invalid Token, try to determine if for method or path httparse::Error::Token => { if req.method.is_none() { - Parse::Method + Parse::new_method() } else { debug_assert!(req.path.is_none()); - Parse::Uri + Parse::new_uri() } } - other => other.into(), + other => Parse::from_httparse(other), }); } } @@ -207,7 +208,7 @@ impl Http1Transaction for Server { // malformed. A server should respond with 400 Bad Request. if !is_http_11 { debug!("HTTP/1.0 cannot have Transfer-Encoding header"); - return Err(Parse::Header); + return Err(Parse::new_header()); } is_te = true; if headers::is_chunked_(&value) { @@ -223,15 +224,15 @@ impl Http1Transaction for Server { } let len = value .to_str() - .map_err(|_| Parse::Header) - .and_then(|s| s.parse().map_err(|_| Parse::Header))?; + .map_err(|_| Parse::new_header()) + .and_then(|s| s.parse().map_err(|_| Parse::new_header()))?; if let Some(prev) = con_len { if prev != len { debug!( "multiple Content-Length headers with different values: [{}, {}]", prev, len, ); - return Err(Parse::Header); + return Err(Parse::new_header()); } // we don't need to append this secondary length continue; @@ -265,7 +266,7 @@ impl Http1Transaction for Server { if is_te && !is_te_chunked { debug!("request with transfer-encoding header, but not chunked, bad request"); - return Err(Parse::Header); + return Err(Parse::new_header()); } *ctx.req_method = Some(subject.0.clone()); @@ -605,11 +606,11 @@ impl Http1Transaction for Server { fn on_error(err: &crate::Error) -> Option> { use crate::error::Kind; let status = match *err.kind() { - Kind::Parse(Parse::Method) - | Kind::Parse(Parse::Header) - | Kind::Parse(Parse::Uri) - | Kind::Parse(Parse::Version) => StatusCode::BAD_REQUEST, - Kind::Parse(Parse::TooLarge) => StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, + Kind::Parse(Parse::Method(_)) + | Kind::Parse(Parse::Header(_)) + | Kind::Parse(Parse::Uri(_)) + | Kind::Parse(Parse::Version(_)) => StatusCode::BAD_REQUEST, + Kind::Parse(Parse::TooLarge(_)) => StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, _ => return None, }; @@ -686,7 +687,8 @@ impl Http1Transaction for Client { match res.parse(bytes) { Ok(httparse::Status::Complete(len)) => { trace!("Response.parse Complete({})", len); - let status = StatusCode::from_u16(res.code.unwrap())?; + let status = StatusCode::from_u16(res.code.unwrap()) + .map_err(Parse::from_invalid_status_code)?; #[cfg(not(feature = "ffi"))] let reason = (); @@ -721,7 +723,7 @@ impl Http1Transaction for Client { (0, StatusCode::OK, reason, Version::HTTP_09, 0) } - Err(e) => return Err(e.into()), + Err(e) => return Err(Parse::from_httparse(e)), } }; @@ -918,7 +920,7 @@ impl Client { // malformed. A server should respond with 400 Bad Request. if inc.version == Version::HTTP_10 { debug!("HTTP/1.0 cannot have Transfer-Encoding header"); - Err(Parse::Header) + Err(Parse::new_header()) } else if headers::transfer_encoding_is_chunked(&inc.headers) { Ok(Some((DecodedLength::CHUNKED, false))) } else { @@ -929,7 +931,7 @@ impl Client { Ok(Some((DecodedLength::checked_new(len)?, false))) } else if inc.headers.contains_key(header::CONTENT_LENGTH) { debug!("illegal Content-Length header"); - Err(Parse::Header) + Err(Parse::new_header()) } else { trace!("neither Transfer-Encoding nor Content-Length"); Ok(Some((DecodedLength::CLOSE_DELIMITED, false))) @@ -1097,7 +1099,7 @@ fn record_header_indices( for (header, indices) in headers.iter().zip(indices.iter_mut()) { if header.name.len() >= (1 << 16) { debug!("header name larger than 64kb: {:?}", header.name); - return Err(crate::error::Parse::TooLarge); + return Err(crate::error::Parse::new_too_large()); } let name_start = header.name.as_ptr() as usize - bytes_ptr; let name_end = name_start + header.name.len(); diff --git a/src/proto/h2/ping.rs b/src/proto/h2/ping.rs index 105fc69a39..d5ac3dda8b 100644 --- a/src/proto/h2/ping.rs +++ b/src/proto/h2/ping.rs @@ -497,7 +497,7 @@ impl KeepAlive { #[cfg(feature = "runtime")] impl KeepAliveTimedOut { pub(super) fn crate_error(self) -> crate::Error { - crate::Error::new(crate::error::Kind::Http2).with(self) + crate::Error::new_fake_h2(self) } } diff --git a/src/server/conn.rs b/src/server/conn.rs index 5137708fcb..67645fae08 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -663,7 +663,7 @@ where Err(e) => { #[cfg(feature = "http2")] match *e.kind() { - Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => { + Kind::Parse(Parse::H2Preface(_)) if self.fallback.to_h2() => { self.upgrade_h2(); continue; } @@ -760,7 +760,7 @@ where #[cfg(feature = "http1")] #[cfg(feature = "http2")] match *e.kind() { - Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => { + Kind::Parse(Parse::H2Preface(_)) if self.fallback.to_h2() => { self.upgrade_h2(); continue; } @@ -1148,7 +1148,7 @@ mod upgrades { #[cfg(feature = "http1")] #[cfg(feature = "http2")] match *e.kind() { - Kind::Parse(Parse::VersionH2) if self.inner.fallback.to_h2() => { + Kind::Parse(Parse::H2Preface(_)) if self.inner.fallback.to_h2() => { self.inner.upgrade_h2(); continue; }