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")] This is deliberately not _tls, because we want to let
80 // bespoke TLS transports use this error code.
81 Tls(&'static str),
82
83 /// Error in reading PEM certificates/private keys.
84 ///
85 /// *Note:* The wrapped error struct is not considered part of ureq API.
86 /// Breaking changes in that struct will not be reflected in ureq
87 /// major versions.
88 #[cfg(feature = "_tls")]
89 Pem(rustls_pki_types::pem::Error),
90
91 /// An error originating in Rustls.
92 ///
93 /// *Note:* The wrapped error struct is not considered part of ureq API.
94 /// Breaking changes in that struct will not be reflected in ureq
95 /// major versions.
96 #[cfg(feature = "_rustls")]
97 Rustls(rustls::Error),
98
99 /// An error originating in Native-TLS.
100 ///
101 /// *Note:* The wrapped error struct is not considered part of ureq API.
102 /// Breaking changes in that struct will not be reflected in ureq
103 /// major versions.
104 #[cfg(feature = "native-tls")]
105 NativeTls(native_tls::Error),
106
107 /// An error providing DER encoded certificates or private keys to Native-TLS.
108 ///
109 /// *Note:* The wrapped error struct is not considered part of ureq API.
110 /// Breaking changes in that struct will not be reflected in ureq
111 /// major versions.
112 #[cfg(feature = "native-tls")]
113 Der(der::Error),
114
115 /// An error with the cookies.
116 ///
117 /// *Note:* The wrapped error struct is not considered part of ureq API.
118 /// Breaking changes in that struct will not be reflected in ureq
119 /// major versions.
120 #[cfg(feature = "cookies")]
121 Cookie(cookie_store::CookieError),
122
123 /// An error parsing a cookie value.
124 #[cfg(feature = "cookies")]
125 CookieValue(&'static str),
126
127 /// An error in the cookie store.
128 ///
129 /// *Note:* The wrapped error struct is not considered part of ureq API.
130 /// Breaking changes in that struct will not be reflected in ureq
131 /// major versions.
132 #[cfg(feature = "cookies")]
133 CookieJar(cookie_store::Error),
134
135 /// An unrecognised character set.
136 #[cfg(feature = "charset")]
137 UnknownCharset(String),
138
139 /// The setting [`https_only`](crate::config::ConfigBuilder::https_only) is true and
140 /// the URI is not https.
141 RequireHttpsOnly(String),
142
143 /// The response header, from status up until body, is too big.
144 LargeResponseHeader(usize, usize),
145
146 /// Body decompression failed (gzip or brotli).
147 #[cfg(any(feature = "gzip", feature = "brotli"))]
148 Decompress(&'static str, io::Error),
149
150 /// Serde JSON error.
151 #[cfg(feature = "json")]
152 Json(serde_json::Error),
153
154 /// Invalid MIME type in multipart form.
155 ///
156 /// *Note:* The wrapped error struct is not considered part of ureq API.
157 /// Breaking changes in that struct will not be reflected in ureq
158 /// major versions.
159 #[cfg(feature = "multipart")]
160 InvalidMimeType(mime_guess::mime::FromStrError),
161
162 /// Attempt to connect to a CONNECT proxy failed.
163 ConnectProxyFailed(String),
164
165 /// The protocol requires TLS (https), but the connector did not
166 /// create a TLS secured transport.
167 ///
168 /// This typically indicates a fault in bespoke `Connector` chains.
169 TlsRequired,
170
171 /// Some other error occured.
172 ///
173 /// This is an escape hatch for bespoke connector chains having errors that don't naturally
174 /// map to any other error. For connector chains we recommend:
175 ///
176 /// 1. Map to [`Error::Io`] as far as possible.
177 /// 2. Map to other [`Error`] where reasonable.
178 /// 3. Fall back on [`Error::Other`].
179 /// 4. As a last resort [`Error::ConnectionFailed`].
180 ///
181 /// ureq does not produce this error using the default connectors.
182 Other(Box<dyn std::error::Error + Send + Sync>),
183
184 /// ureq-proto made no progress and there is no more input to read.
185 ///
186 /// We should never see this value.
187 #[doc(hidden)]
188 BodyStalled,
189}
190
191impl std::error::Error for Error {}
192
193impl Error {
194 /// Convert the error into a [`std::io::Error`].
195 ///
196 /// If the error is [`Error::Io`], we unpack the error. In othe cases we make
197 /// an `std::io::ErrorKind::Other`.
198 pub fn into_io(self) -> io::Error {
199 if let Self::Io(e) = self {
200 e
201 } else {
202 io::Error::new(io::ErrorKind::Other, self)
203 }
204 }
205
206 pub(crate) fn disconnected(reason: &'static str) -> Error {
207 trace!("UnexpectedEof reason: {}", reason);
208 io::Error::new(io::ErrorKind::UnexpectedEof, "Peer disconnected").into()
209 }
210}
211
212pub(crate) fn is_wrapped_ureq_error(e: &io::Error) -> bool {
213 e.get_ref().map(|x| x.is::<Error>()).unwrap_or(false)
214}
215
216impl From<io::Error> for Error {
217 fn from(e: io::Error) -> Self {
218 if is_wrapped_ureq_error(&e) {
219 // unwraps are ok, see above.
220 let boxed = e.into_inner().unwrap();
221 let ureq = boxed.downcast::<Error>().unwrap();
222 *ureq
223 } else {
224 Error::Io(e)
225 }
226 }
227}
228
229impl fmt::Display for Error {
230 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
231 match self {
232 Error::StatusCode(v) => write!(f, "http status: {}", v),
233 Error::Http(v) => write!(f, "http: {}", v),
234 Error::BadUri(v) => write!(f, "bad uri: {}", v),
235 Error::Protocol(v) => write!(f, "protocol: {}", v),
236 Error::Io(v) => write!(f, "io: {}", v),
237 Error::Timeout(v) => write!(f, "timeout: {}", v),
238 Error::HostNotFound => write!(f, "host not found"),
239 Error::RedirectFailed => write!(f, "redirect failed"),
240 Error::InvalidProxyUrl => write!(f, "invalid proxy url"),
241 Error::ConnectionFailed => write!(f, "connection failed"),
242 Error::BodyExceedsLimit(v) => {
243 write!(f, "the response body is larger than request limit: {}", v)
244 }
245 Error::TooManyRedirects => write!(f, "too many redirects"),
246 Error::Tls(v) => write!(f, "{}", v),
247 #[cfg(feature = "_tls")]
248 Error::Pem(v) => write!(f, "PEM: {:?}", v),
249 #[cfg(feature = "_rustls")]
250 Error::Rustls(v) => write!(f, "rustls: {}", v),
251 #[cfg(feature = "native-tls")]
252 Error::NativeTls(v) => write!(f, "native-tls: {}", v),
253 #[cfg(feature = "native-tls")]
254 Error::Der(v) => write!(f, "der: {}", v),
255 #[cfg(feature = "cookies")]
256 Error::Cookie(v) => write!(f, "cookie: {}", v),
257 #[cfg(feature = "cookies")]
258 Error::CookieValue(v) => write!(f, "{}", v),
259 #[cfg(feature = "cookies")]
260 Error::CookieJar(v) => write!(f, "cookie: {}", v),
261 #[cfg(feature = "charset")]
262 Error::UnknownCharset(v) => write!(f, "unknown character set: {}", v),
263 Error::RequireHttpsOnly(v) => write!(f, "configured for https only: {}", v),
264 Error::LargeResponseHeader(x, y) => {
265 write!(f, "response header is too big: {} > {}", x, y)
266 }
267 #[cfg(any(feature = "gzip", feature = "brotli"))]
268 Error::Decompress(x, y) => write!(f, "{} decompression failed: {}", x, y),
269 #[cfg(feature = "json")]
270 Error::Json(v) => write!(f, "json: {}", v),
271 #[cfg(feature = "multipart")]
272 Error::InvalidMimeType(v) => write!(f, "invalid MIME type: {}", v),
273 Error::ConnectProxyFailed(v) => write!(f, "CONNECT proxy failed: {}", v),
274 Error::TlsRequired => write!(f, "TLS required, but transport is unsecured"),
275 Error::Other(v) => write!(f, "other: {}", v),
276 Error::BodyStalled => write!(f, "body data reading stalled"),
277 }
278 }
279}
280
281impl From<http::Error> for Error {
282 fn from(value: http::Error) -> Self {
283 Self::Http(value)
284 }
285}
286
287impl From<ureq_proto::Error> for Error {
288 fn from(value: ureq_proto::Error) -> Self {
289 Self::Protocol(value)
290 }
291}
292
293#[cfg(feature = "_rustls")]
294impl From<rustls::Error> for Error {
295 fn from(value: rustls::Error) -> Self {
296 Self::Rustls(value)
297 }
298}
299
300#[cfg(feature = "native-tls")]
301impl From<native_tls::Error> for Error {
302 fn from(value: native_tls::Error) -> Self {
303 Self::NativeTls(value)
304 }
305}
306
307#[cfg(feature = "native-tls")]
308impl From<der::Error> for Error {
309 fn from(value: der::Error) -> Self {
310 Self::Der(value)
311 }
312}
313
314#[cfg(feature = "cookies")]
315impl From<cookie_store::CookieError> for Error {
316 fn from(value: cookie_store::CookieError) -> Self {
317 Self::Cookie(value)
318 }
319}
320
321#[cfg(feature = "cookies")]
322impl From<cookie_store::Error> for Error {
323 fn from(value: cookie_store::Error) -> Self {
324 Self::CookieJar(value)
325 }
326}
327
328#[cfg(feature = "json")]
329impl From<serde_json::Error> for Error {
330 fn from(value: serde_json::Error) -> Self {
331 Self::Json(value)
332 }
333}
334
335#[cfg(test)]
336mod test {
337
338 use super::*;
339
340 #[test]
341 #[cfg(feature = "_test")]
342 fn status_code_error_redirect() {
343 use crate::test::init_test_log;
344 use crate::transport::set_handler;
345 init_test_log();
346 set_handler(
347 "/redirect_a",
348 302,
349 &[("Location", "http://example.edu/redirect_b")],
350 &[],
351 );
352 set_handler(
353 "/redirect_b",
354 302,
355 &[("Location", "http://example.com/status/500")],
356 &[],
357 );
358 set_handler("/status/500", 500, &[], &[]);
359 let err = crate::get("http://example.org/redirect_a")
360 .call()
361 .unwrap_err();
362 assert!(matches!(err, Error::StatusCode(500)));
363 }
364
365 #[test]
366 fn ensure_error_size() {
367 // This is platform dependent, so we can't be too strict or precise.
368 let size = std::mem::size_of::<Error>();
369 assert!(size < 100); // 40 on Macbook M1
370 }
371}