use std::panic::{AssertUnwindSafe, catch_unwind};
use crate::engine::EngineError;
const PANIC_LOG_MSG: &str = "ffi boundary caught panic from storage engine";
#[derive(Debug)]
#[non_exhaustive]
pub struct FfiBoundary;
impl FfiBoundary {
#[inline]
pub fn run<F>(f: F) -> i32
where
F: FnOnce() -> Result<(), EngineError>,
{
match catch_unwind(AssertUnwindSafe(f)) {
Ok(Ok(())) => 0,
Ok(Err(e)) => e.to_mysql_errno(),
Err(_) => {
tracing::error!("{PANIC_LOG_MSG}");
EngineError::Internal.to_mysql_errno()
}
}
}
#[inline]
pub fn run_void<F>(f: F)
where
F: FnOnce(),
{
match catch_unwind(AssertUnwindSafe(f)) {
Ok(()) => {}
Err(_) => tracing::error!("{PANIC_LOG_MSG}"),
}
}
#[inline]
pub fn run_default<T, F>(default: T, f: F) -> T
where
F: FnOnce() -> T,
{
match catch_unwind(AssertUnwindSafe(f)) {
Ok(v) => v,
Err(_) => {
tracing::error!("{PANIC_LOG_MSG}");
default
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sys;
#[test]
fn ok_closure_returns_zero() {
assert_eq!(FfiBoundary::run(|| Ok(())), 0);
}
#[test]
fn err_closure_returns_mapped_errno() {
assert_eq!(
FfiBoundary::run(|| Err(EngineError::EndOfFile)),
sys::HA_ERR_END_OF_FILE
);
}
#[test]
fn panicking_closure_returns_internal_error() {
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
let result = FfiBoundary::run(|| panic!("intentional panic for test"));
std::panic::set_hook(prev_hook);
assert_eq!(result, sys::HA_ERR_INTERNAL_ERROR);
}
#[test]
fn panicking_void_closure_does_not_abort() {
let prev_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(|_| {}));
FfiBoundary::run_void(|| panic!("intentional panic for test"));
std::panic::set_hook(prev_hook);
}
}