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 pub fn is_upgrade(&self) -> bool {
186 matches!(self.inner.kind, Kind::Upgrade)
187 }
188
189 #[allow(unused)]
192 pub(crate) fn into_io(self) -> io::Error {
193 io::Error::new(io::ErrorKind::Other, self)
194 }
195}
196
197#[cfg(not(target_arch = "wasm32"))]
202pub(crate) fn cast_to_internal_error(error: BoxError) -> BoxError {
203 if error.is::<tower::timeout::error::Elapsed>() {
204 Box::new(crate::error::TimedOut) as BoxError
205 } else {
206 error
207 }
208}
209
210impl fmt::Debug for Error {
211 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
212 let mut builder = f.debug_struct("reqwest::Error");
213
214 builder.field("kind", &self.inner.kind);
215
216 if let Some(ref url) = self.inner.url {
217 builder.field("url", &url.as_str());
218 }
219 if let Some(ref source) = self.inner.source {
220 builder.field("source", source);
221 }
222
223 builder.finish()
224 }
225}
226
227impl fmt::Display for Error {
228 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
229 match self.inner.kind {
230 Kind::Builder => f.write_str("builder error")?,
231 Kind::Request => f.write_str("error sending request")?,
232 Kind::Body => f.write_str("request or response body error")?,
233 Kind::Decode => f.write_str("error decoding response body")?,
234 Kind::Redirect => f.write_str("error following redirect")?,
235 Kind::Upgrade => f.write_str("error upgrading connection")?,
236 #[cfg(target_arch = "wasm32")]
237 Kind::Status(ref code) => {
238 let prefix = if code.is_client_error() {
239 "HTTP status client error"
240 } else {
241 debug_assert!(code.is_server_error());
242 "HTTP status server error"
243 };
244 write!(f, "{prefix} ({code})")?;
245 }
246 #[cfg(not(target_arch = "wasm32"))]
247 Kind::Status(ref code, ref reason) => {
248 let prefix = if code.is_client_error() {
249 "HTTP status client error"
250 } else {
251 debug_assert!(code.is_server_error());
252 "HTTP status server error"
253 };
254 if let Some(reason) = reason {
255 write!(
256 f,
257 "{prefix} ({} {})",
258 code.as_str(),
259 Escape::new(reason.as_bytes())
260 )?;
261 } else {
262 write!(f, "{prefix} ({code})")?;
263 }
264 }
265 };
266
267 if let Some(url) = &self.inner.url {
268 write!(f, " for url ({url})")?;
269 }
270
271 Ok(())
272 }
273}
274
275impl StdError for Error {
276 fn source(&self) -> Option<&(dyn StdError + 'static)> {
277 self.inner.source.as_ref().map(|e| &**e as _)
278 }
279}
280
281#[cfg(target_arch = "wasm32")]
282impl From<crate::error::Error> for wasm_bindgen::JsValue {
283 fn from(err: Error) -> wasm_bindgen::JsValue {
284 js_sys::Error::from(err).into()
285 }
286}
287
288#[cfg(target_arch = "wasm32")]
289impl From<crate::error::Error> for js_sys::Error {
290 fn from(err: Error) -> js_sys::Error {
291 js_sys::Error::new(&format!("{err}"))
292 }
293}
294
295#[derive(Debug)]
296pub(crate) enum Kind {
297 Builder,
298 Request,
299 Redirect,
300 #[cfg(not(target_arch = "wasm32"))]
301 Status(StatusCode, Option<hyper::ext::ReasonPhrase>),
302 #[cfg(target_arch = "wasm32")]
303 Status(StatusCode),
304 Body,
305 Decode,
306 Upgrade,
307}
308
309pub(crate) fn builder<E: Into<BoxError>>(e: E) -> Error {
312 Error::new(Kind::Builder, Some(e))
313}
314
315pub(crate) fn body<E: Into<BoxError>>(e: E) -> Error {
316 Error::new(Kind::Body, Some(e))
317}
318
319pub(crate) fn decode<E: Into<BoxError>>(e: E) -> Error {
320 Error::new(Kind::Decode, Some(e))
321}
322
323pub(crate) fn request<E: Into<BoxError>>(e: E) -> Error {
324 Error::new(Kind::Request, Some(e))
325}
326
327pub(crate) fn redirect<E: Into<BoxError>>(e: E, url: Url) -> Error {
328 Error::new(Kind::Redirect, Some(e)).with_url(url)
329}
330
331pub(crate) fn status_code(
332 url: Url,
333 status: StatusCode,
334 #[cfg(not(target_arch = "wasm32"))] reason: Option<hyper::ext::ReasonPhrase>,
335) -> Error {
336 Error::new(
337 Kind::Status(
338 status,
339 #[cfg(not(target_arch = "wasm32"))]
340 reason,
341 ),
342 None::<Error>,
343 )
344 .with_url(url)
345}
346
347pub(crate) fn url_bad_scheme(url: Url) -> Error {
348 Error::new(Kind::Builder, Some(BadScheme)).with_url(url)
349}
350
351pub(crate) fn url_invalid_uri(url: Url) -> Error {
352 Error::new(Kind::Builder, Some("Parsed Url is not a valid Uri")).with_url(url)
353}
354
355if_wasm! {
356 pub(crate) fn wasm(js_val: wasm_bindgen::JsValue) -> BoxError {
357 format!("{js_val:?}").into()
358 }
359}
360
361pub(crate) fn upgrade<E: Into<BoxError>>(e: E) -> Error {
362 Error::new(Kind::Upgrade, Some(e))
363}
364
365#[allow(unused)]
368pub(crate) fn decode_io(e: io::Error) -> Error {
369 if e.get_ref().map(|r| r.is::<Error>()).unwrap_or(false) {
370 *e.into_inner()
371 .expect("io::Error::get_ref was Some(_)")
372 .downcast::<Error>()
373 .expect("StdError::is() was true")
374 } else {
375 decode(e)
376 }
377}
378
379#[derive(Debug)]
382pub(crate) struct TimedOut;
383
384impl fmt::Display for TimedOut {
385 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
386 f.write_str("operation timed out")
387 }
388}
389
390impl StdError for TimedOut {}
391
392#[derive(Debug)]
393pub(crate) struct BadScheme;
394
395impl fmt::Display for BadScheme {
396 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
397 f.write_str("URL scheme is not allowed")
398 }
399}
400
401impl StdError for BadScheme {}
402
403#[cfg(test)]
404mod tests {
405 use super::*;
406
407 fn assert_send<T: Send>() {}
408 fn assert_sync<T: Sync>() {}
409
410 #[test]
411 fn test_source_chain() {
412 let root = Error::new(Kind::Request, None::<Error>);
413 assert!(root.source().is_none());
414
415 let link = super::body(root);
416 assert!(link.source().is_some());
417 assert_send::<Error>();
418 assert_sync::<Error>();
419 }
420
421 #[test]
422 fn mem_size_of() {
423 use std::mem::size_of;
424 assert_eq!(size_of::<Error>(), size_of::<usize>());
425 }
426
427 #[test]
428 fn roundtrip_io_error() {
429 let orig = super::request("orig");
430 let io = orig.into_io();
432 let err = super::decode_io(io);
434 match err.inner.kind {
436 Kind::Request => (),
437 _ => panic!("{err:?}"),
438 }
439 }
440
441 #[test]
442 fn from_unknown_io_error() {
443 let orig = io::Error::new(io::ErrorKind::Other, "orly");
444 let err = super::decode_io(orig);
445 match err.inner.kind {
446 Kind::Decode => (),
447 _ => panic!("{err:?}"),
448 }
449 }
450
451 #[test]
452 fn is_timeout() {
453 let err = super::request(super::TimedOut);
454 assert!(err.is_timeout());
455
456 let io = io::Error::from(io::ErrorKind::TimedOut);
459 let nested = super::request(io);
460 assert!(nested.is_timeout());
461 }
462}