Skip to main content

reqwest_rotate/
error.rs

1//! The error type returned by this crate.
2
3use thiserror::Error;
4
5/// Errors produced by [`RotatingClient`](crate::RotatingClient) and its
6/// builder.
7///
8/// HTTP status codes are never errors: after the last attempt the response
9/// is handed back as-is, whatever its status, exactly like `reqwest` does.
10/// Use [`Response::error_for_status`](reqwest::Response::error_for_status)
11/// if you want a non-2xx status to become an error.
12///
13/// Where a variant wraps a `reqwest::Error`, that error is this one's
14/// [`source`](std::error::Error::source); [`Reqwest`](Error::Reqwest) and
15/// [`Build`](Error::Build) do not repeat it in their own message.
16/// [`InvalidProxy`](Error::InvalidProxy) is the exception: its message
17/// includes the reason for readability even though the same text is also
18/// reachable through `source`.
19///
20/// Marked `#[non_exhaustive]`: new variants may be added in a minor release
21/// without that counting as a breaking change.
22#[derive(Debug, Error)]
23#[non_exhaustive]
24pub enum Error {
25    /// A request failed at the `reqwest` layer (network error, timeout,
26    /// invalid URL, ...). Returned directly for failures that are not
27    /// retried, and as the last attempt's failure once retries are used up
28    /// on a transient transport error.
29    #[error("request failed")]
30    Reqwest(#[from] reqwest::Error),
31
32    /// A proxy URL is blank, cannot be parsed, or uses a scheme this build
33    /// does not support (SOCKS schemes need the `socks` feature); or a
34    /// syntactically valid proxy was rejected when the underlying
35    /// `reqwest::Client` was built.
36    ///
37    /// [`source`](std::error::Error::source) carries the reason: either a
38    /// plain-text explanation with no cause of its own, or the
39    /// `reqwest::Error` from the failed client build.
40    #[error("invalid proxy: {proxy}: {source}")]
41    InvalidProxy {
42        /// The proxy URL as far as it could be read, or the caller's own
43        /// spelling if it could not be parsed at all. Credentials are
44        /// always redacted.
45        proxy: String,
46        /// Why the proxy was rejected.
47        #[source]
48        source: Box<dyn std::error::Error + Send + Sync + 'static>,
49    },
50
51    /// The underlying `reqwest::Client` could not be built: an invalid TLS
52    /// or proxy configuration, usually.
53    #[error("failed to build the underlying client")]
54    Build(#[source] reqwest::Error),
55}
56
57impl Error {
58    /// Builds [`Error::InvalidProxy`] from a redacted proxy string and a
59    /// plain-text reason, for a validation failure that has no underlying
60    /// error of its own.
61    pub(crate) fn invalid_proxy(proxy: impl Into<String>, reason: impl Into<String>) -> Self {
62        Error::InvalidProxy {
63            proxy: proxy.into(),
64            source: Box::new(ProxyReason(reason.into())),
65        }
66    }
67}
68
69/// A plain-text reason for rejecting a proxy URL, with no underlying cause
70/// of its own.
71#[derive(Debug)]
72struct ProxyReason(String);
73
74impl std::fmt::Display for ProxyReason {
75    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
76        f.write_str(&self.0)
77    }
78}
79
80impl std::error::Error for ProxyReason {}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn build_error_keeps_its_source() {
88        let inner = reqwest::Proxy::all("http://[").unwrap_err();
89        let err = Error::Build(inner);
90        assert!(std::error::Error::source(&err).is_some());
91        assert_eq!(err.to_string(), "failed to build the underlying client");
92    }
93
94    #[test]
95    fn reqwest_display_is_bare() {
96        let inner = reqwest::Proxy::all("http://[").unwrap_err();
97        let err = Error::from(inner);
98        assert!(std::error::Error::source(&err).is_some());
99        assert_eq!(err.to_string(), "request failed");
100    }
101
102    #[test]
103    fn invalid_proxy_keeps_its_source() {
104        let err = Error::invalid_proxy("http://proxy.example", "not a valid proxy URL");
105        assert!(std::error::Error::source(&err).is_some());
106        assert_eq!(
107            err.to_string(),
108            "invalid proxy: http://proxy.example: not a valid proxy URL"
109        );
110    }
111}