ureq/error.rs
1use std::{fmt, io};
2
3use crate::http;
4use crate::Timeout;
5
6/// Errors from ureq.
7#[derive(Debug)]
8#[non_exhaustive]
9pub enum Error {
10 /// When [`http_status_as_error()`](crate::config::ConfigBuilder::http_status_as_error) is true,
11 /// 4xx and 5xx response status codes are translated to this error.
12 ///
13 /// This is the default behavior.
14 StatusCode(u16),
15
16 /// Errors arising from the http-crate.
17 ///
18 /// These errors happen for things like invalid characters in header names.
19 Http(http::Error),
20
21 /// Error if the URI is missing scheme or host.
22 BadUri(String),
23
24 /// An HTTP/1.1 protocol error.
25 ///
26 /// This can happen if the remote server ends incorrect HTTP data like
27 /// missing version or invalid chunked transfer.
28 Protocol(ureq_proto::Error),
29
30 /// Error in io such as the TCP socket.
31 Io(io::Error),
32
33 /// Error raised if the request hits any configured timeout.
34 ///
35 /// By default no timeouts are set, which means this error can't happen.
36 Timeout(Timeout),
37
38 /// Error when resolving a hostname fails.
39 HostNotFound,
40
41 /// A redirect failed.
42 ///
43 /// This happens when ureq encounters a redirect when sending a request body
44 /// such as a POST request, and receives a 307/308 response. ureq refuses to
45 /// redirect the POST body and instead raises this error.
46 RedirectFailed,
47
48 /// Error when creating proxy settings.
49 InvalidProxyUrl,
50
51 /// A connection failed.
52 ///
53 /// This is a fallback error when there is no other explanation as to
54 /// why a connector chain didn't produce a connection. The idea is that the connector
55 /// chain would return some other [`Error`] rather than rely om this value.
56 ///
57 /// Typically bespoke connector chains should, as far as possible, map their underlying
58 /// errors to [`Error::Io`] and use the [`io::ErrorKind`] to provide a reason.
59 ///
60 /// A bespoke chain is allowed to map to this value, but that provides very little
61 /// information to the user as to why the connection failed. One way to mitigate that
62 /// would be to rely on the `log` crate to provide additional information.
63 ConnectionFailed,
64
65 /// A send body (Such as `&str`) is larger than the `content-length` header.
66 BodyExceedsLimit(u64),
67
68 /// Too many redirects.
69 ///
70 /// The error can be turned off by setting
71 /// [`max_redirects_will_error()`](crate::config::ConfigBuilder::max_redirects_will_error)
72 /// to false. When turned off, the last response will be returned instead of causing
73 /// an error, even if it is a redirect.
74 ///
75 /// The number of redirects is limited to 10 by default.
76 TooManyRedirects,
77
78 /// Some error with TLS.
79 #[cfg(feature = "_tls")]
80 Tls(&'static str),
81
82 /// Error in reading PEM certificates/private keys.
83 ///
84 /// *Note:* The wrapped error struct is not considered part of ureq API.
85 /// Breaking changes in that struct will not be reflected in ureq
86 /// major versions.
87 #[cfg(feature = "_tls")]
88 Pem(rustls_pemfile::Error),
89
90 /// An error originating in Rustls.
91 ///
92 /// *Note:* The wrapped error struct is not considered part of ureq API.
93 /// Breaking changes in that struct will not be reflected in ureq
94 /// major versions.
95 #[cfg(feature = "_rustls")]
96 Rustls(rustls::Error),
97
98 /// An error originating in Native-TLS.
99 ///
100 /// *Note:* The wrapped error struct is not considered part of ureq API.
101 /// Breaking changes in that struct will not be reflected in ureq
102 /// major versions.
103 #[cfg(feature = "native-tls")]
104 NativeTls(native_tls::Error),
105
106 /// An error providing DER encoded certificates or private keys to Native-TLS.
107 ///
108 /// *Note:* The wrapped error struct is not considered part of ureq API.
109 /// Breaking changes in that struct will not be reflected in ureq
110 /// major versions.
111 #[cfg(feature = "native-tls")]
112 Der(der::Error),
113
114 /// An error with the cookies.
115 ///
116 /// *Note:* The wrapped error struct is not considered part of ureq API.
117 /// Breaking changes in that struct will not be reflected in ureq
118 /// major versions.
119 #[cfg(feature = "cookies")]
120 Cookie(cookie_store::CookieError),
121
122 /// An error parsing a cookie value.
123 #[cfg(feature = "cookies")]
124 CookieValue(&'static str),
125
126 /// An error in the cookie store.
127 ///
128 /// *Note:* The wrapped error struct is not considered part of ureq API.
129 /// Breaking changes in that struct will not be reflected in ureq
130 /// major versions.
131 #[cfg(feature = "cookies")]
132 CookieJar(cookie_store::Error),
133
134 /// An unrecognised character set.
135 #[cfg(feature = "charset")]
136 UnknownCharset(String),
137
138 /// The setting [`https_only`](crate::config::ConfigBuilder::https_only) is true and
139 /// the URI is not https.
140 RequireHttpsOnly(String),
141
142 /// The response header, from status up until body, is too big.
143 LargeResponseHeader(usize, usize),
144
145 /// Body decompression failed (gzip or brotli).
146 #[cfg(any(feature = "gzip", feature = "brotli"))]
147 Decompress(&'static str, io::Error),
148
149 /// Serde JSON error.
150 #[cfg(feature = "json")]
151 Json(serde_json::Error),
152
153 /// Attempt to connect to a CONNECT proxy failed.
154 ConnectProxyFailed(String),
155
156 /// The protocol requires TLS (https), but the connector did not
157 /// create a TLS secured transport.
158 ///
159 /// This typically indicates a fault in bespoke `Connector` chains.
160 TlsRequired,
161
162 /// Some other error occured.
163 ///
164 /// This is an escape hatch for bespoke connector chains having errors that don't naturally
165 /// map to any other error. For connector chains we recommend:
166 ///
167 /// 1. Map to [`Error::Io`] as far as possible.
168 /// 2. Map to other [`Error`] where reasonable.
169 /// 3. Fall back on [`Error::Other`].
170 /// 4. As a last resort [`Error::ConnectionFailed`].
171 ///
172 /// ureq does not produce this error using the default connectors.
173 Other(Box<dyn std::error::Error + Send + Sync>),
174
175 /// ureq-proto made no progress and there is no more input to read.
176 ///
177 /// We should never see this value.
178 #[doc(hidden)]
179 BodyStalled,
180}
181
182impl std::error::Error for Error {}
183
184impl Error {
185 /// Convert the error into a [`std::io::Error`].
186 ///
187 /// If the error is [`Error::Io`], we unpack the error. In othe cases we make
188 /// an `std::io::ErrorKind::Other`.
189 pub fn into_io(self) -> io::Error {
190 if let Self::Io(e) = self {
191 e
192 } else {
193 io::Error::new(io::ErrorKind::Other, self)
194 }
195 }
196
197 pub(crate) fn disconnected() -> Error {
198 io::Error::new(io::ErrorKind::UnexpectedEof, "Peer disconnected").into()
199 }
200}
201
202pub(crate) fn is_wrapped_ureq_error(e: &io::Error) -> bool {
203 e.get_ref().map(|x| x.is::<Error>()).unwrap_or(false)
204}
205
206impl From<io::Error> for Error {
207 fn from(e: io::Error) -> Self {
208 if is_wrapped_ureq_error(&e) {
209 // unwraps are ok, see above.
210 let boxed = e.into_inner().unwrap();
211 let ureq = boxed.downcast::<Error>().unwrap();
212 *ureq
213 } else {
214 Error::Io(e)
215 }
216 }
217}
218
219impl fmt::Display for Error {
220 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
221 match self {
222 Error::StatusCode(v) => write!(f, "http status: {}", v),
223 Error::Http(v) => write!(f, "http: {}", v),
224 Error::BadUri(v) => write!(f, "bad uri: {}", v),
225 Error::Protocol(v) => write!(f, "protocol: {}", v),
226 Error::Io(v) => write!(f, "io: {}", v),
227 Error::Timeout(v) => write!(f, "timeout: {}", v),
228 Error::HostNotFound => write!(f, "host not found"),
229 Error::RedirectFailed => write!(f, "redirect failed"),
230 Error::InvalidProxyUrl => write!(f, "invalid proxy url"),
231 Error::ConnectionFailed => write!(f, "connection failed"),
232 Error::BodyExceedsLimit(v) => {
233 write!(f, "the response body is larger than request limit: {}", v)
234 }
235 Error::TooManyRedirects => write!(f, "too many redirects"),
236 #[cfg(feature = "_tls")]
237 Error::Tls(v) => write!(f, "{}", v),
238 #[cfg(feature = "_tls")]
239 Error::Pem(v) => write!(f, "PEM: {:?}", v),
240 #[cfg(feature = "_rustls")]
241 Error::Rustls(v) => write!(f, "rustls: {}", v),
242 #[cfg(feature = "native-tls")]
243 Error::NativeTls(v) => write!(f, "native-tls: {}", v),
244 #[cfg(feature = "native-tls")]
245 Error::Der(v) => write!(f, "der: {}", v),
246 #[cfg(feature = "cookies")]
247 Error::Cookie(v) => write!(f, "cookie: {}", v),
248 #[cfg(feature = "cookies")]
249 Error::CookieValue(v) => write!(f, "{}", v),
250 #[cfg(feature = "cookies")]
251 Error::CookieJar(v) => write!(f, "cookie: {}", v),
252 #[cfg(feature = "charset")]
253 Error::UnknownCharset(v) => write!(f, "unknown character set: {}", v),
254 Error::RequireHttpsOnly(v) => write!(f, "configured for https only: {}", v),
255 Error::LargeResponseHeader(x, y) => {
256 write!(f, "response header is too big: {} > {}", x, y)
257 }
258 #[cfg(any(feature = "gzip", feature = "brotli"))]
259 Error::Decompress(x, y) => write!(f, "{} decompression failed: {}", x, y),
260 #[cfg(feature = "json")]
261 Error::Json(v) => write!(f, "json: {}", v),
262 Error::ConnectProxyFailed(v) => write!(f, "CONNECT proxy failed: {}", v),
263 Error::TlsRequired => write!(f, "TLS required, but transport is unsecured"),
264 Error::Other(v) => write!(f, "other: {}", v),
265 Error::BodyStalled => write!(f, "body data reading stalled"),
266 }
267 }
268}
269
270impl From<http::Error> for Error {
271 fn from(value: http::Error) -> Self {
272 Self::Http(value)
273 }
274}
275
276impl From<ureq_proto::Error> for Error {
277 fn from(value: ureq_proto::Error) -> Self {
278 Self::Protocol(value)
279 }
280}
281
282#[cfg(feature = "_rustls")]
283impl From<rustls::Error> for Error {
284 fn from(value: rustls::Error) -> Self {
285 Self::Rustls(value)
286 }
287}
288
289#[cfg(feature = "native-tls")]
290impl From<native_tls::Error> for Error {
291 fn from(value: native_tls::Error) -> Self {
292 Self::NativeTls(value)
293 }
294}
295
296#[cfg(feature = "native-tls")]
297impl From<der::Error> for Error {
298 fn from(value: der::Error) -> Self {
299 Self::Der(value)
300 }
301}
302
303#[cfg(feature = "cookies")]
304impl From<cookie_store::CookieError> for Error {
305 fn from(value: cookie_store::CookieError) -> Self {
306 Self::Cookie(value)
307 }
308}
309
310#[cfg(feature = "cookies")]
311impl From<cookie_store::Error> for Error {
312 fn from(value: cookie_store::Error) -> Self {
313 Self::CookieJar(value)
314 }
315}
316
317#[cfg(feature = "json")]
318impl From<serde_json::Error> for Error {
319 fn from(value: serde_json::Error) -> Self {
320 Self::Json(value)
321 }
322}
323
324#[cfg(test)]
325mod test {
326
327 use super::*;
328
329 #[test]
330 #[cfg(feature = "_test")]
331 fn status_code_error_redirect() {
332 use crate::test::init_test_log;
333 use crate::transport::set_handler;
334 init_test_log();
335 set_handler(
336 "/redirect_a",
337 302,
338 &[("Location", "http://example.edu/redirect_b")],
339 &[],
340 );
341 set_handler(
342 "/redirect_b",
343 302,
344 &[("Location", "http://example.com/status/500")],
345 &[],
346 );
347 set_handler("/status/500", 500, &[], &[]);
348 let err = crate::get("http://example.org/redirect_a")
349 .call()
350 .unwrap_err();
351 assert!(matches!(err, Error::StatusCode(500)));
352 }
353
354 #[test]
355 fn ensure_error_size() {
356 // This is platform dependent, so we can't be too strict or precise.
357 let size = std::mem::size_of::<Error>();
358 assert!(size < 100); // 40 on Macbook M1
359 }
360}