1#![cfg_attr(target_arch = "wasm32", allow(unused))]
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5
6use crate::{StatusCode, Url};
7
8/// A `Result` alias where the `Err` case is `reqwest::Error`.
9pub type Result<T> = std::result::Result<T, Error>;
10
11/// The Errors that may occur when processing a `Request`.
12///
13/// Note: Errors may include the full URL used to make the `Request`. If the URL
14/// contains sensitive information (e.g. an API key as a query parameter), be
15/// sure to remove it ([`without_url`](Error::without_url))
16pub struct Error {
17 inner: Box<Inner>,
18}
19
20pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
21
22struct Inner {
23 kind: Kind,
24 source: Option<BoxError>,
25 url: Option<Url>,
26}
27
28impl Error {
29 pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
30 where
31 E: Into<BoxError>,
32 {
33 Error {
34 inner: Box::new(Inner {
35 kind,
36 source: source.map(Into::into),
37 url: None,
38 }),
39 }
40 }
41
42 /// Returns a possible URL related to this error.
43 ///
44 /// # Examples
45 ///
46 /// ```
47 /// # async fn run() {
48 /// // displays last stop of a redirect loop
49 /// let response = reqwest::get("http://site.with.redirect.loop").await;
50 /// if let Err(e) = response {
51 /// if e.is_redirect() {
52 /// if let Some(final_stop) = e.url() {
53 /// println!("redirect loop at {}", final_stop);
54 /// }
55 /// }
56 /// }
57 /// # }
58 /// ```
59 pub fn url(&self) -> Option<&Url> {
60 self.inner.url.as_ref()
61 }
62
63 /// Returns a mutable reference to the URL related to this error
64 ///
65 /// This is useful if you need to remove sensitive information from the URL
66 /// (e.g. an API key in the query), but do not want to remove the URL
67 /// entirely.
68 pub fn url_mut(&mut self) -> Option<&mut Url> {
69 self.inner.url.as_mut()
70 }
71
72 /// Add a url related to this error (overwriting any existing)
73 pub fn with_url(mut self, url: Url) -> Self {
74 self.inner.url = Some(url);
75 self
76 }
77
78 /// Strip the related url from this error (if, for example, it contains
79 /// sensitive information)
80 pub fn without_url(mut self) -> Self {
81 self.inner.url = None;
82 self
83 }
84
85 /// Returns true if the error is from a type Builder.
86 pub fn is_builder(&self) -> bool {
87 matches!(self.inner.kind, Kind::Builder)
88 }
89
90 /// Returns true if the error is from a `RedirectPolicy`.
91 pub fn is_redirect(&self) -> bool {
92 matches!(self.inner.kind, Kind::Redirect)
93 }
94
95 /// Returns true if the error is from `Response::error_for_status`.
96 pub fn is_status(&self) -> bool {
97 matches!(self.inner.kind, Kind::Status(_))
98 }
99
100 /// Returns true if the error is related to a timeout.
101 pub fn is_timeout(&self) -> bool {
102 let mut source = self.source();
103
104 while let Some(err) = source {
105 if err.is::<TimedOut>() {
106 return true;
107 }
108 source = err.source();
109 }
110
111 false
112 }
113
114 /// Returns true if the error is related to the request
115 pub fn is_request(&self) -> bool {
116 matches!(self.inner.kind, Kind::Request)
117 }
118
119 #[cfg(not(target_arch = "wasm32"))]
120 /// Returns true if the error is related to connect
121 pub fn is_connect(&self) -> bool {
122 let mut source = self.source();
123
124 while let Some(err) = source {
125 if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
126 if hyper_err.is_connect() {
127 return true;
128 }
129 }
130
131 source = err.source();
132 }
133
134 false
135 }
136
137 /// Returns true if the error is related to the request or response body
138 pub fn is_body(&self) -> bool {
139 matches!(self.inner.kind, Kind::Body)
140 }
141
142 /// Returns true if the error is related to decoding the response's body
143 pub fn is_decode(&self) -> bool {
144 matches!(self.inner.kind, Kind::Decode)
145 }
146
147 /// Returns the status code, if the error was generated from a response.
148 pub fn status(&self) -> Option<StatusCode> {
149 match self.inner.kind {
150 Kind::Status(code) => Some(code),
151 _ => None,
152 }
153 }
154
155 // private
156
157 #[allow(unused)]
158 pub(crate) fn into_io(self) -> io::Error {
159 io::Error::new(io::ErrorKind::Other, self)
160 }
161}
162
163impl fmt::Debug for Error {
164 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
165 let mut builder: DebugStruct<'_, '_> = f.debug_struct(name:"reqwest::Error");
166
167 builder.field(name:"kind", &self.inner.kind);
168
169 if let Some(ref url: &Url) = self.inner.url {
170 builder.field(name:"url", value:url);
171 }
172 if let Some(ref source: &Box) = self.inner.source {
173 builder.field(name:"source", value:source);
174 }
175
176 builder.finish()
177 }
178}
179
180impl fmt::Display for Error {
181 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
182 match self.inner.kind {
183 Kind::Builder => f.write_str("builder error")?,
184 Kind::Request => f.write_str("error sending request")?,
185 Kind::Body => f.write_str("request or response body error")?,
186 Kind::Decode => f.write_str("error decoding response body")?,
187 Kind::Redirect => f.write_str("error following redirect")?,
188 Kind::Upgrade => f.write_str("error upgrading connection")?,
189 Kind::Status(ref code) => {
190 let prefix = if code.is_client_error() {
191 "HTTP status client error"
192 } else {
193 debug_assert!(code.is_server_error());
194 "HTTP status server error"
195 };
196 write!(f, "{} ({})", prefix, code)?;
197 }
198 };
199
200 if let Some(url) = &self.inner.url {
201 write!(f, " for url ({})", url.as_str())?;
202 }
203
204 if let Some(e) = &self.inner.source {
205 write!(f, ": {}", e)?;
206 }
207
208 Ok(())
209 }
210}
211
212impl StdError for Error {
213 fn source(&self) -> Option<&(dyn StdError + 'static)> {
214 self.inner.source.as_ref().map(|e: &Box| &**e as _)
215 }
216}
217
218#[cfg(target_arch = "wasm32")]
219impl From<crate::error::Error> for wasm_bindgen::JsValue {
220 fn from(err: Error) -> wasm_bindgen::JsValue {
221 js_sys::Error::from(err).into()
222 }
223}
224
225#[cfg(target_arch = "wasm32")]
226impl From<crate::error::Error> for js_sys::Error {
227 fn from(err: Error) -> js_sys::Error {
228 js_sys::Error::new(&format!("{}", err))
229 }
230}
231
232#[derive(Debug)]
233pub(crate) enum Kind {
234 Builder,
235 Request,
236 Redirect,
237 Status(StatusCode),
238 Body,
239 Decode,
240 Upgrade,
241}
242
243// constructors
244
245pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
246 Error::new(Kind::Builder, source:Some(e))
247}
248
249pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
250 Error::new(Kind::Body, source:Some(e))
251}
252
253pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
254 Error::new(Kind::Decode, source:Some(e))
255}
256
257pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
258 Error::new(Kind::Request, source:Some(e))
259}
260
261pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
262 Error::new(Kind::Redirect, source:Some(e)).with_url(url)
263}
264
265pub(crate) fn status_code(url: Url, status: StatusCode) -> Error {
266 Error::new(Kind::Status(status), source:None::<Error>).with_url(url)
267}
268
269pub(crate) fn url_bad_scheme(url: Url) -> Error {
270 Error::new(Kind::Builder, source:Some(BadScheme)).with_url(url)
271}
272
273if_wasm! {
274 pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
275 format!("{:?}", js_val).into()
276 }
277}
278
279pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
280 Error::new(Kind::Upgrade, source:Some(e))
281}
282
283// io::Error helpers
284
285#[allow(unused)]
286pub(crate) fn into_io(e: Error) -> io::Error {
287 e.into_io()
288}
289
290#[allow(unused)]
291pub(crate) fn decode_io(e: io::Error) -> Error {
292 if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(default:false) {
293 *e.into_inner()
294 .expect("io::Error::get_ref was Some(_)")
295 .downcast::<Error>()
296 .expect(msg:"StdError::is() was true")
297 } else {
298 decode(e)
299 }
300}
301
302// internal Error "sources"
303
304#[derive(Debug)]
305pub(crate) struct TimedOut;
306
307impl fmt::Display for TimedOut {
308 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
309 f.write_str(data:"operation timed out")
310 }
311}
312
313impl StdError for TimedOut {}
314
315#[derive(Debug)]
316pub(crate) struct BadScheme;
317
318impl fmt::Display for BadScheme {
319 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
320 f.write_str(data:"URL scheme is not allowed")
321 }
322}
323
324impl StdError for BadScheme {}
325
326#[cfg(test)]
327mod tests {
328 use super::*;
329
330 fn assert_send<T: Send>() {}
331 fn assert_sync<T: Sync>() {}
332
333 #[test]
334 fn test_source_chain() {
335 let root = Error::new(Kind::Request, None::<Error>);
336 assert!(root.source().is_none());
337
338 let link = super::body(root);
339 assert!(link.source().is_some());
340 assert_send::<Error>();
341 assert_sync::<Error>();
342 }
343
344 #[test]
345 fn mem_size_of() {
346 use std::mem::size_of;
347 assert_eq!(size_of::<Error>(), size_of::<usize>());
348 }
349
350 #[test]
351 fn roundtrip_io_error() {
352 let orig = super::request("orig");
353 // Convert reqwest::Error into an io::Error...
354 let io = orig.into_io();
355 // Convert that io::Error back into a reqwest::Error...
356 let err = super::decode_io(io);
357 // It should have pulled out the original, not nested it...
358 match err.inner.kind {
359 Kind::Request => (),
360 _ => panic!("{:?}", err),
361 }
362 }
363
364 #[test]
365 fn from_unknown_io_error() {
366 let orig = io::Error::new(io::ErrorKind::Other, "orly");
367 let err = super::decode_io(orig);
368 match err.inner.kind {
369 Kind::Decode => (),
370 _ => panic!("{:?}", err),
371 }
372 }
373
374 #[test]
375 fn is_timeout() {
376 let err = super::request(super::TimedOut);
377 assert!(err.is_timeout());
378
379 let io = io::Error::new(io::ErrorKind::Other, err);
380 let nested = super::request(io);
381 assert!(nested.is_timeout());
382 }
383}
384