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 }
104 self
105 }
106
107 #[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 #[inline]
119 pub fn kind(&self) -> &ErrorKind {
120 &self.kind
121 }
122
123 #[inline]
125 pub fn uri(&self) -> Option<&Uri> {
126 self.uri.as_deref()
127 }
128
129 #[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#[non_exhaustive]
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub enum ErrorKind {
162 Builder,
164 Context,
166 Connect,
168 Request,
170 LoadBalance,
175 Body,
177 Other,
179}
180
181pub fn builder_error<E>(error: E) -> ClientError
183where
184 E: Into<BoxError>,
185{
186 ClientError::new(ErrorKind::Builder, Some(error))
187}
188
189pub fn connect_error<E>(error: E) -> ClientError
191where
192 E: Into<BoxError>,
193{
194 ClientError::new(ErrorKind::Connect, Some(error))
195}
196
197pub fn context_error<E>(error: E) -> ClientError
199where
200 E: Into<BoxError>,
201{
202 ClientError::new(ErrorKind::Context, Some(error))
203}
204
205pub fn request_error<E>(error: E) -> ClientError
207where
208 E: Into<BoxError>,
209{
210 ClientError::new(ErrorKind::Request, Some(error))
211}
212
213pub fn lb_error<E>(error: E) -> ClientError
215where
216 E: Into<BoxError>,
217{
218 ClientError::new(ErrorKind::LoadBalance, Some(error))
219}
220
221pub fn body_error<E>(error: E) -> ClientError
223where
224 E: Into<BoxError>,
225{
226 ClientError::new(ErrorKind::Body, Some(error))
227}
228
229pub 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}