Skip to main content

ntex_http/
error.rs

1use std::{error, fmt};
2
3pub use crate::value::{InvalidHeaderValue, ToStrError};
4
5#[derive(Copy, Clone, PartialEq, Eq, Hash)]
6/// A generic error for HTTP value conversions.
7///
8/// This error type is less specific than the error returned from other
9/// functions in this crate, but all other errors can be converted to this
10/// error. Consumers of this crate can typically consume and work with this form
11/// of error for conversions with the `?` operator.
12pub struct Error {
13    inner: ErrorKind,
14}
15
16#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
17#[error("Invalid URI")]
18/// An error resulting from a failed attempt to construct a URI.
19pub struct InvalidUri {
20    _priv: (),
21}
22
23#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
24#[error("Invalid HTTP header name")]
25/// A possible error when converting a `HeaderName` from another type.
26pub struct InvalidHeaderName {
27    _priv: (),
28}
29
30#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
31#[error("Invalid status code")]
32/// A possible error value when converting a `StatusCode` from a `u16` or `&str`.
33///
34/// This error indicates that the supplied input was not a valid number, was less
35/// than 100, or was greater than 999.
36pub struct InvalidStatusCode {
37    _priv: (),
38}
39
40#[derive(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
41#[error("Invalid HTTP method")]
42/// A possible error value when converting `Method` from bytes.
43pub struct InvalidMethod {
44    _priv: (),
45}
46
47#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
48enum ErrorKind {
49    StatusCode(InvalidStatusCode),
50    Method(InvalidMethod),
51    Uri(InvalidUri),
52    UriParts(InvalidUri),
53    HeaderName(InvalidHeaderName),
54    HeaderValue(InvalidHeaderValue),
55    Http,
56}
57
58impl fmt::Debug for Error {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        f.debug_tuple("ntex_http::Error")
61            // Skip the noise of the ErrorKind enum
62            .field(&self.get_ref())
63            .finish()
64    }
65}
66
67impl fmt::Display for Error {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        fmt::Display::fmt(self.get_ref(), f)
70    }
71}
72
73impl Error {
74    /// Returns `true` if the underlying error has type `T`.
75    pub fn is<T: error::Error + 'static>(&self) -> bool {
76        self.get_ref().is::<T>()
77    }
78
79    /// Returns the underlying conversion error.
80    pub fn get_ref(&self) -> &(dyn error::Error + 'static) {
81        match self.inner {
82            ErrorKind::StatusCode(ref e) => e,
83            ErrorKind::Method(ref e) => e,
84            ErrorKind::Uri(ref e) | ErrorKind::UriParts(ref e) => e,
85            ErrorKind::HeaderName(ref e) => e,
86            ErrorKind::HeaderValue(ref e) => e,
87            ErrorKind::Http => &DEFAULT_ERR,
88        }
89    }
90}
91
92#[derive(thiserror::Error, Copy, Clone, Debug)]
93#[error("{_0}")]
94struct ErrorMessage(&'static str);
95
96const DEFAULT_ERR: ErrorMessage = ErrorMessage("http error");
97
98impl error::Error for Error {
99    // Return any available cause from the inner error. Note the inner error is
100    // not itself the cause.
101    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
102        self.get_ref().source()
103    }
104}
105
106impl From<http::status::InvalidStatusCode> for Error {
107    fn from(_: http::status::InvalidStatusCode) -> Error {
108        Error {
109            inner: ErrorKind::StatusCode(InvalidStatusCode { _priv: () }),
110        }
111    }
112}
113
114impl From<http::method::InvalidMethod> for Error {
115    fn from(_: http::method::InvalidMethod) -> Error {
116        Error {
117            inner: ErrorKind::Method(InvalidMethod { _priv: () }),
118        }
119    }
120}
121
122impl From<http::uri::InvalidUri> for Error {
123    fn from(_: http::uri::InvalidUri) -> Error {
124        Error {
125            inner: ErrorKind::Uri(InvalidUri { _priv: () }),
126        }
127    }
128}
129
130impl From<http::uri::InvalidUriParts> for Error {
131    fn from(_: http::uri::InvalidUriParts) -> Error {
132        Error {
133            inner: ErrorKind::UriParts(InvalidUri { _priv: () }),
134        }
135    }
136}
137
138impl From<http::header::InvalidHeaderName> for Error {
139    fn from(_: http::header::InvalidHeaderName) -> Error {
140        Error {
141            inner: ErrorKind::HeaderName(InvalidHeaderName { _priv: () }),
142        }
143    }
144}
145
146impl From<InvalidHeaderValue> for Error {
147    fn from(err: InvalidHeaderValue) -> Error {
148        Error {
149            inner: ErrorKind::HeaderValue(err),
150        }
151    }
152}
153
154impl From<http::Error> for Error {
155    fn from(err: http::Error) -> Error {
156        let inner = if err.is::<http::status::InvalidStatusCode>() {
157            ErrorKind::StatusCode(InvalidStatusCode { _priv: () })
158        } else if err.is::<http::method::InvalidMethod>() {
159            ErrorKind::Method(InvalidMethod { _priv: () })
160        } else if err.is::<http::uri::InvalidUri>() {
161            ErrorKind::Uri(InvalidUri { _priv: () })
162        } else if err.is::<http::uri::InvalidUriParts>() {
163            ErrorKind::UriParts(InvalidUri { _priv: () })
164        } else if err.is::<http::header::InvalidHeaderName>() {
165            ErrorKind::HeaderName(InvalidHeaderName { _priv: () })
166        } else if err.is::<http::header::InvalidHeaderValue>() {
167            ErrorKind::HeaderValue(InvalidHeaderValue::default())
168        } else {
169            ErrorKind::Http
170        };
171        Error { inner }
172    }
173}
174
175impl From<std::convert::Infallible> for Error {
176    fn from(err: std::convert::Infallible) -> Error {
177        match err {}
178    }
179}
180
181impl From<http::status::InvalidStatusCode> for InvalidStatusCode {
182    fn from(_: http::status::InvalidStatusCode) -> InvalidStatusCode {
183        InvalidStatusCode { _priv: () }
184    }
185}
186
187impl From<http::method::InvalidMethod> for InvalidMethod {
188    fn from(_: http::method::InvalidMethod) -> InvalidMethod {
189        InvalidMethod { _priv: () }
190    }
191}
192
193impl From<http::uri::InvalidUri> for InvalidUri {
194    fn from(_: http::uri::InvalidUri) -> InvalidUri {
195        InvalidUri { _priv: () }
196    }
197}
198
199impl From<http::header::InvalidHeaderName> for InvalidHeaderName {
200    fn from(_: http::header::InvalidHeaderName) -> InvalidHeaderName {
201        InvalidHeaderName { _priv: () }
202    }
203}
204
205impl From<http::header::InvalidHeaderValue> for InvalidHeaderValue {
206    fn from(_: http::header::InvalidHeaderValue) -> InvalidHeaderValue {
207        InvalidHeaderValue::default()
208    }
209}
210
211#[cfg(test)]
212mod tests {
213    use super::*;
214    use std::error::Error as StdError;
215
216    #[test]
217    fn inner_http_error() {
218        let e = http::method::Method::from_bytes(b"").unwrap_err();
219        let err: Error = http::Error::from(e).into();
220        let ie = err.get_ref();
221        assert!(!ie.is::<http::Error>());
222    }
223
224    #[test]
225    fn inner_error_is_invalid_status_code() {
226        let e = http::status::StatusCode::from_u16(6666).unwrap_err();
227        let err: Error = e.into();
228        let ie = err.get_ref();
229        assert!(!ie.is::<InvalidHeaderValue>());
230        assert!(ie.is::<InvalidStatusCode>());
231        ie.downcast_ref::<InvalidStatusCode>().unwrap();
232
233        assert!(err.source().is_none());
234        assert!(!err.is::<InvalidHeaderValue>());
235        assert!(err.is::<InvalidStatusCode>());
236
237        let s = format!("{err:?}");
238        assert!(s.starts_with("ntex_http::Error"));
239    }
240
241    #[test]
242    fn inner_error_is_invalid_method() {
243        let e = http::method::Method::from_bytes(b"").unwrap_err();
244        let err: Error = e.into();
245        let ie = err.get_ref();
246        assert!(ie.is::<InvalidMethod>());
247        ie.downcast_ref::<InvalidMethod>().unwrap();
248
249        assert!(err.source().is_none());
250        assert!(err.is::<InvalidMethod>());
251
252        let s = format!("{err:?}");
253        assert!(s.starts_with("ntex_http::Error"));
254    }
255
256    #[test]
257    fn other_http_error() {
258        // http errors without a dedicated kind convert to a generic error
259        let e = http::HeaderMap::<http::HeaderValue>::new()
260            .try_reserve(usize::MAX)
261            .unwrap_err();
262        let err: Error = http::Error::from(e).into();
263        assert_eq!(err.to_string(), "http error");
264        assert!(err.source().is_none());
265        assert!(!err.is::<InvalidStatusCode>());
266        assert_eq!(
267            format!("{err:?}"),
268            "ntex_http::Error(ErrorMessage(\"http error\"))"
269        );
270    }
271}