volo_http/server/
panic_handler.rs1use 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#[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
42pub 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
53pub 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 {
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 {
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 {
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 {
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}