#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
use crate::client::connect::Connected;
use std::error::Error as StdError;
use std::fmt;
pub type Result<T> = std::result::Result<T, Error>;
type Cause = Box<dyn StdError + Send + Sync>;
pub struct Error {
inner: Box<ErrorImpl>,
}
struct ErrorImpl {
kind: Kind,
cause: Option<Cause>,
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
connect_info: Option<Connected>,
}
#[derive(Debug)]
pub(super) enum Kind {
Parse(Parse),
User(User),
#[allow(unused)]
IncompleteMessage,
#[cfg(feature = "http1")]
UnexpectedMessage,
Canceled,
ChannelClosed,
#[cfg(any(feature = "http1", feature = "http2"))]
Io,
#[allow(unused)]
Connect,
#[cfg(all(feature = "tcp", feature = "server"))]
Listen,
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
Accept,
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
HeaderTimeout,
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
Body,
#[cfg(any(feature = "http1", feature = "http2"))]
BodyWrite,
#[cfg(feature = "http1")]
Shutdown,
#[cfg(feature = "http2")]
Http2,
}
#[derive(Debug)]
pub(super) enum Parse {
Method,
Version,
#[cfg(feature = "http1")]
VersionH2,
Uri,
#[cfg_attr(not(all(feature = "http1", feature = "server")), allow(unused))]
UriTooLong,
Header(Header),
TooLarge,
Status,
#[cfg_attr(debug_assertions, allow(unused))]
Internal,
}
#[derive(Debug)]
pub(super) enum Header {
Token,
#[cfg(feature = "http1")]
ContentLengthInvalid,
#[cfg(all(feature = "http1", feature = "server"))]
TransferEncodingInvalid,
#[cfg(feature = "http1")]
TransferEncodingUnexpected,
}
#[derive(Debug)]
pub(super) enum User {
#[cfg(any(feature = "http1", feature = "http2"))]
Body,
BodyWriteAborted,
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
MakeService,
#[cfg(any(feature = "http1", feature = "http2"))]
Service,
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
UnexpectedHeader,
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
UnsupportedVersion,
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
UnsupportedRequestMethod,
#[cfg(feature = "http1")]
#[cfg(feature = "server")]
UnsupportedStatusCode,
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
AbsoluteUriRequired,
NoUpgrade,
#[cfg(feature = "http1")]
ManualUpgrade,
#[cfg(feature = "server")]
WithoutShutdownNonHttp1,
#[cfg(feature = "client")]
DispatchGone,
#[cfg(feature = "ffi")]
AbortedByCallback,
}
#[derive(Debug)]
pub(super) struct TimedOut;
impl Error {
pub fn is_parse(&self) -> bool {
matches!(self.inner.kind, Kind::Parse(_))
}
pub fn is_parse_too_large(&self) -> bool {
matches!(
self.inner.kind,
Kind::Parse(Parse::TooLarge) | Kind::Parse(Parse::UriTooLong)
)
}
pub fn is_parse_status(&self) -> bool {
matches!(self.inner.kind, Kind::Parse(Parse::Status))
}
pub fn is_user(&self) -> bool {
matches!(self.inner.kind, Kind::User(_))
}
pub fn is_canceled(&self) -> bool {
matches!(self.inner.kind, Kind::Canceled)
}
pub fn is_closed(&self) -> bool {
matches!(self.inner.kind, Kind::ChannelClosed)
}
pub fn is_connect(&self) -> bool {
matches!(self.inner.kind, Kind::Connect)
}
pub fn is_incomplete_message(&self) -> bool {
matches!(self.inner.kind, Kind::IncompleteMessage)
}
pub fn is_body_write_aborted(&self) -> bool {
matches!(self.inner.kind, Kind::User(User::BodyWriteAborted))
}
pub fn is_timeout(&self) -> bool {
self.find_source::<TimedOut>().is_some()
}
pub fn into_cause(self) -> Option<Box<dyn StdError + Send + Sync>> {
self.inner.cause
}
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
pub fn client_connect_info(&self) -> Option<&Connected> {
self.inner.connect_info.as_ref()
}
pub(super) fn new(kind: Kind) -> Error {
Error {
inner: Box::new(ErrorImpl {
kind,
cause: None,
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
connect_info: None,
}),
}
}
pub(super) fn with<C: Into<Cause>>(mut self, cause: C) -> Error {
self.inner.cause = Some(cause.into());
self
}
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
pub(super) fn with_client_connect_info(mut self, connect_info: Connected) -> Error {
self.inner.connect_info = Some(connect_info);
self
}
#[cfg(any(all(feature = "http1", feature = "server"), feature = "ffi"))]
pub(super) fn kind(&self) -> &Kind {
&self.inner.kind
}
pub(crate) fn find_source<E: StdError + 'static>(&self) -> Option<&E> {
let mut cause = self.source();
while let Some(err) = cause {
if let Some(ref typed) = err.downcast_ref() {
return Some(typed);
}
cause = err.source();
}
None
}
#[cfg(feature = "http2")]
pub(super) fn h2_reason(&self) -> h2::Reason {
self.find_source::<h2::Error>()
.and_then(|h2_err| h2_err.reason())
.unwrap_or(h2::Reason::INTERNAL_ERROR)
}
pub(super) fn new_canceled() -> Error {
Error::new(Kind::Canceled)
}
#[cfg(feature = "http1")]
pub(super) fn new_incomplete() -> Error {
Error::new(Kind::IncompleteMessage)
}
#[cfg(feature = "http1")]
pub(super) fn new_too_large() -> Error {
Error::new(Kind::Parse(Parse::TooLarge))
}
#[cfg(feature = "http1")]
pub(super) fn new_version_h2() -> Error {
Error::new(Kind::Parse(Parse::VersionH2))
}
#[cfg(feature = "http1")]
pub(super) fn new_unexpected_message() -> Error {
Error::new(Kind::UnexpectedMessage)
}
#[cfg(any(feature = "http1", feature = "http2"))]
pub(super) fn new_io(cause: std::io::Error) -> Error {
Error::new(Kind::Io).with(cause)
}
#[cfg(all(feature = "server", feature = "tcp"))]
pub(super) fn new_listen<E: Into<Cause>>(cause: E) -> Error {
Error::new(Kind::Listen).with(cause)
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
pub(super) fn new_accept<E: Into<Cause>>(cause: E) -> Error {
Error::new(Kind::Accept).with(cause)
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
pub(super) fn new_connect<E: Into<Cause>>(cause: E) -> Error {
Error::new(Kind::Connect).with(cause)
}
pub(super) fn new_closed() -> Error {
Error::new(Kind::ChannelClosed)
}
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error {
Error::new(Kind::Body).with(cause)
}
#[cfg(any(feature = "http1", feature = "http2"))]
pub(super) fn new_body_write<E: Into<Cause>>(cause: E) -> Error {
Error::new(Kind::BodyWrite).with(cause)
}
pub(super) fn new_body_write_aborted() -> Error {
Error::new(Kind::User(User::BodyWriteAborted))
}
fn new_user(user: User) -> Error {
Error::new(Kind::User(user))
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
pub(super) fn new_user_header() -> Error {
Error::new_user(User::UnexpectedHeader)
}
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
pub(super) fn new_header_timeout() -> Error {
Error::new(Kind::HeaderTimeout)
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
pub(super) fn new_user_unsupported_version() -> Error {
Error::new_user(User::UnsupportedVersion)
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
pub(super) fn new_user_unsupported_request_method() -> Error {
Error::new_user(User::UnsupportedRequestMethod)
}
#[cfg(feature = "http1")]
#[cfg(feature = "server")]
pub(super) fn new_user_unsupported_status_code() -> Error {
Error::new_user(User::UnsupportedStatusCode)
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
pub(super) fn new_user_absolute_uri_required() -> Error {
Error::new_user(User::AbsoluteUriRequired)
}
pub(super) fn new_user_no_upgrade() -> Error {
Error::new_user(User::NoUpgrade)
}
#[cfg(feature = "http1")]
pub(super) fn new_user_manual_upgrade() -> Error {
Error::new_user(User::ManualUpgrade)
}
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
pub(super) fn new_user_make_service<E: Into<Cause>>(cause: E) -> Error {
Error::new_user(User::MakeService).with(cause)
}
#[cfg(any(feature = "http1", feature = "http2"))]
pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error {
Error::new_user(User::Service).with(cause)
}
#[cfg(any(feature = "http1", feature = "http2"))]
pub(super) fn new_user_body<E: Into<Cause>>(cause: E) -> Error {
Error::new_user(User::Body).with(cause)
}
#[cfg(feature = "server")]
pub(super) fn new_without_shutdown_not_h1() -> Error {
Error::new(Kind::User(User::WithoutShutdownNonHttp1))
}
#[cfg(feature = "http1")]
pub(super) fn new_shutdown(cause: std::io::Error) -> Error {
Error::new(Kind::Shutdown).with(cause)
}
#[cfg(feature = "ffi")]
pub(super) fn new_user_aborted_by_callback() -> Error {
Error::new_user(User::AbortedByCallback)
}
#[cfg(feature = "client")]
pub(super) fn new_user_dispatch_gone() -> Error {
Error::new(Kind::User(User::DispatchGone))
}
#[cfg(feature = "http2")]
pub(super) fn new_h2(cause: ::h2::Error) -> Error {
if cause.is_io() {
Error::new_io(cause.into_io().expect("h2::Error::is_io"))
} else {
Error::new(Kind::Http2).with(cause)
}
}
pub fn message(&self) -> impl fmt::Display + '_ {
self.description()
}
fn description(&self) -> &str {
match self.inner.kind {
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::UriTooLong) => "URI too long",
Kind::Parse(Parse::Header(Header::Token)) => "invalid HTTP header parsed",
#[cfg(feature = "http1")]
Kind::Parse(Parse::Header(Header::ContentLengthInvalid)) => {
"invalid content-length parsed"
}
#[cfg(all(feature = "http1", feature = "server"))]
Kind::Parse(Parse::Header(Header::TransferEncodingInvalid)) => {
"invalid transfer-encoding parsed"
}
#[cfg(feature = "http1")]
Kind::Parse(Parse::Header(Header::TransferEncodingUnexpected)) => {
"unexpected transfer-encoding parsed"
}
Kind::Parse(Parse::TooLarge) => "message head is too large",
Kind::Parse(Parse::Status) => "invalid HTTP status-code parsed",
Kind::Parse(Parse::Internal) => {
"internal error inside Hyper and/or its dependencies, please report"
}
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",
#[cfg(all(feature = "server", feature = "tcp"))]
Kind::Listen => "error creating server listener",
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
Kind::Accept => "error accepting connection",
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
Kind::HeaderTimeout => "read header from client timeout",
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
Kind::Body => "error reading a body from connection",
#[cfg(any(feature = "http1", feature = "http2"))]
Kind::BodyWrite => "error writing a body to connection",
#[cfg(feature = "http1")]
Kind::Shutdown => "error shutting down connection",
#[cfg(feature = "http2")]
Kind::Http2 => "http2 error",
#[cfg(any(feature = "http1", feature = "http2"))]
Kind::Io => "connection error",
#[cfg(any(feature = "http1", feature = "http2"))]
Kind::User(User::Body) => "error from user's HttpBody stream",
Kind::User(User::BodyWriteAborted) => "user body write aborted",
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
Kind::User(User::MakeService) => "error from user's MakeService",
#[cfg(any(feature = "http1", feature = "http2"))]
Kind::User(User::Service) => "error from user's Service",
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "server")]
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",
#[cfg(any(feature = "http1", feature = "http2"))]
#[cfg(feature = "client")]
Kind::User(User::UnsupportedRequestMethod) => "request has unsupported HTTP method",
#[cfg(feature = "http1")]
#[cfg(feature = "server")]
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",
#[cfg(feature = "http1")]
Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use",
#[cfg(feature = "server")]
Kind::User(User::WithoutShutdownNonHttp1) => {
"without_shutdown() called on a non-HTTP/1 connection"
}
#[cfg(feature = "client")]
Kind::User(User::DispatchGone) => "dispatch task is gone",
#[cfg(feature = "ffi")]
Kind::User(User::AbortedByCallback) => "operation aborted by an application callback",
}
}
}
impl fmt::Debug for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut f = f.debug_tuple("hyper::Error");
f.field(&self.inner.kind);
if let Some(ref cause) = self.inner.cause {
f.field(cause);
}
f.finish()
}
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if let Some(ref cause) = self.inner.cause {
write!(f, "{}: {}", self.description(), cause)
} else {
f.write_str(self.description())
}
}
}
impl StdError for Error {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
self.inner
.cause
.as_ref()
.map(|cause| &**cause as &(dyn StdError + 'static))
}
}
#[doc(hidden)]
impl From<Parse> for Error {
fn from(err: Parse) -> Error {
Error::new(Kind::Parse(err))
}
}
#[cfg(feature = "http1")]
impl Parse {
pub(crate) fn content_length_invalid() -> Self {
Parse::Header(Header::ContentLengthInvalid)
}
#[cfg(all(feature = "http1", feature = "server"))]
pub(crate) fn transfer_encoding_invalid() -> Self {
Parse::Header(Header::TransferEncodingInvalid)
}
pub(crate) fn transfer_encoding_unexpected() -> Self {
Parse::Header(Header::TransferEncodingUnexpected)
}
}
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 => Parse::Header(Header::Token),
httparse::Error::Status => Parse::Status,
httparse::Error::TooManyHeaders => Parse::TooLarge,
httparse::Error::Version => Parse::Version,
}
}
}
impl From<http::method::InvalidMethod> for Parse {
fn from(_: http::method::InvalidMethod) -> Parse {
Parse::Method
}
}
impl From<http::status::InvalidStatusCode> for Parse {
fn from(_: http::status::InvalidStatusCode) -> Parse {
Parse::Status
}
}
impl From<http::uri::InvalidUri> for Parse {
fn from(_: http::uri::InvalidUri) -> Parse {
Parse::Uri
}
}
impl From<http::uri::InvalidUriParts> for Parse {
fn from(_: http::uri::InvalidUriParts) -> Parse {
Parse::Uri
}
}
#[doc(hidden)]
trait AssertSendSync: Send + Sync + 'static {}
#[doc(hidden)]
impl AssertSendSync for Error {}
impl fmt::Display for TimedOut {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("operation timed out")
}
}
impl StdError for TimedOut {}
#[cfg(test)]
mod tests {
use super::*;
use std::mem;
#[test]
fn error_size_of() {
assert_eq!(mem::size_of::<Error>(), mem::size_of::<usize>());
}
#[cfg(feature = "http2")]
#[test]
fn h2_reason_unknown() {
let closed = Error::new_closed();
assert_eq!(closed.h2_reason(), h2::Reason::INTERNAL_ERROR);
}
#[cfg(feature = "http2")]
#[test]
fn h2_reason_one_level() {
let body_err = Error::new_user_body(h2::Error::from(h2::Reason::ENHANCE_YOUR_CALM));
assert_eq!(body_err.h2_reason(), h2::Reason::ENHANCE_YOUR_CALM);
}
#[cfg(feature = "http2")]
#[test]
fn h2_reason_nested() {
let recvd = Error::new_h2(h2::Error::from(h2::Reason::HTTP_1_1_REQUIRED));
let svc_err = Error::new_user_service(recvd);
assert_eq!(svc_err.h2_reason(), h2::Reason::HTTP_1_1_REQUIRED);
}
}