beekeeper/
panic.rs

1//! Data type that wraps a `panic` payload.
2use super::boxed::BoxedFnOnce;
3use derive_more::Debug;
4use std::any::Any;
5use std::fmt;
6use std::panic::AssertUnwindSafe;
7
8pub type PanicPayload = Box<dyn Any + Send + 'static>;
9
10/// Wraps a payload from a caught `panic` with an optional `detail`.
11#[derive(Debug)]
12pub struct Panic<T: Send + fmt::Debug + Eq> {
13    #[debug("<panic! arg>")]
14    payload: PanicPayload,
15    detail: Option<T>,
16}
17
18impl<T: Send + fmt::Debug + Eq> Panic<T> {
19    /// Attempts to call the provided function `f` and catches any panic. Returns either the return
20    /// value of the function or a `Panic` created from the panic payload and the provided `detail`.
21    pub fn try_call<O, F: FnOnce() -> O>(detail: Option<T>, f: F) -> Result<O, Self> {
22        std::panic::catch_unwind(AssertUnwindSafe(f)).map_err(|payload| Self { payload, detail })
23    }
24
25    pub(crate) fn try_call_boxed<O, F: BoxedFnOnce<Output = O> + ?Sized>(
26        detail: Option<T>,
27        f: Box<F>,
28    ) -> Result<O, Self> {
29        std::panic::catch_unwind(AssertUnwindSafe(|| f.call_box()))
30            .map_err(|payload| Self { payload, detail })
31    }
32
33    /// Returns the payload of the panic.
34    pub fn payload(&self) -> &PanicPayload {
35        &self.payload
36    }
37
38    /// Returns the optional detail of the panic.
39    pub fn detail(&self) -> Option<&T> {
40        self.detail.as_ref()
41    }
42
43    /// Consumes this `Panic` and resumes unwinding the thread.
44    pub fn resume(self) -> ! {
45        std::panic::resume_unwind(self.payload)
46    }
47}
48
49impl<T: Send + fmt::Debug + Eq> PartialEq for Panic<T> {
50    fn eq(&self, other: &Self) -> bool {
51        (*self.payload).type_id() == (*other.payload).type_id() && self.detail == other.detail
52    }
53}
54
55impl<T: Send + fmt::Debug + Eq> Eq for Panic<T> {}
56
57#[cfg(test)]
58#[cfg_attr(coverage_nightly, coverage(off))]
59mod tests {
60    use super::Panic;
61    use std::fmt::Debug;
62
63    impl<T: Send + Debug + Eq> Panic<T> {
64        /// Panics with `msg` and immediately catches it to create a new `Panic` instance for testing.
65        pub fn new(msg: &str, detail: Option<T>) -> Self {
66            let payload = std::panic::catch_unwind(|| panic!("{}", msg))
67                .err()
68                .unwrap();
69            Self { payload, detail }
70        }
71    }
72
73    #[test]
74    fn test_catch_panic() {
75        let result = Panic::try_call("test".into(), || panic!("panic!"));
76        let panic = result.unwrap_err();
77        assert_eq!(*panic.detail().unwrap(), "test");
78    }
79
80    #[test]
81    #[should_panic]
82    fn test_resume_panic() {
83        let result = Panic::try_call("test".into(), || panic!("panic!"));
84        let panic = result.unwrap_err();
85        panic.resume();
86    }
87}