isahc/error.rs
1//! Types for error handling.
2
3use std::{error::Error as StdError, fmt, io, net::SocketAddr, sync::Arc};
4
5use http::Response;
6use once_cell::sync::OnceCell;
7
8use crate::ResponseExt;
9
10/// A non-exhaustive list of error types that can occur while sending an HTTP
11/// request or receiving an HTTP response.
12///
13/// These are meant to be treated as general error codes that allow you to
14/// handle different sorts of errors in different ways, but are not always
15/// specific. The list is also non-exhaustive, and more variants may be added in
16/// the future.
17#[derive(Clone, Debug, Eq, PartialEq)]
18#[non_exhaustive]
19pub enum ErrorKind {
20 /// A problem occurred with the local certificate.
21 BadClientCertificate,
22
23 /// The server certificate could not be validated.
24 BadServerCertificate,
25
26 /// The HTTP client failed to initialize.
27 ///
28 /// This error can occur when trying to create a client with invalid
29 /// configuration, if there were insufficient resources to create the
30 /// client, or if a system error occurred when trying to initialize an I/O
31 /// driver.
32 ClientInitialization,
33
34 /// Failed to connect to the server. This can occur if the server rejects
35 /// the request on the specified port.
36 ConnectionFailed,
37
38 /// The server either returned a response using an unknown or unsupported
39 /// encoding format, or the response encoding was malformed.
40 InvalidContentEncoding,
41
42 /// Provided authentication credentials were rejected by the server.
43 ///
44 /// This error is only returned when using Isahc's built-in authentication
45 /// methods. If using authentication headers manually, the server's response
46 /// will be returned as a success unaltered.
47 InvalidCredentials,
48
49 /// The request to be sent was invalid and could not be sent.
50 ///
51 /// Note that this is only returned for requests that the client deemed
52 /// invalid. If the request appears to be valid but is rejected by the
53 /// server, then the server's response will likely indicate as such.
54 InvalidRequest,
55
56 /// An I/O error either sending the request or reading the response. This
57 /// could be caused by a problem on the client machine, a problem on the
58 /// server machine, or a problem with the network between the two.
59 ///
60 /// You can get more details about the underlying I/O error with
61 /// [`Error::source`][std::error::Error::source].
62 Io,
63
64 /// Failed to resolve a host name.
65 ///
66 /// This could be caused by any number of problems, including failure to
67 /// reach a DNS server, misconfigured resolver configuration, or the
68 /// hostname simply does not exist.
69 NameResolution,
70
71 /// The server made an unrecoverable HTTP protocol violation. This indicates
72 /// a bug in the server. Retrying a request that returns this error is
73 /// likely to produce the same error.
74 ProtocolViolation,
75
76 /// Request processing could not continue because the client needed to
77 /// re-send the request body, but was unable to rewind the body stream to
78 /// the beginning in order to do so.
79 ///
80 /// If you need Isahc to be able to re-send the request body during a retry
81 /// or redirect then you must load the body into a contiguous memory buffer
82 /// first. Then you can create a rewindable body using
83 /// [`Body::from_bytes_static`][crate::Body::from_bytes_static] or
84 /// [`AsyncBody::from_bytes_static`][crate::AsyncBody::from_bytes_static].
85 RequestBodyNotRewindable,
86
87 /// A request or operation took longer than the configured timeout time.
88 Timeout,
89
90 /// An error ocurred in the secure socket engine.
91 TlsEngine,
92
93 /// Number of redirects hit the maximum configured amount.
94 TooManyRedirects,
95
96 /// An unknown error occurred. This likely indicates a problem in the HTTP
97 /// client or in a dependency, but the client was able to recover instead of
98 /// panicking. Subsequent requests will likely succeed.
99 ///
100 /// Only used internally.
101 #[doc(hidden)]
102 Unknown,
103}
104
105impl ErrorKind {
106 #[inline]
107 fn description(&self) -> Option<&str> {
108 match self {
109 Self::BadClientCertificate => Some("a problem occurred with the local certificate"),
110 Self::BadServerCertificate => Some("the server certificate could not be validated"),
111 Self::ClientInitialization => Some("failed to initialize client"),
112 Self::ConnectionFailed => Some("failed to connect to the server"),
113 Self::InvalidContentEncoding => Some(
114 "the server either returned a response using an unknown or unsupported encoding format, or the response encoding was malformed",
115 ),
116 Self::InvalidCredentials => {
117 Some("provided authentication credentials were rejected by the server")
118 }
119 Self::InvalidRequest => Some("invalid HTTP request"),
120 Self::NameResolution => Some("failed to resolve host name"),
121 Self::ProtocolViolation => {
122 Some("the server made an unrecoverable HTTP protocol violation")
123 }
124 Self::RequestBodyNotRewindable => {
125 Some("request body could not be re-sent because it is not rewindable")
126 }
127 Self::Timeout => {
128 Some("request or operation took longer than the configured timeout time")
129 }
130 Self::TlsEngine => Some("error ocurred in the secure socket engine"),
131 Self::TooManyRedirects => Some("number of redirects hit the maximum amount"),
132 _ => None,
133 }
134 }
135}
136
137impl fmt::Display for ErrorKind {
138 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
139 f.write_str(self.description().unwrap_or("unknown error"))
140 }
141}
142
143// Improve equality ergonomics for references.
144impl PartialEq<ErrorKind> for &'_ ErrorKind {
145 fn eq(&self, other: &ErrorKind) -> bool {
146 *self == other
147 }
148}
149
150/// An error encountered while sending an HTTP request or receiving an HTTP
151/// response.
152///
153/// Note that errors are typically caused by failed I/O or protocol errors. 4xx
154/// or 5xx responses successfully received from the server are generally _not_
155/// considered an error case.
156///
157/// This type is intentionally opaque, as sending an HTTP request involves many
158/// different moving parts, some of which can be platform or device-dependent.
159/// It is recommended that you use the [`kind`][Error::kind] method to get a
160/// more generalized classification of error types that this error could be if
161/// you need to handle different sorts of errors in different ways.
162///
163/// If you need to get more specific details about the reason for the error, you
164/// can use the [`source`][std::error::Error::source] method. We do not provide
165/// any stability guarantees about what error sources are returned.
166#[derive(Clone)]
167pub struct Error(Arc<Inner>);
168
169struct Inner {
170 kind: ErrorKind,
171 context: Option<String>,
172 source: Option<Box<dyn SourceError>>,
173 local_addr: OnceCell<SocketAddr>,
174 remote_addr: OnceCell<SocketAddr>,
175}
176
177impl Error {
178 /// Create a new error from a given error kind and source error.
179 pub(crate) fn new<E>(kind: ErrorKind, source: E) -> Self
180 where
181 E: StdError + Send + Sync + 'static,
182 {
183 Self::with_context(kind, None, source)
184 }
185
186 /// Create a new error from a given error kind, source error, and context
187 /// string.
188 pub(crate) fn with_context<E>(kind: ErrorKind, context: Option<String>, source: E) -> Self
189 where
190 E: StdError + Send + Sync + 'static,
191 {
192 Self(Arc::new(Inner {
193 kind,
194 context,
195 source: Some(Box::new(source)),
196 local_addr: OnceCell::new(),
197 remote_addr: OnceCell::new(),
198 }))
199 }
200
201 /// Create a new error from a given error kind and response.
202 pub(crate) fn with_response<B>(kind: ErrorKind, response: &Response<B>) -> Self {
203 let error = Self::from(kind);
204
205 if let Some(addr) = response.local_addr() {
206 let _ = error.0.local_addr.set(addr);
207 }
208
209 if let Some(addr) = response.remote_addr() {
210 let _ = error.0.remote_addr.set(addr);
211 }
212
213 error
214 }
215
216 /// Statically cast a given error into an Isahc error, converting if
217 /// necessary.
218 ///
219 /// This is useful for converting or creating errors from external types
220 /// without publicly implementing `From` over them and leaking them into our
221 /// API.
222 pub(crate) fn from_any<E>(error: E) -> Self
223 where
224 E: StdError + Send + Sync + 'static,
225 {
226 castaway::match_type!(error, {
227 Error as error => error,
228 std::io::Error as error => error.into(),
229 curl::Error as error => {
230 Self::with_context(
231 if error.is_ssl_certproblem() || error.is_ssl_cacert_badfile() {
232 ErrorKind::BadClientCertificate
233 } else if error.is_peer_failed_verification()
234 || error.is_ssl_cacert()
235 || error.is_ssl_cipher()
236 || error.is_ssl_issuer_error()
237 {
238 ErrorKind::BadServerCertificate
239 } else if error.is_interface_failed() {
240 ErrorKind::ClientInitialization
241 } else if error.is_couldnt_connect() || error.is_ssl_connect_error() {
242 ErrorKind::ConnectionFailed
243 } else if error.is_bad_content_encoding() || error.is_conv_failed() {
244 ErrorKind::InvalidContentEncoding
245 } else if error.is_login_denied() {
246 ErrorKind::InvalidCredentials
247 } else if error.is_url_malformed() {
248 ErrorKind::InvalidRequest
249 } else if error.is_couldnt_resolve_host() || error.is_couldnt_resolve_proxy() {
250 ErrorKind::NameResolution
251 } else if error.is_got_nothing()
252 || error.is_http2_error()
253 || error.is_http2_stream_error()
254 || error.is_unsupported_protocol()
255 || error.code() == curl_sys::CURLE_FTP_WEIRD_SERVER_REPLY
256 {
257 ErrorKind::ProtocolViolation
258 } else if error.is_send_error()
259 || error.is_recv_error()
260 || error.is_read_error()
261 || error.is_write_error()
262 || error.is_upload_failed()
263 || error.is_send_fail_rewind()
264 || error.is_aborted_by_callback()
265 || error.is_partial_file()
266 {
267 ErrorKind::Io
268 } else if error.is_ssl_engine_initfailed()
269 || error.is_ssl_engine_notfound()
270 || error.is_ssl_engine_setfailed()
271 {
272 ErrorKind::TlsEngine
273 } else if error.is_operation_timedout() {
274 ErrorKind::Timeout
275 } else if error.is_too_many_redirects() {
276 ErrorKind::TooManyRedirects
277 } else {
278 ErrorKind::Unknown
279 },
280 error.extra_description().map(String::from),
281 error,
282 )
283 },
284 curl::MultiError as error => {
285 Self::new(
286 if error.is_bad_socket() {
287 ErrorKind::Io
288 } else {
289 ErrorKind::Unknown
290 },
291 error,
292 )
293 },
294 error => Error::new(ErrorKind::Unknown, error),
295 })
296 }
297
298 /// Get the kind of error this represents.
299 ///
300 /// The kind returned may not be matchable against any documented variants
301 /// if the reason for the error is unknown. Unknown errors may be an
302 /// indication of a bug, or an error condition that we do not recognize
303 /// appropriately. Either way, please report such occurrences to us!
304 #[inline]
305 pub fn kind(&self) -> &ErrorKind {
306 &self.0.kind
307 }
308
309 /// Returns true if this error was likely caused by the client.
310 ///
311 /// Usually indicates that the client was misconfigured or used to send
312 /// invalid data to the server. Requests that return these sorts of errors
313 /// probably should not be retried without first fixing the request
314 /// parameters.
315 pub fn is_client(&self) -> bool {
316 match self.kind() {
317 ErrorKind::BadClientCertificate
318 | ErrorKind::ClientInitialization
319 | ErrorKind::InvalidCredentials
320 | ErrorKind::InvalidRequest
321 | ErrorKind::RequestBodyNotRewindable
322 | ErrorKind::TlsEngine => true,
323 _ => false,
324 }
325 }
326
327 /// Returns true if this is an error likely related to network failures.
328 ///
329 /// Network operations are inherently unreliable. Sometimes retrying the
330 /// request once or twice is enough to resolve the error.
331 pub fn is_network(&self) -> bool {
332 match self.kind() {
333 ErrorKind::ConnectionFailed | ErrorKind::Io | ErrorKind::NameResolution => true,
334 _ => false,
335 }
336 }
337
338 /// Returns true if this error was likely the fault of the server.
339 pub fn is_server(&self) -> bool {
340 match self.kind() {
341 ErrorKind::BadServerCertificate
342 | ErrorKind::ProtocolViolation
343 | ErrorKind::TooManyRedirects => true,
344 _ => false,
345 }
346 }
347
348 /// Returns true if this error is caused from exceeding a configured
349 /// timeout.
350 ///
351 /// A request could time out for any number of reasons, for example:
352 ///
353 /// - Slow or broken network preventing the server from receiving the
354 /// request or replying in a timely manner.
355 /// - The server received the request but is taking a long time to fulfill
356 /// the request.
357 ///
358 /// Sometimes retrying the request once or twice is enough to resolve the
359 /// error.
360 pub fn is_timeout(&self) -> bool {
361 self.kind() == ErrorKind::Timeout
362 }
363
364 /// Returns true if this error is related to SSL/TLS.
365 pub fn is_tls(&self) -> bool {
366 match self.kind() {
367 ErrorKind::BadClientCertificate
368 | ErrorKind::BadServerCertificate
369 | ErrorKind::TlsEngine => true,
370 _ => false,
371 }
372 }
373
374 /// Get the local socket address of the last-used connection involved in
375 /// this error, if known.
376 ///
377 /// If the request that caused this error failed to create a local socket
378 /// for connecting then this will return `None`.
379 pub fn local_addr(&self) -> Option<SocketAddr> {
380 self.0.local_addr.get().cloned()
381 }
382
383 /// Get the remote socket address of the last-used connection involved in
384 /// this error, if known.
385 ///
386 /// If the request that caused this error failed to connect to any server,
387 /// then this will return `None`.
388 pub fn remote_addr(&self) -> Option<SocketAddr> {
389 self.0.remote_addr.get().cloned()
390 }
391
392 pub(crate) fn with_local_addr(self, addr: SocketAddr) -> Self {
393 let _ = self.0.local_addr.set(addr);
394 self
395 }
396
397 pub(crate) fn with_remote_addr(self, addr: SocketAddr) -> Self {
398 let _ = self.0.remote_addr.set(addr);
399 self
400 }
401}
402
403impl StdError for Error {
404 fn source(&self) -> Option<&(dyn StdError + 'static)> {
405 self.0.source.as_ref().map(|source| source.as_dyn_error())
406 }
407}
408
409impl PartialEq<ErrorKind> for Error {
410 fn eq(&self, kind: &ErrorKind) -> bool {
411 self.kind().eq(kind)
412 }
413}
414
415impl fmt::Debug for Error {
416 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
417 f.debug_struct("Error")
418 .field("kind", &self.kind())
419 .field("context", &self.0.context)
420 .field("source", &self.source())
421 .field(
422 "source_type",
423 &self.0.source.as_ref().map(|e| e.type_name()),
424 )
425 .field("local_addr", &self.0.local_addr.get())
426 .field("remote_addr", &self.0.remote_addr.get())
427 .finish()
428 }
429}
430
431impl fmt::Display for Error {
432 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433 if let Some(s) = self.0.context.as_ref() {
434 write!(f, "{}: {}", self.kind(), s)
435 } else {
436 write!(f, "{}", self.kind())
437 }
438 }
439}
440
441impl From<ErrorKind> for Error {
442 fn from(kind: ErrorKind) -> Self {
443 Self(Arc::new(Inner {
444 kind,
445 context: None,
446 source: None,
447 local_addr: OnceCell::new(),
448 remote_addr: OnceCell::new(),
449 }))
450 }
451}
452
453impl From<io::Error> for Error {
454 fn from(error: io::Error) -> Self {
455 // If this I/O error is just a wrapped Isahc error, then unwrap it.
456 if let Some(inner) = error.get_ref() {
457 if inner.is::<Self>() {
458 return *error.into_inner().unwrap().downcast().unwrap();
459 }
460 }
461
462 Self::new(
463 match error.kind() {
464 io::ErrorKind::ConnectionRefused => ErrorKind::ConnectionFailed,
465 io::ErrorKind::TimedOut => ErrorKind::Timeout,
466 _ => ErrorKind::Io,
467 },
468 error,
469 )
470 }
471}
472
473impl From<Error> for io::Error {
474 fn from(error: Error) -> Self {
475 let kind = match error.kind() {
476 ErrorKind::ConnectionFailed => io::ErrorKind::ConnectionRefused,
477 ErrorKind::Timeout => io::ErrorKind::TimedOut,
478 _ => io::ErrorKind::Other,
479 };
480
481 Self::new(kind, error)
482 }
483}
484
485impl From<http::Error> for Error {
486 fn from(error: http::Error) -> Error {
487 Self::new(
488 if error.is::<http::header::InvalidHeaderName>()
489 || error.is::<http::header::InvalidHeaderValue>()
490 || error.is::<http::method::InvalidMethod>()
491 || error.is::<http::uri::InvalidUri>()
492 || error.is::<http::uri::InvalidUriParts>()
493 {
494 ErrorKind::InvalidRequest
495 } else {
496 ErrorKind::Unknown
497 },
498 error,
499 )
500 }
501}
502
503/// Internal trait object for source errors. This is used to capture additional
504/// methods about the source error value in the vtable.
505trait SourceError: StdError + Send + Sync + 'static {
506 /// Get the type name of the concrete error type when the parent error was
507 /// created. Used for enriching the debug formatting.
508 fn type_name(&self) -> &'static str;
509
510 /// Cast this error as a stdlib error trait object.
511 fn as_dyn_error(&self) -> &(dyn StdError + 'static);
512}
513
514impl<T: StdError + Send + Sync + 'static> SourceError for T {
515 fn type_name(&self) -> &'static str {
516 std::any::type_name::<Self>()
517 }
518
519 fn as_dyn_error(&self) -> &(dyn StdError + 'static) {
520 self
521 }
522}
523
524#[cfg(test)]
525mod tests {
526 use super::*;
527
528 static_assertions::assert_impl_all!(Error: Send, Sync);
529}