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            #[allow(unreachable_patterns)]
104            _ => unimplemented!("unsupported type of address"),
105        }
106        self
107    }
108
109    /// Consume current [`ClientError`] and return a new one with [`SocketAddr`] from the
110    /// [`Address`] if exists.
111    #[inline]
112    pub fn with_endpoint(mut self, ep: &Endpoint) -> Self {
113        if let Some(Address::Ip(addr)) = &ep.address {
114            self.addr = Some(*addr);
115        }
116        self
117    }
118
119    /// Get a reference to the [`ErrorKind`]
120    #[inline]
121    pub fn kind(&self) -> &ErrorKind {
122        &self.kind
123    }
124
125    /// Get a reference to the [`Uri`] if it exists
126    #[inline]
127    pub fn uri(&self) -> Option<&Uri> {
128        self.uri.as_deref()
129    }
130
131    /// Get a reference to the [`SocketAddr`] if it exists
132    #[inline]
133    pub fn addr(&self) -> Option<&SocketAddr> {
134        self.addr.as_ref()
135    }
136}
137
138impl fmt::Display for ClientError {
139    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140        write!(f, "{}", self.kind)?;
141        if let Some(addr) = &self.addr {
142            write!(f, " to addr {addr}")?;
143        }
144        if let Some(uri) = &self.uri {
145            write!(f, " for uri `{uri}`")?;
146        }
147        if let Some(source) = &self.source {
148            write!(f, ": {source}")?;
149        }
150        Ok(())
151    }
152}
153
154impl Error for ClientError {
155    fn source(&self) -> Option<&(dyn Error + 'static)> {
156        Some(self.source.as_ref()?.as_ref())
157    }
158}
159
160/// Error kind of [`ClientError`]
161#[non_exhaustive]
162#[derive(Clone, Copy, Debug, Eq, PartialEq)]
163pub enum ErrorKind {
164    /// Error occurs when building a client or a request
165    Builder,
166    /// Something wrong with the [`ClientContext`](crate::context::ClientContext)
167    Context,
168    /// Failed to connect to target server
169    Connect,
170    /// Fails to send a request to target server
171    Request,
172    /// Something wrong from [`LoadBalance`][LoadBalance] or [`Discover`][Discover]
173    ///
174    /// [LoadBalance]: volo::loadbalance::LoadBalance
175    /// [Discover]: volo::discovery::Discover
176    LoadBalance,
177    /// Something wrong when processing on [`Body`](crate::body::Body)
178    Body,
179    /// Other unrecognizable errors
180    Other,
181}
182
183/// Create a [`ClientError`] with [`ErrorKind::Builder`]
184pub fn builder_error<E>(error: E) -> ClientError
185where
186    E: Into<BoxError>,
187{
188    ClientError::new(ErrorKind::Builder, Some(error))
189}
190
191/// Create a [`ClientError`] with [`ErrorKind::Context`]
192pub fn connect_error<E>(error: E) -> ClientError
193where
194    E: Into<BoxError>,
195{
196    ClientError::new(ErrorKind::Connect, Some(error))
197}
198
199/// Create a [`ClientError`] with [`ErrorKind::Context`]
200pub fn context_error<E>(error: E) -> ClientError
201where
202    E: Into<BoxError>,
203{
204    ClientError::new(ErrorKind::Context, Some(error))
205}
206
207/// Create a [`ClientError`] with [`ErrorKind::Request`]
208pub fn request_error<E>(error: E) -> ClientError
209where
210    E: Into<BoxError>,
211{
212    ClientError::new(ErrorKind::Request, Some(error))
213}
214
215/// Create a [`ClientError`] with [`ErrorKind::LoadBalance`]
216pub fn lb_error<E>(error: E) -> ClientError
217where
218    E: Into<BoxError>,
219{
220    ClientError::new(ErrorKind::LoadBalance, Some(error))
221}
222
223/// Create a [`ClientError`] with [`ErrorKind::Body`]
224pub fn body_error<E>(error: E) -> ClientError
225where
226    E: Into<BoxError>,
227{
228    ClientError::new(ErrorKind::Body, Some(error))
229}
230
231/// Create a [`ClientError`] with [`ErrorKind::Other`]
232pub fn other_error<E>(error: E) -> ClientError
233where
234    E: Into<BoxError>,
235{
236    ClientError::new(ErrorKind::Other, Some(error))
237}
238
239impl From<BodyConvertError> for ClientError {
240    fn from(value: BodyConvertError) -> Self {
241        ClientError::new(ErrorKind::Body, Some(BoxError::from(value)))
242    }
243}
244
245impl std::fmt::Display for ErrorKind {
246    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247        match self {
248            Self::Builder => f.write_str("builder error"),
249            Self::Context => f.write_str("processing context error"),
250            Self::Connect => f.write_str("connect error"),
251            Self::Request => f.write_str("sending request error"),
252            Self::LoadBalance => f.write_str("load balance error"),
253            Self::Body => f.write_str("processing body error"),
254            Self::Other => f.write_str("error"),
255        }
256    }
257}
258
259macro_rules! simple_error {
260    ($(#[$attr:meta])* $kind:ident => $name:ident => $msg:literal) => {
261        paste! {
262            #[doc = $kind " error \"" $msg "\""]
263            $(#[$attr])*
264            #[derive(Debug, PartialEq, Eq)]
265            pub struct $name;
266
267            $(#[$attr])*
268            impl ::std::fmt::Display for $name {
269                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
270                    f.write_str($msg)
271                }
272            }
273
274            $(#[$attr])*
275            impl ::std::error::Error for $name {}
276
277            $(#[$attr])*
278            pub(crate) fn [<$name:snake>]() -> ClientError {
279                ClientError::new(ErrorKind::$kind, Some($name))
280            }
281        }
282    };
283
284    ($(#[$attr:meta])* $kind:ident => $name:ident($inner:ty) => $msg:literal) => {
285        paste! {
286            #[doc = $kind " error \"" $msg "\""]
287            $(#[$attr])*
288            #[derive(Debug, PartialEq, Eq)]
289            pub struct $name($inner);
290
291            $(#[$attr])*
292            impl ::std::fmt::Display for $name {
293                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
294                    write!(f, concat!($msg, ": {}"), self.0)
295                }
296            }
297
298            $(#[$attr])*
299            impl ::std::error::Error for $name {}
300
301            $(#[$attr])*
302            pub(crate) fn [<$name:snake>](inner: $inner) -> ClientError {
303                ClientError::new(ErrorKind::$kind, Some($name(inner)))
304            }
305        }
306    };
307}
308
309simple_error!(Builder => NoAddress => "missing target address");
310simple_error!(Builder => BadScheme(::http::uri::Scheme) => "bad scheme");
311#[cfg(not(all(feature = "http1", feature = "http2")))]
312simple_error!(Builder => BadVersion => "bad http protocol version");
313simple_error!(Builder => BadHostName(::faststr::FastStr) => "bad host name");
314simple_error!(Builder => SchemeUnavailable => "scheme is unavailable in current target");
315simple_error!(Builder => PortUnavailable => "port is unavailable in current target");
316simple_error!(Connect => Retry => "retry");
317simple_error!(Request => Timeout => "request timeout");
318simple_error!(LoadBalance => NoAvailableEndpoint => "no available endpoint");
319
320#[cfg(test)]
321mod client_error_tests {
322    use std::error::Error;
323
324    use crate::error::client::{
325        BadHostName, BadScheme, NoAddress, NoAvailableEndpoint, Timeout, bad_host_name, bad_scheme,
326        no_address, no_available_endpoint, timeout,
327    };
328
329    #[test]
330    fn types_downcast() {
331        assert!(no_address().source().unwrap().is::<NoAddress>());
332        assert!(
333            bad_scheme(::http::uri::Scheme::HTTP)
334                .source()
335                .unwrap()
336                .is::<BadScheme>()
337        );
338        assert!(
339            bad_host_name(::faststr::FastStr::from_static_str("foo"))
340                .source()
341                .unwrap()
342                .is::<BadHostName>()
343        );
344        assert!(timeout().source().unwrap().is::<Timeout>());
345        assert!(
346            no_available_endpoint()
347                .source()
348                .unwrap()
349                .is::<NoAvailableEndpoint>()
350        );
351    }
352}