741 lines
26 KiB
Rust
741 lines
26 KiB
Rust
//! Error and Result module.
|
|
use std::error::Error as StdError;
|
|
use std::fmt;
|
|
|
|
/// Result type often returned from methods that can have hyper `Error`s.
|
|
pub type Result<T> = std::result::Result<T, Error>;
|
|
|
|
type Cause = Box<dyn StdError + Send + Sync>;
|
|
|
|
/// Represents errors that can occur handling HTTP streams.
|
|
///
|
|
/// # Formatting
|
|
///
|
|
/// The `Display` implementation of this type will only print the details of
|
|
/// this level of error, even though it may have been caused by another error
|
|
/// and contain that error in its source. To print all the relevant
|
|
/// information, including the source chain, using something like
|
|
/// `std::error::Report`, or equivalent 3rd party types.
|
|
///
|
|
/// The contents of the formatted error message of this specific `Error` type
|
|
/// is unspecified. **You must not depend on it.** The wording and details may
|
|
/// change in any version, with the goal of improving error messages.
|
|
///
|
|
/// # Source
|
|
///
|
|
/// A `hyper::Error` may be caused by another error. To aid in debugging,
|
|
/// those are exposed in `Error::source()` as erased types. While it is
|
|
/// possible to check the exact type of the sources, they **can not be depended
|
|
/// on**. They may come from private internal dependencies, and are subject to
|
|
/// change at any moment.
|
|
pub struct Error {
|
|
inner: Box<ErrorImpl>,
|
|
}
|
|
|
|
struct ErrorImpl {
|
|
kind: Kind,
|
|
cause: Option<Cause>,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) enum Kind {
|
|
Parse(Parse),
|
|
User(User),
|
|
/// A message reached EOF, but is not complete.
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
IncompleteMessage,
|
|
/// A connection received a message (or bytes) when not waiting for one.
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
UnexpectedMessage,
|
|
/// A pending item was dropped before ever being processed.
|
|
Canceled,
|
|
/// Indicates a channel (client or body sender) is closed.
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
all(feature = "http2", feature = "client")
|
|
))]
|
|
ChannelClosed,
|
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Io,
|
|
/// User took too long to send headers.
|
|
#[cfg(all(feature = "http1", feature = "server"))]
|
|
HeaderTimeout,
|
|
/// Error while reading a body from connection.
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Body,
|
|
/// Error while writing a body to connection.
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
BodyWrite,
|
|
/// Error calling `AsyncWrite::shutdown()`.
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Shutdown,
|
|
|
|
/// A general error from h2.
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http2"))]
|
|
Http2,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) enum Parse {
|
|
Method,
|
|
#[cfg(feature = "http1")]
|
|
Version,
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
VersionH2,
|
|
Uri,
|
|
#[cfg(all(feature = "http1", feature = "server"))]
|
|
UriTooLong,
|
|
#[cfg(feature = "http1")]
|
|
Header(Header),
|
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
|
#[cfg_attr(feature = "http2", allow(unused))]
|
|
TooLarge,
|
|
Status,
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Internal,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
#[cfg(feature = "http1")]
|
|
pub(super) enum Header {
|
|
Token,
|
|
#[cfg(any(feature = "client", feature = "server"))]
|
|
ContentLengthInvalid,
|
|
#[cfg(feature = "server")]
|
|
TransferEncodingInvalid,
|
|
#[cfg(any(feature = "client", feature = "server"))]
|
|
TransferEncodingUnexpected,
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub(super) enum User {
|
|
/// Error calling the user's `Body::poll_data()`.
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Body,
|
|
/// The user aborted writing of the outgoing body.
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
feature = "ffi"
|
|
))]
|
|
BodyWriteAborted,
|
|
/// User tried to send a connect request with a nonzero body.
|
|
#[cfg(all(feature = "client", feature = "http2"))]
|
|
InvalidConnectWithBody,
|
|
/// Error from future of user's Service.
|
|
#[cfg(any(
|
|
all(any(feature = "client", feature = "server"), feature = "http1"),
|
|
all(feature = "server", feature = "http2")
|
|
))]
|
|
Service,
|
|
/// User tried to send a certain header in an unexpected context.
|
|
///
|
|
/// For example, sending both `content-length` and `transfer-encoding`.
|
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
|
#[cfg(feature = "server")]
|
|
UnexpectedHeader,
|
|
/// User tried to respond with a 1xx (not 101) response code.
|
|
#[cfg(feature = "http1")]
|
|
#[cfg(feature = "server")]
|
|
UnsupportedStatusCode,
|
|
|
|
/// User tried polling for an upgrade that doesn't exist.
|
|
NoUpgrade,
|
|
|
|
/// User polled for an upgrade, but low-level API is not using upgrades.
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
ManualUpgrade,
|
|
|
|
/// The dispatch task is gone.
|
|
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
|
DispatchGone,
|
|
|
|
/// User aborted in an FFI callback.
|
|
#[cfg(feature = "ffi")]
|
|
AbortedByCallback,
|
|
}
|
|
|
|
// Sentinel type to indicate the error was caused by a timeout.
|
|
#[derive(Debug)]
|
|
pub(super) struct TimedOut;
|
|
|
|
impl Error {
|
|
/// Returns true if this was an HTTP parse error.
|
|
///
|
|
/// This can be caused by a malformed HTTP message, an invalid header,
|
|
/// an invalid URI, an invalid HTTP version, or a message head that is
|
|
/// too large. Use the more specific `is_parse_*` methods to determine
|
|
/// the exact cause.
|
|
pub fn is_parse(&self) -> bool {
|
|
matches!(self.inner.kind, Kind::Parse(_))
|
|
}
|
|
|
|
/// Returns true if this was an HTTP parse error caused by a message that was too large.
|
|
///
|
|
/// This is triggered when the message head (request line plus headers for
|
|
/// HTTP/1, or header frame for HTTP/2) exceeds the configured
|
|
/// [`max_buf_size`](crate::server::conn::http1::Builder::max_buf_size).
|
|
/// It also covers the case where the URI alone exceeds the internal
|
|
/// maximum URI length.
|
|
#[cfg(all(feature = "http1", feature = "server"))]
|
|
pub fn is_parse_too_large(&self) -> bool {
|
|
matches!(
|
|
self.inner.kind,
|
|
Kind::Parse(Parse::TooLarge) | Kind::Parse(Parse::UriTooLong)
|
|
)
|
|
}
|
|
|
|
/// Returns true if this was an HTTP parse error caused by an invalid response status code or
|
|
/// reason phrase.
|
|
pub fn is_parse_status(&self) -> bool {
|
|
matches!(self.inner.kind, Kind::Parse(Parse::Status))
|
|
}
|
|
|
|
/// Returns true if this was an HTTP parse error caused by HTTP2 preface sent over an HTTP1
|
|
/// connection.
|
|
///
|
|
/// This can happen when a client sends an HTTP/2 connection preface to a
|
|
/// server that is only expecting HTTP/1.x requests.
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
pub fn is_parse_version_h2(&self) -> bool {
|
|
matches!(self.inner.kind, Kind::Parse(Parse::VersionH2))
|
|
}
|
|
|
|
/// Returns true if this error was caused by user code.
|
|
///
|
|
/// For example, this can be returned when the user's `Service` returns
|
|
/// an error, the user's `Body` stream yields an error, or the user
|
|
/// sends an unexpected header combination (such as both
|
|
/// `content-length` and `transfer-encoding`).
|
|
pub fn is_user(&self) -> bool {
|
|
matches!(self.inner.kind, Kind::User(_))
|
|
}
|
|
|
|
/// Returns true if this was about a `Request` that was canceled.
|
|
///
|
|
/// This typically happens when a pending request is dropped before
|
|
/// it can be dispatched to the connection, for example because the
|
|
/// connection was not ready.
|
|
pub fn is_canceled(&self) -> bool {
|
|
matches!(self.inner.kind, Kind::Canceled)
|
|
}
|
|
|
|
/// Returns true if a sender's channel is closed.
|
|
///
|
|
/// This can occur when the other side of a client or body channel
|
|
/// has been dropped, indicating that the receiver is no longer
|
|
/// interested in the data.
|
|
pub fn is_closed(&self) -> bool {
|
|
#[cfg(not(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
all(feature = "http2", feature = "client")
|
|
)))]
|
|
return false;
|
|
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
all(feature = "http2", feature = "client")
|
|
))]
|
|
matches!(self.inner.kind, Kind::ChannelClosed)
|
|
}
|
|
|
|
/// Returns true if the connection closed before a message could complete.
|
|
///
|
|
/// This means that the supplied IO connection reported EOF (closed) while
|
|
/// hyper's HTTP state indicates more of the message (either request or
|
|
/// response) needed to be transmitted.
|
|
///
|
|
/// Some cases this could happen (not exhaustive):
|
|
///
|
|
/// - A request is written on a connection, and the next `read` reports
|
|
/// EOF (perhaps a server just closed an "idle" connection).
|
|
/// - A message body is only partially receive before the connection
|
|
/// reports EOF.
|
|
/// - A client writes a request to your server, and then closes the write
|
|
/// half while waiting for your response. If you need to support this,
|
|
/// consider enabling [`half_close`].
|
|
///
|
|
/// [`half_close`]: crate::server::conn::http1::Builder::half_close()
|
|
pub fn is_incomplete_message(&self) -> bool {
|
|
#[cfg(not(all(any(feature = "client", feature = "server"), feature = "http1")))]
|
|
return false;
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
matches!(self.inner.kind, Kind::IncompleteMessage)
|
|
}
|
|
|
|
/// Returns true if the body write was aborted.
|
|
///
|
|
/// This occurs when the user's code explicitly aborts writing of the
|
|
/// outgoing body before it completes, for example by dropping the
|
|
/// body sender.
|
|
pub fn is_body_write_aborted(&self) -> bool {
|
|
#[cfg(not(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
feature = "ffi"
|
|
)))]
|
|
return false;
|
|
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
feature = "ffi"
|
|
))]
|
|
matches!(self.inner.kind, Kind::User(User::BodyWriteAborted))
|
|
}
|
|
|
|
/// Returns true if the error was caused while calling `AsyncWrite::shutdown()`.
|
|
///
|
|
/// This can happen when the connection is being gracefully shut down
|
|
/// and the underlying IO reports an error during the shutdown sequence.
|
|
pub fn is_shutdown(&self) -> bool {
|
|
#[cfg(all(feature = "http1", any(feature = "client", feature = "server")))]
|
|
if matches!(self.inner.kind, Kind::Shutdown) {
|
|
return true;
|
|
}
|
|
false
|
|
}
|
|
|
|
/// Returns true if the error was caused by a timeout.
|
|
///
|
|
/// For HTTP/1 servers, this includes the header read timeout (see
|
|
/// [`header_read_timeout`](crate::server::conn::http1::Builder::header_read_timeout)).
|
|
/// It also covers any timeout set via a user-provided timer.
|
|
pub fn is_timeout(&self) -> bool {
|
|
#[cfg(all(feature = "http1", feature = "server"))]
|
|
if matches!(self.inner.kind, Kind::HeaderTimeout) {
|
|
return true;
|
|
}
|
|
self.find_source::<TimedOut>().is_some()
|
|
}
|
|
|
|
pub(super) fn new(kind: Kind) -> Error {
|
|
Error {
|
|
inner: Box::new(ErrorImpl { kind, cause: None }),
|
|
}
|
|
}
|
|
|
|
pub(super) fn with<C: Into<Cause>>(mut self, cause: C) -> Error {
|
|
self.inner.cause = Some(cause.into());
|
|
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(typed) = err.downcast_ref() {
|
|
return Some(typed);
|
|
}
|
|
cause = err.source();
|
|
}
|
|
|
|
// else
|
|
None
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http2"))]
|
|
pub(super) fn h2_reason(&self) -> h2::Reason {
|
|
// Find an h2::Reason somewhere in the cause stack, if it exists,
|
|
// otherwise assume an INTERNAL_ERROR.
|
|
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(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
pub(super) fn new_incomplete() -> Error {
|
|
Error::new(Kind::IncompleteMessage)
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
pub(super) fn new_too_large() -> Error {
|
|
Error::new(Kind::Parse(Parse::TooLarge))
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
pub(super) fn new_version_h2() -> Error {
|
|
Error::new(Kind::Parse(Parse::VersionH2))
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
pub(super) fn new_unexpected_message() -> Error {
|
|
Error::new(Kind::UnexpectedMessage)
|
|
}
|
|
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
pub(super) fn new_io(cause: std::io::Error) -> Error {
|
|
Error::new(Kind::Io).with(cause)
|
|
}
|
|
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
all(feature = "http2", feature = "client")
|
|
))]
|
|
pub(super) fn new_closed() -> Error {
|
|
Error::new(Kind::ChannelClosed)
|
|
}
|
|
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error {
|
|
Error::new(Kind::Body).with(cause)
|
|
}
|
|
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
pub(super) fn new_body_write<E: Into<Cause>>(cause: E) -> Error {
|
|
Error::new(Kind::BodyWrite).with(cause)
|
|
}
|
|
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
feature = "ffi"
|
|
))]
|
|
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"))]
|
|
pub(super) fn new_header_timeout() -> Error {
|
|
Error::new(Kind::HeaderTimeout)
|
|
}
|
|
|
|
#[cfg(feature = "http1")]
|
|
#[cfg(feature = "server")]
|
|
pub(super) fn new_user_unsupported_status_code() -> Error {
|
|
Error::new_user(User::UnsupportedStatusCode)
|
|
}
|
|
|
|
pub(super) fn new_user_no_upgrade() -> Error {
|
|
Error::new_user(User::NoUpgrade)
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
pub(super) fn new_user_manual_upgrade() -> Error {
|
|
Error::new_user(User::ManualUpgrade)
|
|
}
|
|
|
|
#[cfg(any(
|
|
all(any(feature = "client", feature = "server"), feature = "http1"),
|
|
all(feature = "server", feature = "http2")
|
|
))]
|
|
pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error {
|
|
Error::new_user(User::Service).with(cause)
|
|
}
|
|
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
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(all(feature = "client", feature = "http2"))]
|
|
pub(super) fn new_user_invalid_connect() -> Error {
|
|
Error::new_user(User::InvalidConnectWithBody)
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), 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(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
|
pub(super) fn new_user_dispatch_gone() -> Error {
|
|
Error::new(Kind::User(User::DispatchGone))
|
|
}
|
|
|
|
#[cfg(all(any(feature = "client", feature = "server"), 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)
|
|
}
|
|
}
|
|
|
|
fn description(&self) -> &str {
|
|
match self.inner.kind {
|
|
Kind::Parse(Parse::Method) => "invalid HTTP method parsed",
|
|
#[cfg(feature = "http1")]
|
|
Kind::Parse(Parse::Version) => "invalid HTTP version parsed",
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Kind::Parse(Parse::VersionH2) => "invalid HTTP version parsed (found HTTP2 preface)",
|
|
Kind::Parse(Parse::Uri) => "invalid URI",
|
|
#[cfg(all(feature = "http1", feature = "server"))]
|
|
Kind::Parse(Parse::UriTooLong) => "URI too long",
|
|
#[cfg(feature = "http1")]
|
|
Kind::Parse(Parse::Header(Header::Token)) => "invalid HTTP header parsed",
|
|
#[cfg(all(any(feature = "client", feature = "server"), 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(all(feature = "http1", any(feature = "client", feature = "server")))]
|
|
Kind::Parse(Parse::Header(Header::TransferEncodingUnexpected)) => {
|
|
"unexpected transfer-encoding parsed"
|
|
}
|
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
|
Kind::Parse(Parse::TooLarge) => "message head is too large",
|
|
Kind::Parse(Parse::Status) => "invalid HTTP status-code parsed",
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Kind::Parse(Parse::Internal) => {
|
|
"internal error inside Hyper and/or its dependencies, please report"
|
|
}
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Kind::IncompleteMessage => "connection closed before message completed",
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Kind::UnexpectedMessage => "received unexpected message from connection",
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
all(feature = "http2", feature = "client")
|
|
))]
|
|
Kind::ChannelClosed => "channel closed",
|
|
Kind::Canceled => "operation was canceled",
|
|
#[cfg(all(feature = "http1", feature = "server"))]
|
|
Kind::HeaderTimeout => "read header from client timeout",
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Kind::Body => "error reading a body from connection",
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Kind::BodyWrite => "error writing a body to connection",
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Kind::Shutdown => "error shutting down connection",
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http2"))]
|
|
Kind::Http2 => "http2 error",
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Kind::Io => "connection error",
|
|
|
|
#[cfg(all(
|
|
any(feature = "client", feature = "server"),
|
|
any(feature = "http1", feature = "http2")
|
|
))]
|
|
Kind::User(User::Body) => "error from user's Body stream",
|
|
#[cfg(any(
|
|
all(feature = "http1", any(feature = "client", feature = "server")),
|
|
feature = "ffi"
|
|
))]
|
|
Kind::User(User::BodyWriteAborted) => "user body write aborted",
|
|
#[cfg(all(feature = "client", feature = "http2"))]
|
|
Kind::User(User::InvalidConnectWithBody) => {
|
|
"user sent CONNECT request with non-zero body"
|
|
}
|
|
#[cfg(any(
|
|
all(any(feature = "client", feature = "server"), feature = "http1"),
|
|
all(feature = "server", 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(feature = "http1")]
|
|
#[cfg(feature = "server")]
|
|
Kind::User(User::UnsupportedStatusCode) => {
|
|
"response has 1xx status code, not supported by server"
|
|
}
|
|
Kind::User(User::NoUpgrade) => "no upgrade available",
|
|
#[cfg(all(any(feature = "client", feature = "server"), feature = "http1"))]
|
|
Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use",
|
|
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
|
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 {
|
|
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 {
|
|
#[cfg(any(feature = "client", feature = "server"))]
|
|
pub(crate) fn content_length_invalid() -> Self {
|
|
Parse::Header(Header::ContentLengthInvalid)
|
|
}
|
|
|
|
#[cfg(feature = "server")]
|
|
pub(crate) fn transfer_encoding_invalid() -> Self {
|
|
Parse::Header(Header::TransferEncodingInvalid)
|
|
}
|
|
|
|
#[cfg(any(feature = "client", feature = "server"))]
|
|
pub(crate) fn transfer_encoding_unexpected() -> Self {
|
|
Parse::Header(Header::TransferEncodingUnexpected)
|
|
}
|
|
}
|
|
|
|
#[cfg(feature = "http1")]
|
|
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
|
|
}
|
|
}
|
|
|
|
// ===== impl TimedOut ====
|
|
|
|
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;
|
|
|
|
fn assert_send_sync<T: Send + Sync + 'static>() {}
|
|
|
|
#[test]
|
|
fn error_satisfies_send_sync() {
|
|
assert_send_sync::<Error>()
|
|
}
|
|
|
|
#[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));
|
|
// Suppose a user were proxying the received error
|
|
let svc_err = Error::new_user_service(recvd);
|
|
assert_eq!(svc_err.h2_reason(), h2::Reason::HTTP_1_1_REQUIRED);
|
|
}
|
|
}
|