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