Skip to main content

timvw_rust_runtime/
error.rs

1use std::error::Error as StdError;
2use std::fmt;
3
4use crate::BoxError;
5
6/// An error building a request, sending it, or reading the response body.
7pub struct Error {
8    kind: Kind,
9    source: Option<BoxError>,
10}
11
12#[derive(Clone, Copy, Debug, PartialEq, Eq)]
13enum Kind {
14    Builder,
15    Request,
16    Body,
17    Decode,
18}
19
20impl Error {
21    pub(crate) fn builder(source: impl Into<BoxError>) -> Self {
22        Self {
23            kind: Kind::Builder,
24            source: Some(source.into()),
25        }
26    }
27
28    pub(crate) fn request(source: impl Into<BoxError>) -> Self {
29        Self {
30            kind: Kind::Request,
31            source: Some(source.into()),
32        }
33    }
34
35    pub(crate) fn body(source: impl Into<BoxError>) -> Self {
36        Self {
37            kind: Kind::Body,
38            source: Some(source.into()),
39        }
40    }
41
42    pub(crate) fn decode(source: impl Into<BoxError>) -> Self {
43        Self {
44            kind: Kind::Decode,
45            source: Some(source.into()),
46        }
47    }
48
49    /// The request could not be built: an invalid URL, header, or body.
50    pub fn is_builder(&self) -> bool {
51        self.kind == Kind::Builder
52    }
53
54    /// The service failed to send the request or to receive a response: connection refused, a timeout layer gave up,
55    /// and so on. [`Error::source`](std::error::Error::source) is the service's error.
56    pub fn is_request(&self) -> bool {
57        self.kind == Kind::Request
58    }
59
60    /// Reading the response body failed.
61    pub fn is_body(&self) -> bool {
62        self.kind == Kind::Body
63    }
64
65    /// The response body could not be decoded.
66    pub fn is_decode(&self) -> bool {
67        self.kind == Kind::Decode
68    }
69
70    /// Whether the request timed out: a reqwest timeout or tower's `timeout` layer.
71    pub fn is_timeout(&self) -> bool {
72        let mut source: Option<&(dyn StdError + 'static)> = self.source.as_deref().map(|e| e as _);
73        while let Some(error) = source {
74            if error.to_string() == "request timed out" {
75                return true;
76            }
77            #[cfg(feature = "reqwest")]
78            if let Some(error) = error.downcast_ref::<reqwest::Error>() {
79                if error.is_timeout() {
80                    return true;
81                }
82            }
83            source = error.source();
84        }
85        false
86    }
87
88    /// Whether connecting to the server failed (only known for reqwest).
89    pub fn is_connect(&self) -> bool {
90        #[cfg(feature = "reqwest")]
91        {
92            let mut source: Option<&(dyn StdError + 'static)> =
93                self.source.as_deref().map(|e| e as _);
94            while let Some(error) = source {
95                if let Some(error) = error.downcast_ref::<reqwest::Error>() {
96                    if error.is_connect() {
97                        return true;
98                    }
99                }
100                source = error.source();
101            }
102        }
103        false
104    }
105}
106
107impl fmt::Debug for Error {
108    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
109        f.debug_struct("Error")
110            .field("kind", &self.kind)
111            .field("source", &self.source)
112            .finish()
113    }
114}
115
116impl fmt::Display for Error {
117    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118        let what = match self.kind {
119            Kind::Builder => "error building the request",
120            Kind::Request => "error sending the request",
121            Kind::Body => "error reading the response body",
122            Kind::Decode => "error decoding the response body",
123        };
124        match &self.source {
125            Some(source) => write!(f, "{what}: {source}"),
126            None => f.write_str(what),
127        }
128    }
129}
130
131impl StdError for Error {
132    fn source(&self) -> Option<&(dyn StdError + 'static)> {
133        self.source.as_deref().map(|e| e as _)
134    }
135}