use std::cell::RefCell;
use std::ffi::CString;
use std::os::raw::c_char;
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum rgpot_status_t {
RGPOT_SUCCESS = 0,
RGPOT_INVALID_PARAMETER = 1,
RGPOT_INTERNAL_ERROR = 2,
RGPOT_RPC_ERROR = 3,
RGPOT_BUFFER_SIZE_ERROR = 4,
}
thread_local! {
static LAST_ERROR: RefCell<CString> = RefCell::new(CString::default());
}
pub(crate) fn set_last_error(msg: &str) {
LAST_ERROR.with(|cell| {
let c = CString::new(msg).unwrap_or_else(|_| {
CString::new("(error message contained interior NUL)").unwrap()
});
*cell.borrow_mut() = c;
});
}
#[no_mangle]
pub unsafe extern "C" fn rgpot_last_error() -> *const c_char {
LAST_ERROR.with(|cell| cell.borrow().as_ptr())
}
pub(crate) fn catch_unwind<F>(f: F) -> rgpot_status_t
where
F: FnOnce() -> rgpot_status_t + std::panic::UnwindSafe,
{
match std::panic::catch_unwind(f) {
Ok(status) => status,
Err(e) => {
let msg = if let Some(s) = e.downcast_ref::<&str>() {
s.to_string()
} else if let Some(s) = e.downcast_ref::<String>() {
s.clone()
} else {
"unknown panic".to_string()
};
set_last_error(&msg);
rgpot_status_t::RGPOT_INTERNAL_ERROR
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_get_last_error() {
set_last_error("test error");
let ptr = unsafe { rgpot_last_error() };
let msg = unsafe { std::ffi::CStr::from_ptr(ptr) };
assert_eq!(msg.to_str().unwrap(), "test error");
}
#[test]
fn test_catch_unwind_success() {
let status = catch_unwind(|| rgpot_status_t::RGPOT_SUCCESS);
assert_eq!(status, rgpot_status_t::RGPOT_SUCCESS);
}
#[test]
fn test_catch_unwind_panic() {
let status = catch_unwind(|| panic!("boom"));
assert_eq!(status, rgpot_status_t::RGPOT_INTERNAL_ERROR);
let ptr = unsafe { rgpot_last_error() };
let msg = unsafe { std::ffi::CStr::from_ptr(ptr) };
assert_eq!(msg.to_str().unwrap(), "boom");
}
#[test]
fn test_error_overwrite() {
set_last_error("first");
set_last_error("second");
let ptr = unsafe { rgpot_last_error() };
let msg = unsafe { std::ffi::CStr::from_ptr(ptr) };
assert_eq!(msg.to_str().unwrap(), "second");
}
#[test]
fn test_interior_nul_is_handled() {
set_last_error("has\0interior nul");
let ptr = unsafe { rgpot_last_error() };
let msg = unsafe { std::ffi::CStr::from_ptr(ptr) };
assert_eq!(
msg.to_str().unwrap(),
"(error message contained interior NUL)"
);
}
#[test]
fn test_catch_unwind_returns_callback_status() {
let status = catch_unwind(|| rgpot_status_t::RGPOT_RPC_ERROR);
assert_eq!(status, rgpot_status_t::RGPOT_RPC_ERROR);
}
#[test]
fn test_catch_unwind_string_panic() {
let status = catch_unwind(|| panic!("{}", "formatted panic"));
assert_eq!(status, rgpot_status_t::RGPOT_INTERNAL_ERROR);
let ptr = unsafe { rgpot_last_error() };
let msg = unsafe { std::ffi::CStr::from_ptr(ptr) };
assert_eq!(msg.to_str().unwrap(), "formatted panic");
}
}