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(thiserror::Error, Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
48#[error("Unsupported content encoding")]
49/// An error returned when parsing an unknown or empty `ContentEncoding` name.
50pub struct InvalidContentEncoding {
51    _priv: (),
52}
53
54impl InvalidContentEncoding {
55    pub(crate) const fn new() -> Self {
56        InvalidContentEncoding { _priv: () }
57    }
58}
59
60#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
61enum ErrorKind {
62    StatusCode(InvalidStatusCode),
63    Method(InvalidMethod),
64    Uri(InvalidUri),
65    UriParts(InvalidUri),
66    HeaderName(InvalidHeaderName),
67    HeaderValue(InvalidHeaderValue),
68    ContentEncoding(InvalidContentEncoding),
69    Http,
70}
71
72impl fmt::Debug for Error {
73    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
74        f.debug_tuple("ntex_http::Error")
75            // Skip the noise of the ErrorKind enum
76            .field(&self.get_ref())
77            .finish()
78    }
79}
80
81impl fmt::Display for Error {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        fmt::Display::fmt(self.get_ref(), f)
84    }
85}
86
87impl Error {
88    /// Returns `true` if the underlying error has type `T`.
89    pub fn is<T: error::Error + 'static>(&self) -> bool {
90        self.get_ref().is::<T>()
91    }
92
93    /// Returns the underlying conversion error.
94    pub fn get_ref(&self) -> &(dyn error::Error + 'static) {
95        match self.inner {
96            ErrorKind::StatusCode(ref e) => e,
97            ErrorKind::Method(ref e) => e,
98            ErrorKind::Uri(ref e) | ErrorKind::UriParts(ref e) => e,
99            ErrorKind::HeaderName(ref e) => e,
100            ErrorKind::HeaderValue(ref e) => e,
101            ErrorKind::ContentEncoding(ref e) => e,
102            ErrorKind::Http => &DEFAULT_ERR,
103        }
104    }
105}
106
107#[derive(thiserror::Error, Copy, Clone, Debug)]
108#[error("{_0}")]
109struct ErrorMessage(&'static str);
110
111const DEFAULT_ERR: ErrorMessage = ErrorMessage("http error");
112
113impl error::Error for Error {
114    // Return any available cause from the inner error. Note the inner error is
115    // not itself the cause.
116    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
117        self.get_ref().source()
118    }
119}
120
121impl From<http::status::InvalidStatusCode> for Error {
122    fn from(_: http::status::InvalidStatusCode) -> Error {
123        Error {
124            inner: ErrorKind::StatusCode(InvalidStatusCode { _priv: () }),
125        }
126    }
127}
128
129impl From<http::method::InvalidMethod> for Error {
130    fn from(_: http::method::InvalidMethod) -> Error {
131        Error {
132            inner: ErrorKind::Method(InvalidMethod { _priv: () }),
133        }
134    }
135}
136
137impl From<http::uri::InvalidUri> for Error {
138    fn from(_: http::uri::InvalidUri) -> Error {
139        Error {
140            inner: ErrorKind::Uri(InvalidUri { _priv: () }),
141        }
142    }
143}
144
145impl From<http::uri::InvalidUriParts> for Error {
146    fn from(_: http::uri::InvalidUriParts) -> Error {
147        Error {
148            inner: ErrorKind::UriParts(InvalidUri { _priv: () }),
149        }
150    }
151}
152
153impl From<http::header::InvalidHeaderName> for Error {
154    fn from(_: http::header::InvalidHeaderName) -> Error {
155        Error {
156            inner: ErrorKind::HeaderName(InvalidHeaderName { _priv: () }),
157        }
158    }
159}
160
161impl From<InvalidHeaderValue> for Error {
162    fn from(err: InvalidHeaderValue) -> Error {
163        Error {
164            inner: ErrorKind::HeaderValue(err),
165        }
166    }
167}
168
169impl From<InvalidContentEncoding> for Error {
170    fn from(err: InvalidContentEncoding) -> Error {
171        Error {
172            inner: ErrorKind::ContentEncoding(err),
173        }
174    }
175}
176
177impl From<http::Error> for Error {
178    fn from(err: http::Error) -> Error {
179        let inner = if err.is::<http::status::InvalidStatusCode>() {
180            ErrorKind::StatusCode(InvalidStatusCode { _priv: () })
181        } else if err.is::<http::method::InvalidMethod>() {
182            ErrorKind::Method(InvalidMethod { _priv: () })
183        } else if err.is::<http::uri::InvalidUri>() {
184            ErrorKind::Uri(InvalidUri { _priv: () })
185        } else if err.is::<http::uri::InvalidUriParts>() {
186            ErrorKind::UriParts(InvalidUri { _priv: () })
187        } else if err.is::<http::header::InvalidHeaderName>() {
188            ErrorKind::HeaderName(InvalidHeaderName { _priv: () })
189        } else if err.is::<http::header::InvalidHeaderValue>() {
190            ErrorKind::HeaderValue(InvalidHeaderValue::default())
191        } else {
192            ErrorKind::Http
193        };
194        Error { inner }
195    }
196}
197
198impl From<std::convert::Infallible> for Error {
199    fn from(err: std::convert::Infallible) -> Error {
200        match err {}
201    }
202}
203
204impl From<http::status::InvalidStatusCode> for InvalidStatusCode {
205    fn from(_: http::status::InvalidStatusCode) -> InvalidStatusCode {
206        InvalidStatusCode { _priv: () }
207    }
208}
209
210impl From<http::method::InvalidMethod> for InvalidMethod {
211    fn from(_: http::method::InvalidMethod) -> InvalidMethod {
212        InvalidMethod { _priv: () }
213    }
214}
215
216impl From<http::uri::InvalidUri> for InvalidUri {
217    fn from(_: http::uri::InvalidUri) -> InvalidUri {
218        InvalidUri { _priv: () }
219    }
220}
221
222impl From<http::header::InvalidHeaderName> for InvalidHeaderName {
223    fn from(_: http::header::InvalidHeaderName) -> InvalidHeaderName {
224        InvalidHeaderName { _priv: () }
225    }
226}
227
228impl From<http::header::InvalidHeaderValue> for InvalidHeaderValue {
229    fn from(_: http::header::InvalidHeaderValue) -> InvalidHeaderValue {
230        InvalidHeaderValue::default()
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237    use std::error::Error as StdError;
238
239    #[test]
240    fn inner_http_error() {
241        let e = http::method::Method::from_bytes(b"").unwrap_err();
242        let err: Error = http::Error::from(e).into();
243        let ie = err.get_ref();
244        assert!(!ie.is::<http::Error>());
245    }
246
247    #[test]
248    fn inner_error_is_invalid_status_code() {
249        let e = http::status::StatusCode::from_u16(6666).unwrap_err();
250        let err: Error = e.into();
251        let ie = err.get_ref();
252        assert!(!ie.is::<InvalidHeaderValue>());
253        assert!(ie.is::<InvalidStatusCode>());
254        ie.downcast_ref::<InvalidStatusCode>().unwrap();
255
256        assert!(err.source().is_none());
257        assert!(!err.is::<InvalidHeaderValue>());
258        assert!(err.is::<InvalidStatusCode>());
259
260        let s = format!("{err:?}");
261        assert!(s.starts_with("ntex_http::Error"));
262    }
263
264    #[test]
265    fn inner_error_is_invalid_method() {
266        let e = http::method::Method::from_bytes(b"").unwrap_err();
267        let err: Error = e.into();
268        let ie = err.get_ref();
269        assert!(ie.is::<InvalidMethod>());
270        ie.downcast_ref::<InvalidMethod>().unwrap();
271
272        assert!(err.source().is_none());
273        assert!(err.is::<InvalidMethod>());
274
275        let s = format!("{err:?}");
276        assert!(s.starts_with("ntex_http::Error"));
277    }
278
279    #[test]
280    fn other_http_error() {
281        // http errors without a dedicated kind convert to a generic error
282        let e = http::HeaderMap::<http::HeaderValue>::new()
283            .try_reserve(usize::MAX)
284            .unwrap_err();
285        let err: Error = http::Error::from(e).into();
286        assert_eq!(err.to_string(), "http error");
287        assert!(err.source().is_none());
288        assert!(!err.is::<InvalidStatusCode>());
289        assert_eq!(
290            format!("{err:?}"),
291            "ntex_http::Error(ErrorMessage(\"http error\"))"
292        );
293    }
294}