volo_http/server/
panic_handler.rs

1//! Collections for some panic handlers
2//!
3//! [`volo::catch_panic::Layer`] can handle panics in services and when panic occurs, it can
4//! respond a [`Response`]. This module has some useful handlers for handling panics and
5//! returning a response.
6
7use std::any::Any;
8
9use http::StatusCode;
10use motore::service::Service;
11use volo::catch_panic;
12
13use super::IntoResponse;
14use crate::response::Response;
15
16/// Panic handler which can return a fixed payload.
17///
18/// This type can be constructed by [`fixed_payload`].
19#[derive(Clone, Debug)]
20pub struct FixedPayload<R> {
21    payload: R,
22}
23
24impl<S, Cx, Req, Resp> catch_panic::Handler<S, Cx, Req> for FixedPayload<Resp>
25where
26    S: Service<Cx, Req, Response = Response> + Send + Sync + 'static,
27    Cx: Send + 'static,
28    Req: Send + 'static,
29    Resp: IntoResponse + Clone,
30{
31    fn handle(
32        &self,
33        _: &mut Cx,
34        _: Box<dyn Any + Send>,
35        panic_info: catch_panic::PanicInfo,
36    ) -> Result<S::Response, S::Error> {
37        tracing::error!("[Volo-HTTP] panic_handler: {panic_info}");
38        Ok(self.payload.clone().into_response())
39    }
40}
41
42/// This function is a panic handler and can work with [`volo::catch_panic::Layer`], it will always
43/// return `500 Internal Server Error`.
44pub fn always_internal_error<Cx, E>(
45    _: &mut Cx,
46    _: Box<dyn Any + Send>,
47    panic_info: catch_panic::PanicInfo,
48) -> Result<Response, E> {
49    tracing::error!("[Volo-HTTP] panic_handler: {panic_info}");
50    Ok(StatusCode::INTERNAL_SERVER_ERROR.into_response())
51}
52
53/// Create a panic handler which can work with [`volo::catch_panic::Layer`]. The handler will
54/// always return the specified fixed payload as response.
55pub fn fixed_payload<R>(payload: R) -> FixedPayload<R> {
56    FixedPayload { payload }
57}
58
59#[cfg(test)]
60mod panic_handler_tests {
61    use std::{convert::Infallible, marker::PhantomData};
62
63    use http::status::StatusCode;
64    use motore::{layer::Layer, service::Service};
65
66    use super::{always_internal_error, fixed_payload};
67    use crate::{
68        body::{Body, BodyConversion},
69        request::Request,
70        response::Response,
71        server::{IntoResponse, test_helpers},
72    };
73
74    struct PanicService<R = Response, E = Infallible> {
75        _marker: PhantomData<fn(R, E)>,
76    }
77
78    impl<R, E> PanicService<R, E> {
79        const fn new() -> Self {
80            Self {
81                _marker: PhantomData,
82            }
83        }
84    }
85
86    impl<Cx, Req, Resp, E> Service<Cx, Req> for PanicService<Resp, E>
87    where
88        Cx: Send,
89        Req: Send,
90        Resp: Send,
91    {
92        type Response = Resp;
93        type Error = E;
94
95        async fn call(&self, _: &mut Cx, _: Req) -> Result<Self::Response, Self::Error> {
96            panic!("oops")
97        }
98    }
99
100    #[tokio::test]
101    async fn fixed_payload_test() {
102        const ERR_MSG: &str = "oops";
103
104        // it's ok
105        {
106            let srv =
107                volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
108                    .layer(test_helpers::DefaultService::<Response, Infallible>::new());
109            let resp = srv
110                .call(&mut test_helpers::empty_cx(), Request::<Body>::default())
111                .await
112                .into_response();
113            assert_eq!(resp.status(), StatusCode::OK);
114        }
115        // it panics
116        {
117            let srv =
118                volo::catch_panic::Layer::new(fixed_payload((StatusCode::NOT_FOUND, ERR_MSG)))
119                    .layer(PanicService::<Response, Infallible>::new());
120            let resp = srv
121                .call(&mut test_helpers::empty_cx(), Request::<Body>::default())
122                .await
123                .into_response();
124            assert_eq!(resp.status(), StatusCode::NOT_FOUND);
125            assert_eq!(resp.into_string().await.unwrap(), ERR_MSG);
126        }
127    }
128
129    #[tokio::test]
130    async fn internal_error_test() {
131        // it's ok
132        {
133            let srv = volo::catch_panic::Layer::new(always_internal_error)
134                .layer(test_helpers::DefaultService::<Response, Infallible>::new());
135            let resp = srv
136                .call(&mut test_helpers::empty_cx(), Request::<Body>::default())
137                .await
138                .into_response();
139            assert_eq!(resp.status(), StatusCode::OK);
140        }
141        // it panics
142        {
143            let srv = volo::catch_panic::Layer::new(always_internal_error).layer(PanicService::<
144                Response,
145                Infallible,
146            >::new(
147            ));
148            let resp = srv
149                .call(&mut test_helpers::empty_cx(), Request::<Body>::default())
150                .await
151                .into_response();
152            assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
153        }
154    }
155}