Skip to main content

wreq_proto/
error.rs

1//! Error and Result module.
2use std::{error::Error as StdError, fmt};
3
4/// Result type often returned from methods that can have crate::core: `Error`s.
5pub type Result<T, E = Error> = std::result::Result<T, E>;
6
7pub type BoxError = Box<dyn StdError + Send + Sync>;
8
9type Cause = BoxError;
10
11/// Represents errors that can occur handling HTTP streams.
12///
13/// # Formatting
14///
15/// The `Display` implementation of this type will only print the details of
16/// this level of error, even though it may have been caused by another error
17/// and contain that error in its source. To print all the relevant
18/// information, including the source chain, using something like
19/// `std::error::Report`, or equivalent 3rd party types.
20///
21/// The contents of the formatted error message of this specific `Error` type
22/// is unspecified. **You must not depend on it.** The wording and details may
23/// change in any version, with the goal of improving error messages.
24///
25/// # Source
26///
27/// A `wreq_proto::Error` may be caused by another error. To aid in debugging,
28/// those are exposed in `Error::source()` as erased types. While it is
29/// possible to check the exact type of the sources, they **can not be depended
30/// on**. They may come from private internal dependencies, and are subject to
31/// change at any moment.
32pub struct Error {
33    inner: Box<ErrorImpl>,
34}
35
36struct ErrorImpl {
37    kind: Kind,
38    cause: Option<Cause>,
39}
40
41#[derive(Debug)]
42pub(super) enum Kind {
43    Parse(Parse),
44    User(User),
45    /// A message reached EOF, but is not complete.
46    IncompleteMessage,
47    /// A connection received a message (or bytes) when not waiting for one.
48    UnexpectedMessage,
49    /// A pending item was dropped before ever being processed.
50    Canceled,
51    /// Indicates a channel (client or body sender) is closed.
52    ChannelClosed,
53    /// An `io::Error` that occurred while trying to read or write to a network stream.
54    Io,
55    /// Error while reading a body from connection.
56    Body,
57    /// Error while writing a body to connection.
58    BodyWrite,
59    /// Error calling AsyncWrite::shutdown()
60    Shutdown,
61    /// A general error from h2.
62    Http2,
63}
64
65#[derive(Debug)]
66pub(crate) enum Parse {
67    Method,
68    Version,
69    VersionH2,
70    Uri,
71    Header(Header),
72    TooLarge,
73    Status,
74    Internal,
75}
76
77#[derive(Debug)]
78pub(crate) enum Header {
79    Token,
80    ContentLengthInvalid,
81    TransferEncodingUnexpected,
82}
83
84#[derive(Debug)]
85pub(super) enum User {
86    /// Error calling user's Body::poll_data().
87    Body,
88    /// The user aborted writing of the outgoing body.
89    BodyWriteAborted,
90    /// User tried to send a connect request with a nonzero body
91    InvalidConnectWithBody,
92    /// Error from future of user's Service.
93    Service,
94    /// User tried polling for an upgrade that doesn't exist.
95    NoUpgrade,
96    /// User polled for an upgrade, but low-level API is not using upgrades.
97    ManualUpgrade,
98    /// The dispatch task is gone.
99    DispatchGone,
100}
101
102// Sentinel type to indicate the error was caused by a timeout.
103#[derive(Debug)]
104pub(super) struct TimedOut;
105
106impl Error {
107    /// Returns true if this was an HTTP parse error.
108    #[inline]
109    pub fn is_parse(&self) -> bool {
110        matches!(self.inner.kind, Kind::Parse(_))
111    }
112
113    /// Returns true if this was an HTTP parse error caused by an invalid response status code or
114    /// reason phrase.
115    #[inline]
116    pub fn is_parse_status(&self) -> bool {
117        matches!(self.inner.kind, Kind::Parse(Parse::Status))
118    }
119
120    /// Returns true if this error was caused by user code.
121    #[inline]
122    pub fn is_user(&self) -> bool {
123        matches!(self.inner.kind, Kind::User(_))
124    }
125
126    /// Returns true if this was about a `Request` that was canceled.
127    #[inline]
128    pub fn is_canceled(&self) -> bool {
129        matches!(self.inner.kind, Kind::Canceled)
130    }
131
132    /// Returns true if a sender's channel is closed.
133    #[inline]
134    pub fn is_closed(&self) -> bool {
135        matches!(self.inner.kind, Kind::ChannelClosed)
136    }
137
138    /// Returns true if the connection closed before a message could complete.
139    #[inline]
140    pub fn is_incomplete_message(&self) -> bool {
141        matches!(self.inner.kind, Kind::IncompleteMessage)
142    }
143
144    /// Returns true if the body write was aborted.
145    #[inline]
146    pub fn is_body_write_aborted(&self) -> bool {
147        matches!(self.inner.kind, Kind::User(User::BodyWriteAborted))
148    }
149
150    /// Returns true if the error was caused by a timeout.
151    #[inline]
152    pub fn is_timeout(&self) -> bool {
153        self.find_source::<TimedOut>().is_some()
154    }
155
156    #[inline]
157    pub(super) fn new(kind: Kind) -> Error {
158        Error {
159            inner: Box::new(ErrorImpl { kind, cause: None }),
160        }
161    }
162
163    #[inline]
164    pub(super) fn with<C: Into<Cause>>(mut self, cause: C) -> Error {
165        self.inner.cause = Some(cause.into());
166        self
167    }
168
169    pub(crate) fn find_source<E: StdError + 'static>(&self) -> Option<&E> {
170        let mut cause = self.source();
171        while let Some(err) = cause {
172            if let Some(typed) = err.downcast_ref() {
173                return Some(typed);
174            }
175            cause = err.source();
176        }
177
178        // else
179        None
180    }
181
182    pub(super) fn h2_reason(&self) -> http2::Reason {
183        // Find an http2::Reason somewhere in the cause stack, if it exists,
184        // otherwise assume an INTERNAL_ERROR.
185        self.find_source::<http2::Error>()
186            .and_then(|h2_err| h2_err.reason())
187            .unwrap_or(http2::Reason::INTERNAL_ERROR)
188    }
189
190    #[inline]
191    pub(super) fn new_canceled() -> Error {
192        Error::new(Kind::Canceled)
193    }
194
195    #[inline]
196    pub(super) fn new_incomplete() -> Error {
197        Error::new(Kind::IncompleteMessage)
198    }
199
200    #[inline]
201    pub(super) fn new_too_large() -> Error {
202        Error::new(Kind::Parse(Parse::TooLarge))
203    }
204
205    #[inline]
206    pub(super) fn new_version_h2() -> Error {
207        Error::new(Kind::Parse(Parse::VersionH2))
208    }
209
210    #[inline]
211    pub(super) fn new_unexpected_message() -> Error {
212        Error::new(Kind::UnexpectedMessage)
213    }
214
215    #[inline]
216    pub(super) fn new_io(cause: std::io::Error) -> Error {
217        Error::new(Kind::Io).with(cause)
218    }
219
220    #[inline]
221    pub(super) fn new_closed() -> Error {
222        Error::new(Kind::ChannelClosed)
223    }
224
225    #[inline]
226    pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error {
227        Error::new(Kind::Body).with(cause)
228    }
229
230    #[inline]
231    pub(super) fn new_body_write<E: Into<Cause>>(cause: E) -> Error {
232        Error::new(Kind::BodyWrite).with(cause)
233    }
234
235    #[inline]
236    pub(super) fn new_body_write_aborted() -> Error {
237        Error::new(Kind::User(User::BodyWriteAborted))
238    }
239
240    #[inline]
241    fn new_user(user: User) -> Error {
242        Error::new(Kind::User(user))
243    }
244
245    #[inline]
246    pub(super) fn new_user_no_upgrade() -> Error {
247        Error::new_user(User::NoUpgrade)
248    }
249
250    #[inline]
251    pub(super) fn new_user_manual_upgrade() -> Error {
252        Error::new_user(User::ManualUpgrade)
253    }
254
255    #[inline]
256    pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error {
257        Error::new_user(User::Service).with(cause)
258    }
259
260    #[inline]
261    pub(super) fn new_user_body<E: Into<Cause>>(cause: E) -> Error {
262        Error::new_user(User::Body).with(cause)
263    }
264
265    #[inline]
266    pub(super) fn new_user_invalid_connect() -> Error {
267        Error::new_user(User::InvalidConnectWithBody)
268    }
269
270    #[inline]
271    pub(super) fn new_shutdown(cause: std::io::Error) -> Error {
272        Error::new(Kind::Shutdown).with(cause)
273    }
274
275    #[inline]
276    pub(super) fn new_user_dispatch_gone() -> Error {
277        Error::new(Kind::User(User::DispatchGone))
278    }
279
280    pub(super) fn new_h2(cause: ::http2::Error) -> Error {
281        if cause.is_io() {
282            Error::new_io(cause.into_io().expect("http2::Error::is_io"))
283        } else {
284            Error::new(Kind::Http2).with(cause)
285        }
286    }
287
288    fn description(&self) -> &str {
289        match self.inner.kind {
290            Kind::Parse(Parse::Method) => "invalid HTTP method parsed",
291            Kind::Parse(Parse::Version) => "invalid HTTP version parsed",
292            Kind::Parse(Parse::VersionH2) => "invalid HTTP version parsed (found HTTP2 preface)",
293            Kind::Parse(Parse::Uri) => "invalid URI",
294            Kind::Parse(Parse::Header(Header::Token)) => "invalid HTTP header parsed",
295            Kind::Parse(Parse::Header(Header::ContentLengthInvalid)) => {
296                "invalid content-length parsed"
297            }
298            Kind::Parse(Parse::Header(Header::TransferEncodingUnexpected)) => {
299                "unexpected transfer-encoding parsed"
300            }
301            Kind::Parse(Parse::TooLarge) => "message head is too large",
302            Kind::Parse(Parse::Status) => "invalid HTTP status-code parsed",
303            Kind::Parse(Parse::Internal) => {
304                "internal error inside wreq and/or its dependencies, please report"
305            }
306
307            Kind::IncompleteMessage => "connection closed before message completed",
308            Kind::UnexpectedMessage => "received unexpected message from connection",
309            Kind::ChannelClosed => "channel closed",
310            Kind::Canceled => "operation was canceled",
311            Kind::Body => "error reading a body from connection",
312            Kind::BodyWrite => "error writing a body to connection",
313            Kind::Shutdown => "error shutting down connection",
314            Kind::Http2 => "http2 error",
315            Kind::Io => "connection error",
316
317            Kind::User(User::Body) => "error from user's Body stream",
318            Kind::User(User::BodyWriteAborted) => "user body write aborted",
319            Kind::User(User::InvalidConnectWithBody) => {
320                "user sent CONNECT request with non-zero body"
321            }
322            Kind::User(User::Service) => "error from user's Service",
323            Kind::User(User::NoUpgrade) => "no upgrade available",
324            Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use",
325            Kind::User(User::DispatchGone) => "dispatch task is gone",
326        }
327    }
328}
329
330impl fmt::Debug for Error {
331    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
332        let mut f = f.debug_tuple("wreq_proto::Error");
333        f.field(&self.inner.kind);
334        if let Some(ref cause) = self.inner.cause {
335            f.field(cause);
336        }
337        f.finish()
338    }
339}
340
341impl fmt::Display for Error {
342    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
343        f.write_str(self.description())
344    }
345}
346
347impl StdError for Error {
348    fn source(&self) -> Option<&(dyn StdError + 'static)> {
349        self.inner
350            .cause
351            .as_ref()
352            .map(|cause| &**cause as &(dyn StdError + 'static))
353    }
354}
355
356#[doc(hidden)]
357impl From<Parse> for Error {
358    fn from(err: Parse) -> Error {
359        Error::new(Kind::Parse(err))
360    }
361}
362
363impl Parse {
364    #[inline]
365    pub(crate) fn content_length_invalid() -> Self {
366        Parse::Header(Header::ContentLengthInvalid)
367    }
368
369    #[inline]
370    pub(crate) fn transfer_encoding_unexpected() -> Self {
371        Parse::Header(Header::TransferEncodingUnexpected)
372    }
373}
374
375impl From<httparse::Error> for Parse {
376    fn from(err: httparse::Error) -> Parse {
377        match err {
378            httparse::Error::HeaderName
379            | httparse::Error::HeaderValue
380            | httparse::Error::NewLine
381            | httparse::Error::Token => Parse::Header(Header::Token),
382            httparse::Error::Status => Parse::Status,
383            httparse::Error::TooManyHeaders => Parse::TooLarge,
384            httparse::Error::Version => Parse::Version,
385        }
386    }
387}
388
389impl From<http::method::InvalidMethod> for Parse {
390    fn from(_: http::method::InvalidMethod) -> Parse {
391        Parse::Method
392    }
393}
394
395impl From<http::status::InvalidStatusCode> for Parse {
396    fn from(_: http::status::InvalidStatusCode) -> Parse {
397        Parse::Status
398    }
399}
400
401impl From<http::uri::InvalidUri> for Parse {
402    fn from(_: http::uri::InvalidUri) -> Parse {
403        Parse::Uri
404    }
405}
406
407impl From<http::uri::InvalidUriParts> for Parse {
408    fn from(_: http::uri::InvalidUriParts) -> Parse {
409        Parse::Uri
410    }
411}
412
413// ===== impl TimedOut ====
414
415impl fmt::Display for TimedOut {
416    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417        f.write_str("operation timed out")
418    }
419}
420
421impl StdError for TimedOut {}
422
423#[cfg(test)]
424mod tests {
425    use std::mem;
426
427    use super::*;
428
429    fn assert_send_sync<T: Send + Sync + 'static>() {}
430
431    #[test]
432    fn error_satisfies_send_sync() {
433        assert_send_sync::<Error>()
434    }
435
436    #[test]
437    fn error_size_of() {
438        assert_eq!(mem::size_of::<Error>(), mem::size_of::<usize>());
439    }
440
441    #[test]
442    fn h2_reason_unknown() {
443        let closed = Error::new_closed();
444        assert_eq!(closed.h2_reason(), http2::Reason::INTERNAL_ERROR);
445    }
446
447    #[test]
448    fn h2_reason_one_level() {
449        let body_err = Error::new_user_body(http2::Error::from(http2::Reason::ENHANCE_YOUR_CALM));
450        assert_eq!(body_err.h2_reason(), http2::Reason::ENHANCE_YOUR_CALM);
451    }
452
453    #[test]
454    fn h2_reason_nested() {
455        let recvd = Error::new_h2(http2::Error::from(http2::Reason::HTTP_1_1_REQUIRED));
456        // Suppose a user were proxying the received error
457        let svc_err = Error::new_user_service(recvd);
458        assert_eq!(svc_err.h2_reason(), http2::Reason::HTTP_1_1_REQUIRED);
459    }
460}