flawless_http/
error.rs

1use crate::response::Response;
2
3/// A request error.
4///
5/// There are two types of errors that can happen when a request is performed:
6///
7/// 1. **Status code errors**
8///
9/// Every response with a status code >= 400 is considered an error. In this case, the error `enum` will
10/// contain the full [`Response`].
11///
12/// 2. **Transport protocol errors**
13///
14/// This kind of error happens in cases when there is a connection problem (e.g. connection refused) or a
15/// response can't be parsed.
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub enum Error {
18    /// A response was successfully received but had status code >= 400. Values are (status_code, Response).
19    Status(u16, Response),
20    /// There was an error making the request or receiving the response.
21    Transport(Transport),
22}
23
24impl Error {
25    /// Error kind.
26    ///
27    /// If the response status code is >=400, the error will be of kind `ErrorKind::StatusCode`. All other
28    /// kinds describe a transport protocol error.
29    ///
30    /// ```no_run
31    /// use flawless_http::{get, ErrorKind};
32    ///
33    /// let response = get("http://httpbin.org/status/500").send();
34    /// assert_eq!(response.unwrap_err().kind(), ErrorKind::StatusCode);
35    /// ````
36    pub fn kind(&self) -> ErrorKind {
37        match self {
38            Error::Status(_, _) => ErrorKind::StatusCode,
39            Error::Transport(Transport { kind: k, .. }) => *k,
40        }
41    }
42
43    /// Contains an error message providing more information about the error, if available.
44    ///
45    /// # Status code error message:
46    ///
47    /// ```no_run
48    /// use flawless_http::get;
49    ///
50    /// let response = get("http://httpbin.org/status/500").send();
51    /// assert_eq!(
52    ///   response.unwrap_err().message(),
53    ///   Some("Server returned status code 500".to_owned())
54    /// );
55    /// ````
56    ///
57    /// # Timeout error message:
58    ///
59    /// ```no_run
60    /// use std::time::Duration;
61    /// use flawless_http::get;
62    ///
63    /// let response = get("http://httpbin.org/delay/2")
64    ///                  .timeout(Duration::from_secs(1))
65    ///                  .send();
66    /// let err_msg = response.unwrap_err().message().unwrap();
67    /// assert!(err_msg.contains("timed out"));
68    /// ````
69    pub fn message(&self) -> Option<String> {
70        match self {
71            Error::Status(code, _) => Some(format!("Server returned status code {code}")),
72            Error::Transport(Transport { message: m, .. }) => m.clone(),
73        }
74    }
75}
76
77impl From<flawless_wasabi::HttpError> for Error {
78    fn from(value: flawless_wasabi::HttpError) -> Self {
79        match value {
80            flawless_wasabi::HttpError::Status(code, res) => Self::Status(code, res.into()),
81            flawless_wasabi::HttpError::Transport(transport) => Self::Transport(transport.into()),
82        }
83    }
84}
85
86/// Error indicating an issue with the underlying request transport.
87#[derive(Debug, Clone, PartialEq, Eq)]
88pub struct Transport {
89    pub(crate) kind: ErrorKind,
90    pub(crate) message: Option<String>,
91    pub(crate) url: Option<String>,
92}
93
94impl Transport {
95    /// The type of error that happened while processing the request.
96    pub fn kind(&self) -> ErrorKind {
97        self.kind
98    }
99
100    /// Higher level error details, if there are any.
101    pub fn message(&self) -> Option<&str> {
102        self.message.as_deref()
103    }
104
105    /// The `url` that failed. In cases of redirects, the original `url` might work, but one of the later
106    /// redirects might fail.
107    pub fn url(&self) -> Option<&String> {
108        self.url.as_ref()
109    }
110}
111
112impl From<flawless_wasabi::HttpTransportError> for Transport {
113    fn from(value: flawless_wasabi::HttpTransportError) -> Self {
114        Self { kind: value.kind.into(), message: value.message, url: value.url }
115    }
116}
117
118/// The kind of error that happened when a request is performed.
119///
120/// In case of a statues code error (>= 400), the kind is going to be `StatusCode`. All other kinds are
121/// related to the transport.
122#[derive(Debug, Clone, Copy, PartialEq, Eq)]
123pub enum ErrorKind {
124    /// The `url` could not be understood.
125    InvalidUrl,
126    /// The `url` scheme could not be understood.
127    UnknownScheme,
128    /// DNS lookup failed.
129    Dns,
130    /// Connection to server failed.
131    ConnectionFailed,
132    /// Too many redirects.
133    TooManyRedirects,
134    /// A status line we don't understand (e.g. `HTTP/1.1 200 OK`).
135    BadStatus,
136    /// A header line that couldn't be parsed.
137    BadHeader,
138    /// Some unspecified `std::io::Error`.
139    Io,
140    /// There was an external failure before the request could finish.
141    RequestInterrupted,
142    /// HTTP status code indicating an error (e.g. 4xx, 5xx)
143    /// Read the inner response body for details.
144    StatusCode,
145}
146
147impl From<flawless_wasabi::HttpErrorKind> for ErrorKind {
148    fn from(value: flawless_wasabi::HttpErrorKind) -> Self {
149        match value {
150            flawless_wasabi::HttpErrorKind::InvalidUrl => Self::InvalidUrl,
151            flawless_wasabi::HttpErrorKind::UnknownScheme => Self::UnknownScheme,
152            flawless_wasabi::HttpErrorKind::Dns => Self::Dns,
153            flawless_wasabi::HttpErrorKind::ConnectionFailed => Self::ConnectionFailed,
154            flawless_wasabi::HttpErrorKind::TooManyRedirects => Self::TooManyRedirects,
155            flawless_wasabi::HttpErrorKind::BadStatus => Self::BadStatus,
156            flawless_wasabi::HttpErrorKind::BadHeader => Self::BadHeader,
157            flawless_wasabi::HttpErrorKind::RequestInterrupted => Self::RequestInterrupted,
158            flawless_wasabi::HttpErrorKind::Io => Self::Io,
159            flawless_wasabi::HttpErrorKind::StatusCode => Self::StatusCode,
160        }
161    }
162}