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