1#![cfg_attr(
2 all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")),
3 allow(unused)
4)]
5use std::error::Error as StdError;
6use std::fmt;
7use std::io;
8
9use crate::util::Escape;
10use crate::{StatusCode, Url};
11
12pub type Result<T> = std::result::Result<T, Error>;
14
15pub struct Error {
21 inner: Box<Inner>,
22}
23
24pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
25
26struct Inner {
27 kind: Kind,
28 source: Option<BoxError>,
29 url: Option<Url>,
30}
31
32impl Error {
33 pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
34 where
35 E: Into<BoxError>,
36 {
37 Error {
38 inner: Box::new(Inner {
39 kind,
40 source: source.map(Into::into),
41 url: None,
42 }),
43 }
44 }
45
46 pub fn url(&self) -> Option<&Url> {
64 self.inner.url.as_ref()
65 }
66
67 pub fn url_mut(&mut self) -> Option<&mut Url> {
73 self.inner.url.as_mut()
74 }
75
76 pub fn with_url(mut self, url: Url) -> Self {
78 self.inner.url = Some(url);
79 self
80 }
81
82 pub(crate) fn if_no_url(mut self, f: impl FnOnce() -> Url) -> Self {
83 if self.inner.url.is_none() {
84 self.inner.url = Some(f());
85 }
86 self
87 }
88
89 pub fn without_url(mut self) -> Self {
92 self.inner.url = None;
93 self
94 }
95
96 pub fn is_builder(&self) -> bool {
98 matches!(self.inner.kind, Kind::Builder)
99 }
100
101 pub fn is_redirect(&self) -> bool {
103 matches!(self.inner.kind, Kind::Redirect)
104 }
105
106 pub fn is_status(&self) -> bool {
108 #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
109 {
110 matches!(self.inner.kind, Kind::Status(_, _))
111 }
112 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
113 {
114 matches!(self.inner.kind, Kind::Status(_))
115 }
116 }
117
118 pub fn is_timeout(&self) -> bool {
120 let mut source = self.source();
121
122 while let Some(err) = source {
123 if err.is::<TimedOut>() {
124 return true;
125 }
126 if let Some(err) = err.downcast_ref::<Error>() {
127 if err.is_timeout() {
128 return true;
129 }
130 }
131 #[cfg(not(all(
132 target_arch = "wasm32",
133 any(target_os = "unknown", target_os = "none")
134 )))]
135 if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
136 if hyper_err.is_timeout() {
137 return true;
138 }
139 }
140 if let Some(io) = err.downcast_ref::<io::Error>() {
141 if io.kind() == io::ErrorKind::TimedOut {
142 return true;
143 }
144 }
145 source = err.source();
146 }
147
148 false
149 }
150
151 pub fn is_request(&self) -> bool {
153 matches!(self.inner.kind, Kind::Request)
154 }
155
156 #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
157 pub fn is_connect(&self) -> bool {
159 let mut source = self.source();
160
161 while let Some(err) = source {
162 if let Some(hyper_err) = err.downcast_ref::<hyper_util::client::legacy::Error>() {
163 if hyper_err.is_connect() {
164 return true;
165 }
166 }
167
168 source = err.source();
169 }
170
171 false
172 }
173
174 #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
175 pub fn is_dns(&self) -> bool {
177 let mut source = self.source();
178
179 while let Some(err) = source {
180 if err.is::<DnsError>() {
181 return true;
182 }
183
184 source = err.source();
185 }
186
187 false
188 }
189
190 pub fn is_body(&self) -> bool {
192 matches!(self.inner.kind, Kind::Body)
193 }
194
195 pub fn is_decode(&self) -> bool {
197 matches!(self.inner.kind, Kind::Decode)
198 }
199
200 pub fn status(&self) -> Option<StatusCode> {
202 match self.inner.kind {
203 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
204 Kind::Status(code) => Some(code),
205 #[cfg(not(all(
206 target_arch = "wasm32",
207 any(target_os = "unknown", target_os = "none")
208 )))]
209 Kind::Status(code, _) => Some(code),
210 _ => None,
211 }
212 }
213
214 pub fn is_upgrade(&self) -> bool {
216 matches!(self.inner.kind, Kind::Upgrade)
217 }
218
219 #[allow(unused)]
222 pub(crate) fn into_io(self) -> io::Error {
223 io::Error::other(self)
224 }
225}
226
227#[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
232pub(crate) fn cast_to_internal_error(error: BoxError) -> BoxError {
233 if error.is::<tower::timeout::error::Elapsed>() {
234 Box::new(crate::error::TimedOut) as BoxError
235 } else {
236 error
237 }
238}
239
240impl fmt::Debug for Error {
241 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
242 let mut builder = f.debug_struct("reqwest::Error");
243
244 builder.field("kind", &self.inner.kind);
245
246 if let Some(ref url) = self.inner.url {
247 builder.field("url", &url.as_str());
248 }
249 if let Some(ref source) = self.inner.source {
250 builder.field("source", source);
251 }
252
253 builder.finish()
254 }
255}
256
257impl fmt::Display for Error {
258 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
259 match self.inner.kind {
260 Kind::Builder => f.write_str("builder error")?,
261 Kind::Request => f.write_str("error sending request")?,
262 Kind::Body => f.write_str("request or response body error")?,
263 Kind::Decode => f.write_str("error decoding response body")?,
264 Kind::Redirect => f.write_str("error following redirect")?,
265 Kind::Upgrade => f.write_str("error upgrading connection")?,
266 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
267 Kind::Status(ref code) => {
268 let prefix = if code.is_client_error() {
269 "HTTP status client error"
270 } else {
271 debug_assert!(code.is_server_error());
272 "HTTP status server error"
273 };
274 write!(f, "{prefix} ({code})")?;
275 }
276 #[cfg(not(all(
277 target_arch = "wasm32",
278 any(target_os = "unknown", target_os = "none")
279 )))]
280 Kind::Status(ref code, ref reason) => {
281 let prefix = if code.is_client_error() {
282 "HTTP status client error"
283 } else {
284 debug_assert!(code.is_server_error());
285 "HTTP status server error"
286 };
287 if let Some(reason) = reason {
288 write!(
289 f,
290 "{prefix} ({} {})",
291 code.as_str(),
292 Escape::new(reason.as_bytes())
293 )?;
294 } else {
295 write!(f, "{prefix} ({code})")?;
296 }
297 }
298 };
299
300 if let Some(url) = &self.inner.url {
301 write!(f, " for url ({url})")?;
302 }
303
304 Ok(())
305 }
306}
307
308impl StdError for Error {
309 fn source(&self) -> Option<&(dyn StdError + 'static)> {
310 self.inner.source.as_ref().map(|e| &**e as _)
311 }
312}
313
314#[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
315impl From<crate::error::Error> for wasm_bindgen::JsValue {
316 fn from(err: Error) -> wasm_bindgen::JsValue {
317 js_sys::Error::from(err).into()
318 }
319}
320
321#[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
322impl From<crate::error::Error> for js_sys::Error {
323 fn from(err: Error) -> js_sys::Error {
324 js_sys::Error::new(&format!("{err}"))
325 }
326}
327
328#[derive(Debug)]
329pub(crate) enum Kind {
330 Builder,
331 Request,
332 Redirect,
333 #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
334 Status(StatusCode, Option<hyper::ext::ReasonPhrase>),
335 #[cfg(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none")))]
336 Status(StatusCode),
337 Body,
338 Decode,
339 Upgrade,
340}
341
342pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
345 Error::new(Kind::Builder, Some(e))
346}
347
348pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
349 Error::new(Kind::Body, Some(e))
350}
351
352pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
353 Error::new(Kind::Decode, Some(e))
354}
355
356pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
357 Error::new(Kind::Request, Some(e))
358}
359
360pub(crate) fn dns<E: Into<BoxError>>(e: E) -> BoxError {
361 Box::new(DnsError { inner: e.into() })
362}
363
364pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
365 Error::new(Kind::Redirect, Some(e)).with_url(url)
366}
367
368pub(crate) fn status_code(
369 url: Url,
370 status: StatusCode,
371 #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))] reason: Option<hyper::ext::ReasonPhrase>,
372) -> Error {
373 Error::new(
374 Kind::Status(
375 status,
376 #[cfg(not(all(
377 target_arch = "wasm32",
378 any(target_os = "unknown", target_os = "none")
379 )))]
380 reason,
381 ),
382 None::<Error>,
383 )
384 .with_url(url)
385}
386
387pub(crate) fn url_bad_scheme(url: Url) -> Error {
388 Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
389}
390
391pub(crate) fn url_invalid_uri(url: Url) -> Error {
392 Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
393}
394
395if_wasm! {
396 pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
397 format!("{js_val:?}").into()
398 }
399}
400
401pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
402 Error::new(Kind::Upgrade, Some(e))
403}
404
405#[allow(unused)]
408pub(crate) fn decode_io(e: io::Error) -> Error {
409 if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
410 *e.into_inner()
411 .expect("io::Error::get_ref was Some(_)")
412 .downcast::<Error>()
413 .expect("StdError::is() was true")
414 } else {
415 decode(e)
416 }
417}
418
419#[derive(Debug)]
422pub(crate) struct TimedOut;
423
424impl fmt::Display for TimedOut {
425 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
426 f.write_str("operation timed out")
427 }
428}
429
430impl StdError for TimedOut {}
431
432#[derive(Debug)]
433pub(crate) struct BadScheme;
434
435impl fmt::Display for BadScheme {
436 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
437 f.write_str("URL scheme is not allowed")
438 }
439}
440
441impl StdError for BadScheme {}
442
443#[derive(Debug)]
444pub(crate) struct DnsError {
445 pub(crate) inner: BoxError,
446}
447
448impl fmt::Display for DnsError {
449 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
450 f.write_str("error resolving DNS")
451 }
452}
453
454impl StdError for DnsError {
455 fn source(&self) -> Option<&(dyn StdError + 'static)> {
456 Some(&*self.inner as _)
457 }
458}
459
460#[cfg(test)]
461mod tests {
462 use super::*;
463
464 fn assert_send<T: Send>() {}
465 fn assert_sync<T: Sync>() {}
466
467 #[test]
468 fn test_source_chain() {
469 let root = Error::new(Kind::Request, None::<Error>);
470 assert!(root.source().is_none());
471
472 let link = super::body(root);
473 assert!(link.source().is_some());
474 assert_send::<Error>();
475 assert_sync::<Error>();
476 }
477
478 #[test]
479 fn mem_size_of() {
480 use std::mem::size_of;
481 assert_eq!(size_of::<Error>(), size_of::<usize>());
482 }
483
484 #[test]
485 fn roundtrip_io_error() {
486 let orig = super::request("orig");
487 let io = orig.into_io();
489 let err = super::decode_io(io);
491 match err.inner.kind {
493 Kind::Request => (),
494 _ => panic!("{err:?}"),
495 }
496 }
497
498 #[test]
499 fn from_unknown_io_error() {
500 let orig = io::Error::other("orly");
501 let err = super::decode_io(orig);
502 match err.inner.kind {
503 Kind::Decode => (),
504 _ => panic!("{err:?}"),
505 }
506 }
507
508 #[test]
509 fn decode_body_timeout_is_timeout() {
510 let err = super::decode(super::body(super::TimedOut));
513 assert!(err.is_decode());
514 assert!(err.is_timeout());
515 }
516
517 #[test]
518 fn decode_wraps_other_errors() {
519 let io = io::Error::other("boom");
520 let err = super::decode(io);
521 assert!(err.is_decode());
522 assert!(!err.is_timeout());
523 }
524
525 #[test]
526 fn is_timeout() {
527 let err = super::request(super::TimedOut);
528 assert!(err.is_timeout());
529
530 let io = io::Error::from(io::ErrorKind::TimedOut);
533 let nested = super::request(io);
534 assert!(nested.is_timeout());
535 }
536
537 #[cfg(not(all(target_arch = "wasm32", any(target_os = "unknown", target_os = "none"))))]
538 #[test]
539 fn is_dns() {
540 let err = super::request(DnsError { inner: "".into() });
541 assert!(err.is_dns());
542 }
543}