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