1#![cfg_attr(target_arch = "wasm32", allow(unused))]
2use std::error::Error as StdError;
3use std::fmt;
4use std::io;
5
6use crate::util::Escape;
7use crate::{StatusCode, Url};
8
9pub type Result<T> = std::result::Result<T, Error>;
11
12pub struct Error {
18 inner: Box<Inner>,
19}
20
21pub(crate) type BoxError = Box<dyn StdError + Send + Sync>;
22
23struct Inner {
24 kind: Kind,
25 source: Option<BoxError>,
26 url: Option<Url>,
27}
28
29impl Error {
30 pub(crate) fn new<E>(kind: Kind, source: Option<E>) -> Error
31 where
32 E: Into<BoxError>,
33 {
34 Error {
35 inner: Box::new(Inner {
36 kind,
37 source: source.map(Into::into),
38 url: None,
39 }),
40 }
41 }
42
43 pub fn url(&self) -> Option<&Url> {
61 self.inner.url.as_ref()
62 }
63
64 pub fn url_mut(&mut self) -> Option<&mut Url> {
70 self.inner.url.as_mut()
71 }
72
73 pub fn with_url(mut self, url: Url) -> Self {
75 self.inner.url = Some(url);
76 self
77 }
78
79 pub(crate) fn if_no_url(mut self, f: impl FnOnce() -> Url) -> Self {
80 if self.inner.url.is_none() {
81 self.inner.url = Some(f());
82 }
83 self
84 }
85
86 pub fn without_url(mut self) -> Self {
89 self.inner.url = None;
90 self
91 }
92
93 pub fn is_builder(&self) -> bool {
95 matches!(self.inner.kind, Kind::Builder)
96 }
97
98 pub fn is_redirect(&self) -> bool {
100 matches!(self.inner.kind, Kind::Redirect)
101 }
102
103 pub fn is_status(&self) -> bool {
105 #[cfg(not(target_arch = "wasm32"))]
106 {
107 matches!(self.inner.kind, Kind::Status(_, _))
108 }
109 #[cfg(target_arch = "wasm32")]
110 {
111 matches!(self.inner.kind, Kind::Status(_))
112 }
113 }
114
115 pub fn is_timeout(&self) -> bool {
117 let mut source = self.source();
118
119 while let Some(err) = source {
120 if err.is::<TimedOut>() {
121 return true;
122 }
123 #[cfg(not(target_arch = "wasm32"))]
124 if let Some(hyper_err) = err.downcast_ref::<hyper::Error>() {
125 if hyper_err.is_timeout() {
126 return true;
127 }
128 }
129 if let Some(io) = err.downcast_ref::<io::Error>() {
130 if io.kind() == io::ErrorKind::TimedOut {
131 return true;
132 }
133 }
134 source = err.source();
135 }
136
137 false
138 }
139
140 pub fn is_request(&self) -> bool {
142 matches!(self.inner.kind, Kind::Request)
143 }
144
145 #[cfg(not(target_arch = "wasm32"))]
146 pub fn is_connect(&self) -> bool {
148 let mut source = self.source();
149
150 while let Some(err) = source {
151 if let Some(hyper_err) = err.downcast_ref::<hyper_util::client::legacy::Error>() {
152 if hyper_err.is_connect() {
153 return true;
154 }
155 }
156
157 source = err.source();
158 }
159
160 false
161 }
162
163 pub fn is_body(&self) -> bool {
165 matches!(self.inner.kind, Kind::Body)
166 }
167
168 pub fn is_decode(&self) -> bool {
170 matches!(self.inner.kind, Kind::Decode)
171 }
172
173 pub fn status(&self) -> Option<StatusCode> {
175 match self.inner.kind {
176 #[cfg(target_arch = "wasm32")]
177 Kind::Status(code) => Some(code),
178 #[cfg(not(target_arch = "wasm32"))]
179 Kind::Status(code, _) => Some(code),
180 _ => None,
181 }
182 }
183
184 #[allow(unused)]
187 pub(crate) fn into_io(self) -> io::Error {
188 io::Error::new(io::ErrorKind::Other, self)
189 }
190}
191
192#[cfg(not(target_arch = "wasm32"))]
197pub(crate) fn cast_to_internal_error(error: BoxError) -> BoxError {
198 if error.is::<tower::timeout::error::Elapsed>() {
199 Box::new(crate::error::TimedOut) as BoxError
200 } else {
201 error
202 }
203}
204
205impl fmt::Debug for Error {
206 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
207 let mut builder = f.debug_struct("reqwest::Error");
208
209 builder.field("kind", &self.inner.kind);
210
211 if let Some(ref url) = self.inner.url {
212 builder.field("url", &url.as_str());
213 }
214 if let Some(ref source) = self.inner.source {
215 builder.field("source", source);
216 }
217
218 builder.finish()
219 }
220}
221
222impl fmt::Display for Error {
223 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
224 match self.inner.kind {
225 Kind::Builder => f.write_str("builder error")?,
226 Kind::Request => f.write_str("error sending request")?,
227 Kind::Body => f.write_str("request or response body error")?,
228 Kind::Decode => f.write_str("error decoding response body")?,
229 Kind::Redirect => f.write_str("error following redirect")?,
230 Kind::Upgrade => f.write_str("error upgrading connection")?,
231 #[cfg(target_arch = "wasm32")]
232 Kind::Status(ref code) => {
233 let prefix = if code.is_client_error() {
234 "HTTP status client error"
235 } else {
236 debug_assert!(code.is_server_error());
237 "HTTP status server error"
238 };
239 write!(f, "{prefix} ({code})")?;
240 }
241 #[cfg(not(target_arch = "wasm32"))]
242 Kind::Status(ref code, ref reason) => {
243 let prefix = if code.is_client_error() {
244 "HTTP status client error"
245 } else {
246 debug_assert!(code.is_server_error());
247 "HTTP status server error"
248 };
249 if let Some(reason) = reason {
250 write!(
251 f,
252 "{prefix} ({} {})",
253 code.as_str(),
254 Escape::new(reason.as_bytes())
255 )?;
256 } else {
257 write!(f, "{prefix} ({code})")?;
258 }
259 }
260 };
261
262 if let Some(url) = &self.inner.url {
263 write!(f, " for url ({url})")?;
264 }
265
266 Ok(())
267 }
268}
269
270impl StdError for Error {
271 fn source(&self) -> Option<&(dyn StdError + 'static)> {
272 self.inner.source.as_ref().map(|e| &**e as _)
273 }
274}
275
276#[cfg(target_arch = "wasm32")]
277impl From<crate::error::Error> for wasm_bindgen::JsValue {
278 fn from(err: Error) -> wasm_bindgen::JsValue {
279 js_sys::Error::from(err).into()
280 }
281}
282
283#[cfg(target_arch = "wasm32")]
284impl From<crate::error::Error> for js_sys::Error {
285 fn from(err: Error) -> js_sys::Error {
286 js_sys::Error::new(&format!("{err}"))
287 }
288}
289
290#[derive(Debug)]
291pub(crate) enum Kind {
292 Builder,
293 Request,
294 Redirect,
295 #[cfg(not(target_arch = "wasm32"))]
296 Status(StatusCode, Option<hyper::ext::ReasonPhrase>),
297 #[cfg(target_arch = "wasm32")]
298 Status(StatusCode),
299 Body,
300 Decode,
301 Upgrade,
302}
303
304pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
307 Error::new(Kind::Builder, Some(e))
308}
309
310pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
311 Error::new(Kind::Body, Some(e))
312}
313
314pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
315 Error::new(Kind::Decode, Some(e))
316}
317
318pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
319 Error::new(Kind::Request, Some(e))
320}
321
322pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
323 Error::new(Kind::Redirect, Some(e)).with_url(url)
324}
325
326pub(crate) fn status_code(
327 url: Url,
328 status: StatusCode,
329 #[cfg(not(target_arch = "wasm32"))] reason: Option<hyper::ext::ReasonPhrase>,
330) -> Error {
331 Error::new(
332 Kind::Status(
333 status,
334 #[cfg(not(target_arch = "wasm32"))]
335 reason,
336 ),
337 None::<Error>,
338 )
339 .with_url(url)
340}
341
342pub(crate) fn url_bad_scheme(url: Url) -> Error {
343 Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
344}
345
346pub(crate) fn url_invalid_uri(url: Url) -> Error {
347 Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
348}
349
350if_wasm! {
351 pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
352 format!("{js_val:?}").into()
353 }
354}
355
356pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
357 Error::new(Kind::Upgrade, Some(e))
358}
359
360#[cfg(any(
363 feature = "gzip",
364 feature = "zstd",
365 feature = "brotli",
366 feature = "deflate",
367 feature = "blocking",
368))]
369pub(crate) fn into_io(e: BoxError) -> io::Error {
370 io::Error::new(io::ErrorKind::Other, e)
371}
372
373#[allow(unused)]
374pub(crate) fn decode_io(e: io::Error) -> Error {
375 if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
376 *e.into_inner()
377 .expect("io::Error::get_ref was Some(_)")
378 .downcast::<Error>()
379 .expect("StdError::is() was true")
380 } else {
381 decode(e)
382 }
383}
384
385#[derive(Debug)]
388pub(crate) struct TimedOut;
389
390impl fmt::Display for TimedOut {
391 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
392 f.write_str("operation timed out")
393 }
394}
395
396impl StdError for TimedOut {}
397
398#[derive(Debug)]
399pub(crate) struct BadScheme;
400
401impl fmt::Display for BadScheme {
402 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
403 f.write_str("URL scheme is not allowed")
404 }
405}
406
407impl StdError for BadScheme {}
408
409#[cfg(test)]
410mod tests {
411 use super::*;
412
413 fn assert_send<T: Send>() {}
414 fn assert_sync<T: Sync>() {}
415
416 #[test]
417 fn test_source_chain() {
418 let root = Error::new(Kind::Request, None::<Error>);
419 assert!(root.source().is_none());
420
421 let link = super::body(root);
422 assert!(link.source().is_some());
423 assert_send::<Error>();
424 assert_sync::<Error>();
425 }
426
427 #[test]
428 fn mem_size_of() {
429 use std::mem::size_of;
430 assert_eq!(size_of::<Error>(), size_of::<usize>());
431 }
432
433 #[test]
434 fn roundtrip_io_error() {
435 let orig = super::request("orig");
436 let io = orig.into_io();
438 let err = super::decode_io(io);
440 match err.inner.kind {
442 Kind::Request => (),
443 _ => panic!("{err:?}"),
444 }
445 }
446
447 #[test]
448 fn from_unknown_io_error() {
449 let orig = io::Error::new(io::ErrorKind::Other, "orly");
450 let err = super::decode_io(orig);
451 match err.inner.kind {
452 Kind::Decode => (),
453 _ => panic!("{err:?}"),
454 }
455 }
456
457 #[test]
458 fn is_timeout() {
459 let err = super::request(super::TimedOut);
460 assert!(err.is_timeout());
461
462 let io = io::Error::from(io::ErrorKind::TimedOut);
465 let nested = super::request(io);
466 assert!(nested.is_timeout());
467 }
468}