gotham/handler/
error.rs

1use futures_util::future::FusedFuture;
2use std::fmt::{Debug, Display};
3use std::future::Future;
4use std::pin::Pin;
5use std::task::{Context, Poll};
6
7use hyper::{Body, Response, StatusCode};
8use log::{debug, trace};
9
10use crate::handler::IntoResponse;
11use crate::helpers::http::response::create_empty_response;
12use crate::state::{request_id, State};
13
14/// Describes an error which occurred during handler execution, and allows the creation of a HTTP
15/// `Response`.
16#[derive(Debug)]
17pub struct HandlerError {
18    status_code: StatusCode,
19    cause: anyhow::Error,
20}
21
22/// Convert a generic `anyhow::Error` into a `HandlerError`, similar as you would a concrete error
23/// type with `into_handler_error()`.
24impl<E> From<E> for HandlerError
25where
26    E: Into<anyhow::Error> + Display,
27{
28    fn from(error: E) -> HandlerError {
29        trace!(" converting Error to HandlerError: {}", error);
30
31        HandlerError {
32            status_code: StatusCode::INTERNAL_SERVER_ERROR,
33            cause: error.into(),
34        }
35    }
36}
37
38impl HandlerError {
39    /// Returns the HTTP status code associated with this `HandlerError`.
40    pub fn status(&self) -> StatusCode {
41        self.status_code
42    }
43
44    /// Sets the HTTP status code of the response which is generated by the `IntoResponse`
45    /// implementation.
46    ///
47    /// ```rust
48    /// # use std::pin::Pin;
49    /// #
50    /// # use futures_util::future::{self, FutureExt};
51    /// # use hyper::StatusCode;
52    /// # use gotham::state::State;
53    /// # use gotham::handler::{HandlerError, HandlerFuture};
54    /// # use gotham::test::TestServer;
55    /// #
56    /// fn handler(state: State) -> Pin<Box<HandlerFuture>> {
57    ///     // It's OK if this is bogus, we just need something to convert into a `HandlerError`.
58    ///     let io_error = std::io::Error::last_os_error();
59    ///
60    ///     let handler_error = HandlerError::from(io_error).with_status(StatusCode::IM_A_TEAPOT);
61    ///
62    ///     future::err((state, handler_error)).boxed()
63    /// }
64    ///
65    /// # fn main() {
66    /// #
67    /// let test_server = TestServer::new(|| Ok(handler)).unwrap();
68    /// let response = test_server
69    ///     .client()
70    ///     .get("http://example.com/")
71    ///     .perform()
72    ///     .unwrap();
73    /// assert_eq!(response.status(), StatusCode::IM_A_TEAPOT);
74    /// #
75    /// # }
76    /// ```
77    pub fn with_status(self, status_code: StatusCode) -> HandlerError {
78        HandlerError {
79            status_code,
80            ..self
81        }
82    }
83
84    /// Returns the cause of this error by reference.
85    pub fn cause(&self) -> &anyhow::Error {
86        &self.cause
87    }
88
89    /// Returns the cause of this error.
90    pub fn into_cause(self) -> anyhow::Error {
91        self.cause
92    }
93
94    /// Attempt to downcast the cause by reference.
95    pub fn downcast_cause_ref<E>(&self) -> Option<&E>
96    where
97        E: Display + Debug + Send + Sync + 'static,
98    {
99        self.cause.downcast_ref()
100    }
101
102    /// Attempt to downcast the cause by mutable reference.
103    pub fn downcast_cause_mut<E>(&mut self) -> Option<&mut E>
104    where
105        E: Display + Debug + Send + Sync + 'static,
106    {
107        self.cause.downcast_mut()
108    }
109}
110
111impl IntoResponse for HandlerError {
112    fn into_response(self, state: &State) -> Response<Body> {
113        debug!(
114            "[{}] HandlerError generating {} {} response: {}",
115            request_id(state),
116            self.status_code.as_u16(),
117            self.status_code
118                .canonical_reason()
119                .unwrap_or("(unregistered)",),
120            self.cause
121        );
122
123        create_empty_response(state, self.status_code)
124    }
125}
126
127/// This trait allows you to convert a `Result`'s `Err` case into a handler error with the given
128/// status code. This is handy if you want to specify the status code but still use the `?`
129/// shorthand.
130///
131/// ```rust
132/// # extern crate gotham;
133/// # use gotham::anyhow::anyhow;
134/// # use gotham::handler::{HandlerError, MapHandlerError};
135/// # use gotham::hyper::StatusCode;
136/// fn handler() -> Result<(), HandlerError> {
137///     let result = Err(anyhow!("just a test"));
138///     result.map_err_with_status(StatusCode::IM_A_TEAPOT)?;
139///     unreachable!()
140/// }
141///
142/// # #[allow(non_snake_case)]
143/// # fn Err<T>(err: T) -> Result<(), T> {
144/// #   Result::Err(err)
145/// # }
146/// # fn main() {
147/// let response = handler();
148/// assert_eq!(
149///     response.map_err(|err| err.status()),
150///     Err(StatusCode::IM_A_TEAPOT)
151/// );
152/// # }
153/// ```
154pub trait MapHandlerError<T> {
155    /// Equivalent of `map_err(|err| HandlerError::from(err).with_status(status_code))`.
156    fn map_err_with_status(self, status_code: StatusCode) -> Result<T, HandlerError>;
157}
158
159impl<T, E> MapHandlerError<T> for Result<T, E>
160where
161    E: Into<anyhow::Error> + Display,
162{
163    fn map_err_with_status(self, status_code: StatusCode) -> Result<T, HandlerError> {
164        self.map_err(|err| {
165            trace!(" converting Error to HandlerError: {}", err);
166            HandlerError {
167                status_code,
168                cause: err.into(),
169            }
170        })
171    }
172}
173
174// The future for `map_err_with_status`.
175#[pin_project::pin_project(project = MapErrWithStatusProj, project_replace = MapErrWithStatusProjOwn)]
176#[derive(Debug)]
177#[must_use = "futures do nothing unless you `.await` or poll them"]
178pub enum MapErrWithStatus<F> {
179    Incomplete {
180        #[pin]
181        future: F,
182        status: StatusCode,
183    },
184    Complete,
185}
186
187impl<F> MapErrWithStatus<F> {
188    fn new(future: F, status: StatusCode) -> Self {
189        Self::Incomplete { future, status }
190    }
191}
192
193impl<F, T, E> FusedFuture for MapErrWithStatus<F>
194where
195    F: Future<Output = Result<T, E>>,
196    E: Into<anyhow::Error> + Display,
197{
198    fn is_terminated(&self) -> bool {
199        matches!(self, Self::Complete)
200    }
201}
202
203impl<F, T, E> Future for MapErrWithStatus<F>
204where
205    F: Future<Output = Result<T, E>>,
206    E: Into<anyhow::Error> + Display,
207{
208    type Output = Result<T, HandlerError>;
209
210    fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
211        match self.as_mut().project() {
212            MapErrWithStatusProj::Incomplete { future, .. } => {
213                let output = match future.poll(cx) {
214                    Poll::Ready(output) => output,
215                    Poll::Pending => return Poll::Pending,
216                };
217                match self.project_replace(MapErrWithStatus::Complete) {
218                    MapErrWithStatusProjOwn::Incomplete { status, .. } => {
219                        Poll::Ready(output.map_err_with_status(status))
220                    }
221                    MapErrWithStatusProjOwn::Complete => unreachable!(),
222                }
223            }
224            MapErrWithStatusProj::Complete => {
225                panic!("MapErrWithStatus must not be polled after it returned `Poll::Ready`")
226            }
227        }
228    }
229}
230
231/// This trait allows you to convert a `Result`'s `Err` case into a handler error with the given
232/// status code. This is handy if you want to specify the status code but still use the `?`
233/// shorthand.
234/// ```rust
235/// # use futures_executor::block_on;
236/// # use gotham::anyhow::anyhow;
237/// # use gotham::handler::{HandlerError, MapHandlerErrorFuture};
238/// # use gotham::hyper::StatusCode;
239/// # use std::future::Future;
240/// fn handler() -> impl Future<Output = Result<(), HandlerError>> {
241///     let result = async { Err(anyhow!("just a test")) };
242///     result.map_err_with_status(StatusCode::IM_A_TEAPOT)
243/// }
244///
245/// # #[allow(non_snake_case)]
246/// # fn Err<T>(err: T) -> Result<(), T> {
247/// #   Result::Err(err)
248/// # }
249/// # fn main() {
250/// let response = block_on(handler());
251/// assert_eq!(
252///     response.map_err(|err| err.status()),
253///     Err(StatusCode::IM_A_TEAPOT)
254/// );
255/// # }
256/// ```
257pub trait MapHandlerErrorFuture {
258    /// Equivalent of `map_err(|err| HandlerError::from(err).with_status(status_code))`.
259    fn map_err_with_status(self, status_code: StatusCode) -> MapErrWithStatus<Self>
260    where
261        Self: Sized;
262}
263
264impl<T, E, F> MapHandlerErrorFuture for F
265where
266    E: Into<anyhow::Error> + Display,
267    F: Future<Output = Result<T, E>>,
268{
269    fn map_err_with_status(self, status_code: StatusCode) -> MapErrWithStatus<Self> {
270        MapErrWithStatus::new(self, status_code)
271    }
272}
273
274#[cfg(test)]
275mod test {
276    use super::*;
277    use std::io;
278    use thiserror::Error;
279
280    #[derive(Debug, Error)]
281    #[error("Dummy Error")]
282    struct DummyError;
283
284    fn error_prone() -> Result<(), HandlerError> {
285        Err(DummyError.into())
286    }
287
288    #[test]
289    fn test_error_downcast() {
290        let mut err = error_prone().unwrap_err();
291        assert!(err.downcast_cause_ref::<DummyError>().is_some());
292        assert!(err.downcast_cause_mut::<DummyError>().is_some());
293        assert!(err.downcast_cause_ref::<io::Error>().is_none());
294        assert!(err.downcast_cause_mut::<io::Error>().is_none());
295    }
296}