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