1use 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
12pub 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#[derive(Debug)]
36pub struct ClientError {
37 kind: ErrorKind,
38 source: Option<BoxError>,
39 uri: Option<Box<Uri>>,
45 addr: Option<SocketAddr>,
46}
47
48impl ClientError {
49 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 #[inline]
64 pub fn set_url(&mut self, uri: Uri) {
65 self.uri = Some(Box::new(uri));
66 }
67
68 #[inline]
70 pub fn set_addr(&mut self, addr: SocketAddr) {
71 self.addr = Some(addr);
72 }
73
74 #[inline]
76 pub fn with_url(mut self, uri: Uri) -> Self {
77 self.uri = Some(Box::new(uri));
78 self
79 }
80
81 #[inline]
83 pub fn without_url(mut self) -> Self {
84 self.uri = None;
85 self
86 }
87
88 #[inline]
90 pub fn with_addr(mut self, addr: SocketAddr) -> Self {
91 self.addr = Some(addr);
92 self
93 }
94
95 #[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 #[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 #[inline]
121 pub fn kind(&self) -> &ErrorKind {
122 &self.kind
123 }
124
125 #[inline]
127 pub fn uri(&self) -> Option<&Uri> {
128 self.uri.as_deref()
129 }
130
131 #[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#[non_exhaustive]
162#[derive(Clone, Copy, Debug, Eq, PartialEq)]
163pub enum ErrorKind {
164 Builder,
166 Context,
168 Connect,
170 Request,
172 LoadBalance,
177 Body,
179 Other,
181}
182
183pub fn builder_error<E>(error: E) -> ClientError
185where
186 E: Into<BoxError>,
187{
188 ClientError::new(ErrorKind::Builder, Some(error))
189}
190
191pub fn connect_error<E>(error: E) -> ClientError
193where
194 E: Into<BoxError>,
195{
196 ClientError::new(ErrorKind::Connect, Some(error))
197}
198
199pub fn context_error<E>(error: E) -> ClientError
201where
202 E: Into<BoxError>,
203{
204 ClientError::new(ErrorKind::Context, Some(error))
205}
206
207pub fn request_error<E>(error: E) -> ClientError
209where
210 E: Into<BoxError>,
211{
212 ClientError::new(ErrorKind::Request, Some(error))
213}
214
215pub fn lb_error<E>(error: E) -> ClientError
217where
218 E: Into<BoxError>,
219{
220 ClientError::new(ErrorKind::LoadBalance, Some(error))
221}
222
223pub fn body_error<E>(error: E) -> ClientError
225where
226 E: Into<BoxError>,
227{
228 ClientError::new(ErrorKind::Body, Some(error))
229}
230
231pub 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}