1use std::{fmt, io, net::SocketAddr, sync::Arc};
5
6#[derive(Clone, Debug)]
10pub enum Error {
11 Connect {
13 address: SocketAddr,
14 source: Arc<io::Error>,
15 },
16 Negotiation(base_http_protocol::NegotiationError),
17 Closed,
18 Busy,
19 Cancelled,
20 Abandoned,
21 InvalidOptions,
22 Unsupported(&'static str),
23 Pool(base_pool::Error),
24 Protocol(Arc<base_http1::Error>),
25 Io(Arc<io::Error>),
26 Body(base_http::body::BodyError),
27}
28impl fmt::Display for Error {
29 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
30 match self {
31 Self::Connect { address, .. } => write!(f, "connecting to {address} failed"),
32 Self::Negotiation(error) => error.fmt(f),
33 Self::Closed => f.write_str("HTTP client is closed"),
34 Self::Busy => f.write_str("HTTP client request limit reached"),
35 Self::Cancelled => f.write_str("HTTP client request cancelled"),
36 Self::Abandoned => f.write_str("HTTP client response abandoned"),
37 Self::InvalidOptions => f.write_str("invalid HTTP client limits"),
38 Self::Unsupported(reason) => write!(f, "unsupported HTTP client operation: {reason}"),
39 Self::Pool(error) => error.fmt(f),
40 Self::Protocol(error) => error.fmt(f),
41 Self::Io(_) => f.write_str("HTTP client I/O failed"),
42 Self::Body(error) => error.fmt(f),
43 }
44 }
45}
46impl std::error::Error for Error {
47 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
48 match self {
49 Self::Connect { source, .. } => Some(source.as_ref()),
50 Self::Negotiation(error) => error.source(),
51 Self::Pool(error) => error.source(),
52 Self::Protocol(error) => error.source(),
53 Self::Io(error) => Some(error.as_ref()),
54 Self::Body(error) => error.source(),
55 _ => None,
56 }
57 }
58}
59impl Error {
60 pub(crate) fn connect(address: SocketAddr, error: base_scheduler::OperationError) -> Self {
62 match error {
63 base_scheduler::OperationError::Cancelled(_) => Self::Cancelled,
64 base_scheduler::OperationError::Io(error) => Self::Connect {
65 address,
66 source: Arc::new(error),
67 },
68 }
69 }
70}
71
72impl Error {
73 pub(crate) fn body<E: std::error::Error + Send + Sync + 'static>(error: E) -> Self {
75 let mut error = Some(error);
76 if let Some(Some(Self::Body(body))) =
77 (&mut error as &mut dyn std::any::Any).downcast_mut::<Option<Self>>()
78 {
79 return Self::Body(body.clone());
80 }
81 Self::Body(base_http::body::BodyError::new(error.unwrap()))
82 }
83}
84
85impl From<base_pool::Error> for Error {
86 fn from(error: base_pool::Error) -> Self {
87 match error {
88 base_pool::Error::Cancelled => Self::Cancelled,
89 base_pool::Error::Closed => Self::Closed,
90 error => Self::Pool(error),
91 }
92 }
93}
94impl From<base_http1::Error> for Error {
95 fn from(error: base_http1::Error) -> Self {
96 match error {
97 base_http1::Error::Cancelled => Self::Cancelled,
98 base_http1::Error::Body(error) => match error.downcast::<Self>() {
100 Ok(error) => *error,
101 Err(error) => match error.downcast::<base_http::InterimError<Self>>() {
102 Ok(error) => match *error {
103 base_http::InterimError::Handler(error) => error,
104 error => Self::Protocol(Arc::new(base_http1::Error::Body(Box::new(error)))),
105 },
106 Err(error) => Self::Protocol(Arc::new(base_http1::Error::Body(error))),
107 },
108 },
109 error => Self::Protocol(Arc::new(error)),
110 }
111 }
112}
113impl From<io::Error> for Error {
114 fn from(error: io::Error) -> Self {
115 Self::Io(Arc::new(error))
116 }
117}
118impl From<base_scheduler::OperationError> for Error {
119 fn from(error: base_scheduler::OperationError) -> Self {
120 base_http1::Error::from(error).into()
121 }
122}
123impl From<base_http::body::BodyError> for Error {
124 fn from(error: base_http::body::BodyError) -> Self {
125 Self::Body(error)
126 }
127}
128
129impl From<base_http_protocol::NegotiationError> for Error {
130 fn from(error: base_http_protocol::NegotiationError) -> Self {
131 Self::Negotiation(error)
132 }
133}
134
135#[cfg(test)]
136mod tests {
137 use super::*;
138
139 fn chain(error: &dyn std::error::Error) -> Vec<String> {
140 let mut messages = vec![error.to_string()];
141 let mut source = error.source();
142 while let Some(error) = source {
143 messages.push(error.to_string());
144 source = error.source();
145 }
146 messages
147 }
148
149 fn refused() -> io::Error {
150 io::Error::from(io::ErrorKind::ConnectionRefused)
151 }
152
153 #[test]
154 fn cause_chains_never_repeat_a_message() {
155 let errors = [
156 Error::from(base_http1::Error::Io(refused())),
157 Error::from(refused()),
158 Error::from(base_http::body::BodyError::new(refused())),
159 Error::from(base_http::body::BodyError::new(base_http1::Error::Io(
160 refused(),
161 ))),
162 Error::from(base_http::body::BodyError::new(
164 base_http::body::BodyError::new(refused()),
165 )),
166 Error::from(base_http1::Error::Body(Box::new(
168 base_http::InterimError::Handler(Error::from(base_http::body::BodyError::new(
169 refused(),
170 ))),
171 ))),
172 Error::body(Error::body(refused())),
174 Error::Pool(base_pool::Error::WaitersFull),
175 Error::connect("127.0.0.1:9".parse().unwrap(), refused().into()),
176 Error::from(base_http_protocol::NegotiationError::Missing),
177 ];
178 let repeating: Vec<_> = errors
179 .iter()
180 .map(|error| chain(error))
181 .filter(|messages| {
182 let unique: std::collections::HashSet<_> = messages.iter().collect();
183 unique.len() != messages.len()
184 })
185 .collect();
186 assert_eq!(repeating, Vec::<Vec<String>>::new());
187 }
188
189 #[test]
190 fn connect_errors_name_the_address() {
191 let error = Error::connect("10.0.0.5:9292".parse().unwrap(), refused().into());
192 assert_eq!(
193 chain(&error),
194 ["connecting to 10.0.0.5:9292 failed", "connection refused"]
195 );
196 let cancelled = base_scheduler::OperationError::Cancelled(base_scheduler::Cancelled);
197 let error = Error::connect("10.0.0.5:9292".parse().unwrap(), cancelled);
198 assert_eq!(chain(&error), ["HTTP client request cancelled"]);
199 }
200
201 #[test]
202 fn codec_body_failures_keep_the_client_classification() {
203 let upload = base_http1::Error::Body(Box::new(Error::body(refused())));
204 assert_eq!(
205 chain(&Error::from(upload)),
206 ["body failed", "connection refused"]
207 );
208 let observer = base_http1::Error::Body(Box::new(base_http::InterimError::Handler(
209 Error::body(refused()),
210 )));
211 assert_eq!(
212 chain(&Error::from(observer)),
213 ["body failed", "connection refused"]
214 );
215 let finished =
216 base_http1::Error::Body(Box::new(base_http::InterimError::<Error>::Finished));
217 assert_eq!(
218 chain(&Error::from(finished)),
219 ["HTTP/1 body failed", "interim responses have finished"]
220 );
221 let other = base_http1::Error::Body(Box::new(refused()));
222 assert_eq!(
223 chain(&Error::from(other)),
224 ["HTTP/1 body failed", "connection refused"]
225 );
226 }
227
228 #[test]
229 fn protocol_errors_are_transparent() {
230 let error = Error::from(base_http1::Error::Io(refused()));
231 assert_eq!(
232 chain(&error),
233 ["HTTP/1 connection I/O failed", "connection refused"]
234 );
235 }
236}