use std::cell::RefCell;
use std::ffi::CString;
use std::ptr;
thread_local! {
static LAST_ERROR: RefCell<Option<String>> = const { RefCell::new(None) };
static ERROR_CSTRING: RefCell<Option<CString>> = const { RefCell::new(None) };
}
pub fn set_runtime_error(msg: impl Into<String>) {
ERROR_CSTRING.with(|cs| *cs.borrow_mut() = None);
LAST_ERROR.with(|e| {
*e.borrow_mut() = Some(msg.into());
});
}
pub fn take_runtime_error() -> Option<String> {
LAST_ERROR.with(|e| e.borrow_mut().take())
}
pub fn has_runtime_error() -> bool {
LAST_ERROR.with(|e| e.borrow().is_some())
}
pub fn clear_runtime_error() {
LAST_ERROR.with(|e| *e.borrow_mut() = None);
ERROR_CSTRING.with(|e| *e.borrow_mut() = None);
}
#[unsafe(no_mangle)]
pub extern "C" fn patch_seq_has_error() -> bool {
has_runtime_error()
}
#[unsafe(no_mangle)]
pub extern "C" fn patch_seq_get_error() -> *const i8 {
LAST_ERROR.with(|e| {
let error = e.borrow();
match &*error {
Some(msg) => {
ERROR_CSTRING.with(|cs| {
let safe_msg: String = msg
.chars()
.map(|c| if c == '\0' { '?' } else { c })
.collect();
let cstring = CString::new(safe_msg).expect("null bytes already replaced");
let ptr = cstring.as_ptr();
*cs.borrow_mut() = Some(cstring);
ptr
})
}
None => ptr::null(),
}
})
}
#[unsafe(no_mangle)]
pub extern "C" fn patch_seq_take_error() -> *const i8 {
let msg = take_runtime_error();
match msg {
Some(s) => ERROR_CSTRING.with(|cs| {
let safe_msg: String = s.chars().map(|c| if c == '\0' { '?' } else { c }).collect();
let cstring = CString::new(safe_msg).expect("null bytes already replaced");
let ptr = cstring.as_ptr();
*cs.borrow_mut() = Some(cstring);
ptr
}),
None => ptr::null(),
}
}
#[unsafe(no_mangle)]
pub extern "C" fn patch_seq_clear_error() {
clear_runtime_error();
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_take_error() {
clear_runtime_error();
assert!(!has_runtime_error());
set_runtime_error("test error");
assert!(has_runtime_error());
let error = take_runtime_error();
assert_eq!(error, Some("test error".to_string()));
assert!(!has_runtime_error());
}
#[test]
fn test_clear_error() {
set_runtime_error("another error");
assert!(has_runtime_error());
clear_runtime_error();
assert!(!has_runtime_error());
assert!(take_runtime_error().is_none());
}
}