1use std::future::Future;
22use std::panic::AssertUnwindSafe;
23
24use futures::FutureExt;
25
26fn log_panic(handler_name: &'static str, panic: Box<dyn std::any::Any + Send>) {
27 let msg = if let Some(s) = panic.downcast_ref::<&'static str>() {
28 (*s).to_string()
29 } else if let Some(s) = panic.downcast_ref::<String>() {
30 s.clone()
31 } else {
32 "non-string panic payload".to_string()
33 };
34 tracing::error!(handler = handler_name, panic = msg, "handler panicked");
35}
36
37pub async fn guard_async<F, R>(handler_name: &'static str, fut: F) -> R
41where
42 F: Future<Output = R>,
43 R: Default,
44{
45 match AssertUnwindSafe(fut).catch_unwind().await {
46 Ok(r) => r,
47 Err(panic) => {
48 log_panic(handler_name, panic);
49 R::default()
50 }
51 }
52}
53
54pub async fn guard_async_result<F, R, E>(handler_name: &'static str, fut: F) -> Result<R, E>
59where
60 F: Future<Output = Result<R, E>>,
61 R: Default,
62{
63 match AssertUnwindSafe(fut).catch_unwind().await {
64 Ok(r) => r,
65 Err(panic) => {
66 log_panic(handler_name, panic);
67 Ok(R::default())
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[tokio::test]
77 async fn returns_value_when_no_panic() {
78 let r: i32 = guard_async("test", async { 42 }).await;
79 assert_eq!(r, 42);
80 }
81
82 #[tokio::test]
83 async fn returns_default_on_panic() {
84 let r: i32 = guard_async("test", async { panic!("boom") }).await;
85 assert_eq!(r, 0);
86 }
87
88 #[tokio::test]
89 async fn returns_default_on_panic_for_option() {
90 let r: Option<i32> = guard_async("test", async { panic!("boom") }).await;
91 assert_eq!(r, None);
92 }
93
94 #[tokio::test]
95 async fn result_returns_ok_default_on_panic() {
96 let r: Result<Option<i32>, &str> =
97 guard_async_result("test", async { panic!("boom") }).await;
98 assert_eq!(r, Ok(None));
99 }
100
101 #[tokio::test]
102 async fn result_propagates_err_when_no_panic() {
103 let r: Result<Option<i32>, &str> = guard_async_result("test", async { Err("nope") }).await;
104 assert_eq!(r, Err("nope"));
105 }
106}