Coverage Report

Created: 2026-10-28 06:40

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