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}