use std::any::Any;
use std::panic::{self, UnwindSafe};
use failure::Error;
use error_handling;
const DEFAULT_PANIC_MSG: &str = "The program panicked";
#[macro_export]
macro_rules! catch_panic {
($($tokens:tt)*) => {{
let result = $crate::catch_panic(|| { $($tokens)* });
match result {
Ok(value) => value,
Err(_) => return $crate::Nullable::NULL,
}
}};
}
pub fn catch_panic<T, F>(func: F) -> Result<T, ()>
where
F: FnOnce() -> Result<T, Error> + UnwindSafe,
{
let result = panic::catch_unwind(func)
.map_err(|e| {
let panic_msg =
recover_panic_message(e).unwrap_or_else(|| DEFAULT_PANIC_MSG.to_string());
Error::from(Panic::new(panic_msg))
})
.and_then(|v| v);
match result {
Ok(v) => Ok(v),
Err(e) => {
error_handling::update_last_error(e);
Err(())
}
}
}
#[derive(Debug, Clone, PartialEq, Fail)]
#[fail(display = "Panic: {}", message)]
pub struct Panic {
pub message: String,
}
impl Panic {
fn new<S: Into<String>>(msg: S) -> Panic {
Panic {
message: msg.into(),
}
}
}
pub fn recover_panic_message(e: Box<Any + Send + 'static>) -> Option<String> {
if let Some(msg) = e.downcast_ref::<String>() {
Some(msg.clone())
} else if let Some(msg) = e.downcast_ref::<&str>() {
Some(msg.to_string())
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use error_handling::*;
#[test]
fn able_to_catch_panics_and_recover_the_panic_message() {
let _ = take_last_error();
let err_msg = "Miscellaneous panic message";
let got: Result<(), ()> = catch_panic(|| panic!(err_msg));
assert!(got.is_err());
let got_error = take_last_error().unwrap();
match got_error.downcast_ref::<Panic>() {
Some(p) => assert_eq!(p.message, err_msg),
_ => unreachable!(),
}
}
}