volo_http/error/
client.rs

1//! Generic error types for client
2
3use std::{error::Error, fmt, net::SocketAddr};
4
5use http::uri::Uri;
6use paste::paste;
7use volo::{context::Endpoint, net::Address};
8
9use super::BoxError;
10use crate::body::BodyConvertError;
11
12/// [`Result`](std::result::Result) with [`ClientError`] as its error by default.
13pub type Result<T, E = ClientError> = std::result::Result<T, E>;
14
15macro_rules! tri {
16    ($result:expr) => {
17        match $result {
18            Ok(val) => val,
19            Err(err) => return Err(err),
20        }
21    };
22    ($result:expr, $($arg:tt)*) => {
23        match $result {
24            Ok(val) => val,
25            Err(err) => {
26                ::tracing::info!($($arg)*);
27                return Err(err);
28            }
29        }
30    };
31}
32pub(crate) use tri;
33
34/// Generic client error
35#[derive(Debug)]
36pub struct ClientError {
37    kind: ErrorKind,
38    source: Option<BoxError>,
39    // The type `Uri` takes up 88 bytes, which causes the entire `ClientError` to take up 144 bytes
40    // and clippy will throw `clippy::result_large_err`.
41    //
42    // Considering that the field will not be used in the hot path, in order to avoid the problem
43    // of `ClientError` being too large, we added `Box` to `Uri`.
44    uri: Option<Box<Uri>>,
45    addr: Option<SocketAddr>,
46}
47
48impl ClientError {
49    /// Create a new [`ClientError`] using the given [`ErrorKind`] and [`Error`]
50    pub fn new<E>(kind: ErrorKind, error: Option<E>) -> Self
51    where
52        E: Into<BoxError>,
53    {
54        Self {
55            kind,
56            source: error.map(Into::into),
57            uri: None,
58            addr: None,
59        }
60    }
61
62    /// Set a [`Uri`] to the [`ClientError`].
63    #[inline]
64    pub fn set_url(&mut self, uri: Uri) {
65        self.uri = Some(Box::new(uri));
66    }
67
68    /// Set a [`SocketAddr`] to the [`ClientError`].
69    #[inline]
70    pub fn set_addr(&mut self, addr: SocketAddr) {
71        self.addr = Some(addr);
72    }
73
74    /// Consume current [`ClientError`] and return a new one with given [`Uri`].
75    #[inline]
76    pub fn with_url(mut self, uri: Uri) -> Self {
77        self.uri = Some(Box::new(uri));
78        self
79    }
80
81    /// Remove [`Uri`] from the [`ClientError`].
82    #[inline]
83    pub fn without_url(mut self) -> Self {
84        self.uri = None;
85        self
86    }
87
88    /// Consume current [`ClientError`] and return a new one with given [`SocketAddr`].
89    #[inline]
90    pub fn with_addr(mut self, addr: SocketAddr) -> Self {
91        self.addr = Some(addr);
92        self
93    }
94
95    /// Consume current [`ClientError`] and return a new one with [`SocketAddr`] from the
96    /// [`Address`] if exists.
97    #[inline]
98    pub fn with_address(mut self, address: Address) -> Self {
99        match address {
100            Address::Ip(addr) => self.addr = Some(addr),
101            #[cfg(target_family = "unix")]
102            Address::Unix(_) => {}
103        }
104        self
105    }
106
107    /// Consume current [`ClientError`] and return a new one with [`SocketAddr`] from the
108    /// [`Address`] if exists.
109    #[inline]
110    pub fn with_endpoint(mut self, ep: &Endpoint) -> Self {
111        if let Some(Address::Ip(addr)) = &ep.address {
112            self.addr = Some(*addr);
113        }
114        self
115    }
116
117    /// Get a reference to the [`ErrorKind`]
118    #[inline]
119    pub fn kind(&self) -> &ErrorKind {
120        &self.kind
121    }
122
123    /// Get a reference to the [`Uri`] if it exists
124    #[inline]
125    pub fn uri(&self) -> Option<&Uri> {
126        self.uri.as_deref()
127    }
128
129    /// Get a reference to the [`SocketAddr`] if it exists
130    #[inline]
131    pub fn addr(&self) -> Option<&SocketAddr> {
132        self.addr.as_ref()
133    }
134}
135
136impl fmt::Display for ClientError {
137    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
138        write!(f, "{}", self.kind)?;
139        if let Some(addr) = &self.addr {
140            write!(f, " to addr {addr}")?;
141        }
142        if let Some(uri) = &self.uri {
143            write!(f, " for uri `{uri}`")?;
144        }
145        if let Some(source) = &self.source {
146            write!(f, ": {source}")?;
147        }
148        Ok(())
149    }
150}
151
152impl Error for ClientError {
153    fn source(&self) -> Option<&(dyn Error + 'static)> {
154        Some(self.source.as_ref()?.as_ref())
155    }
156}
157
158/// Error kind of [`ClientError`]
159#[non_exhaustive]
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum ErrorKind {
162    /// Error occurs when building a client or a request
163    Builder,
164    /// Something wrong with the [`ClientContext`](crate::context::ClientContext)
165    Context,
166    /// Failed to connect to target server
167    Connect,
168    /// Fails to send a request to target server
169    Request,
170    /// Something wrong from [`LoadBalance`][LoadBalance] or [`Discover`][Discover]
171    ///
172    /// [LoadBalance]: volo::loadbalance::LoadBalance
173    /// [Discover]: volo::discovery::Discover
174    LoadBalance,
175    /// Something wrong when processing on [`Body`](crate::body::Body)
176    Body,
177    /// Other unrecognizable errors
178    Other,
179}
180
181/// Create a [`ClientError`] with [`ErrorKind::Builder`]
182pub fn builder_error<E>(error: E) -> ClientError
183where
184    E: Into<BoxError>,
185{
186    ClientError::new(ErrorKind::Builder, Some(error))
187}
188
189/// Create a [`ClientError`] with [`ErrorKind::Context`]
190pub fn connect_error<E>(error: E) -> ClientError
191where
192    E: Into<BoxError>,
193{
194    ClientError::new(ErrorKind::Connect, Some(error))
195}
196
197/// Create a [`ClientError`] with [`ErrorKind::Context`]
198pub fn context_error<E>(error: E) -> ClientError
199where
200    E: Into<BoxError>,
201{
202    ClientError::new(ErrorKind::Context, Some(error))
203}
204
205/// Create a [`ClientError`] with [`ErrorKind::Request`]
206pub fn request_error<E>(error: E) -> ClientError
207where
208    E: Into<BoxError>,
209{
210    ClientError::new(ErrorKind::Request, Some(error))
211}
212
213/// Create a [`ClientError`] with [`ErrorKind::LoadBalance`]
214pub fn lb_error<E>(error: E) -> ClientError
215where
216    E: Into<BoxError>,
217{
218    ClientError::new(ErrorKind::LoadBalance, Some(error))
219}
220
221/// Create a [`ClientError`] with [`ErrorKind::Body`]
222pub fn body_error<E>(error: E) -> ClientError
223where
224    E: Into<BoxError>,
225{
226    ClientError::new(ErrorKind::Body, Some(error))
227}
228
229/// Create a [`ClientError`] with [`ErrorKind::Other`]
230pub fn other_error<E>(error: E) -> ClientError
231where
232    E: Into<BoxError>,
233{
234    ClientError::new(ErrorKind::Other, Some(error))
235}
236
237impl From<BodyConvertError> for ClientError {
238    fn from(value: BodyConvertError) -> Self {
239        ClientError::new(ErrorKind::Body, Some(BoxError::from(value)))
240    }
241}
242
243impl std::fmt::Display for ErrorKind {
244    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
245        match self {
246            Self::Builder => f.write_str("builder error"),
247            Self::Context => f.write_str("processing context error"),
248            Self::Connect => f.write_str("connect error"),
249            Self::Request => f.write_str("sending request error"),
250            Self::LoadBalance => f.write_str("load balance error"),
251            Self::Body => f.write_str("processing body error"),
252            Self::Other => f.write_str("error"),
253        }
254    }
255}
256
257macro_rules! simple_error {
258    ($(#[$attr:meta])* $kind:ident => $name:ident => $msg:literal) => {
259        paste! {
260            #[doc = $kind " error \"" $msg "\""]
261            $(#[$attr])*
262            #[derive(Debug, PartialEq, Eq)]
263            pub struct $name;
264
265            $(#[$attr])*
266            impl ::std::fmt::Display for $name {
267                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
268                    f.write_str($msg)
269                }
270            }
271
272            $(#[$attr])*
273            impl ::std::error::Error for $name {}
274
275            $(#[$attr])*
276            pub(crate) fn [<$name:snake>]() -> ClientError {
277                ClientError::new(ErrorKind::$kind, Some($name))
278            }
279        }
280    };
281
282    ($(#[$attr:meta])* $kind:ident => $name:ident($inner:ty) => $msg:literal) => {
283        paste! {
284            #[doc = $kind " error \"" $msg "\""]
285            $(#[$attr])*
286            #[derive(Debug, PartialEq, Eq)]
287            pub struct $name($inner);
288
289            $(#[$attr])*
290            impl ::std::fmt::Display for $name {
291                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
292                    write!(f, concat!($msg, ": {}"), self.0)
293                }
294            }
295
296            $(#[$attr])*
297            impl ::std::error::Error for $name {}
298
299            $(#[$attr])*
300            pub(crate) fn [<$name:snake>](inner: $inner) -> ClientError {
301                ClientError::new(ErrorKind::$kind, Some($name(inner)))
302            }
303        }
304    };
305}
306
307simple_error!(Builder => NoAddress => "missing target address");
308simple_error!(Builder => BadScheme(::http::uri::Scheme) => "bad scheme");
309#[cfg(not(all(feature = "http1", feature = "http2")))]
310simple_error!(Builder => BadVersion => "bad http protocol version");
311simple_error!(Builder => BadHostName(::faststr::FastStr) => "bad host name");
312simple_error!(Builder => SchemeUnavailable => "scheme is unavailable in current target");
313simple_error!(Builder => PortUnavailable => "port is unavailable in current target");
314simple_error!(Connect => Retry => "retry");
315simple_error!(Request => Timeout => "request timeout");
316simple_error!(LoadBalance => NoAvailableEndpoint => "no available endpoint");
317
318#[cfg(test)]
319mod client_error_tests {
320    use std::error::Error;
321
322    use crate::error::client::{
323        BadHostName, BadScheme, NoAddress, NoAvailableEndpoint, Timeout, bad_host_name, bad_scheme,
324        no_address, no_available_endpoint, timeout,
325    };
326
327    #[test]
328    fn types_downcast() {
329        assert!(no_address().source().unwrap().is::<NoAddress>());
330        assert!(
331            bad_scheme(::http::uri::Scheme::HTTP)
332                .source()
333                .unwrap()
334                .is::<BadScheme>()
335        );
336        assert!(
337            bad_host_name(::faststr::FastStr::from_static_str("foo"))
338                .source()
339                .unwrap()
340                .is::<BadHostName>()
341        );
342        assert!(timeout().source().unwrap().is::<Timeout>());
343        assert!(
344            no_available_endpoint()
345                .source()
346                .unwrap()
347                .is::<NoAvailableEndpoint>()
348        );
349    }
350}