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}