//! Thread-local storage for the outcome of the most recent FFI call on this thread -- both its //! error *code* and its human-readable *message*. //! //! This is closer in spirit to Win32 `FormatMessage` or SQLite's `sqlite3_errmsg` than to //! `GetLastError`: the conventional pattern is for the FFI function to *return* the code as its //! return value (see [`get_error_code`]) while this module retains the matching message text for //! later retrieval (see [`get_error_message`]). The code is also retained here so it can be //! queried independently of the return value. //! //! # Call order //! //! The expected usage at each FFI function entry is: //! //! 1. Call [`clear_last_error`] to reset the thread state to absolute success (code = `None`, //! empty message). **This must be done at the entry of every FFI function**; the default state //! is "absolute success". //! 2. On failure, call [`set_last_error`] with the error value; this records the code (via //! `Into`) and derives the message (via `Display`). //! 3. The FFI function returns [`get_error_code`] (absolute success if nothing was set, otherwise //! the recorded code). The foreign side may call [`get_error_message`] for the text. //! //! The `cffi_wrapper!` macro automates this whole sequence. //! //! # The error code type //! //! [`CError`] is defined here as a plain `u32`. [`CERROR_OK`] is the single value that represents //! absolute, unambiguous success. Every other value's meaning is decided by the consuming project //! through `From for CError`: it may denote a failure, or, like some Win32 functions, a //! *partial success*. //! //! # Migrating from a `bool` return value //! //! Projects that previously returned a plain `bool` can adopt the error-code scheme with minimal //! change: define exactly one non-success code (e.g. `const CERROR_FAIL: CError = 1;`) and map //! *every* error type to it via `From for CError`. The result is equivalent to a `bool` //! -- [`CERROR_OK`] for success, `CERROR_FAIL` for any failure -- while leaving room to introduce //! finer-grained codes later. //! //! # Required trait implementations //! //! [`set_last_error`] accepts any error type `E` that implements `Into` (typically provided //! by implementing `From for CError`) and [`Display`] (used to derive the message text). use std::cell::RefCell; use std::ffi::{CString, c_char}; use std::fmt::Display; /// The raw pointer type to an immutable C/C++ NUL-terminated character string, as returned by /// [`get_error_message`]. pub type CStrPtr = *const c_char; /// The error code type returned by FFI functions. /// /// This is a plain `u32`. [`CERROR_OK`] is the single value that represents absolute, unambiguous /// success. Any other value's meaning is defined by the consuming project through /// `From for CError`: it may represent a failure, or, like some Win32 functions, a /// *partial success*. pub type CError = u32; /// The sole error code that represents absolute, unambiguous success. /// /// All other [`CError`] values carry project-defined meaning (failure or partial success) via the /// user's `From for CError`. pub const CERROR_OK: CError = 0; struct LastError { /// `None` means nothing has been recorded since the last clear (i.e. the last call was an /// absolute success). `Some(code)` means an outcome code has been recorded. code: Option, /// Cached message text derived from the error's [`Display`], with interior NUL bytes replaced /// by the replacement character so that building the [`CString`] never fails. msg: CString, } impl LastError { fn new() -> Self { Self { code: None, msg: CString::new("").expect("empty string must be valid for CString"), } } /// Record an outcome: derive a message from `err`'s [`Display`] (sanitizing interior NUL) and /// store `err.into()` as the code. pub fn set_last_error + Display>(&mut self, err: E) { let text = format!("{err}").replace('\0', "\u{FFFD}"); self.msg = CString::new(text).expect("NUL bytes were sanitized away"); self.code = Some(err.into()); } /// Reset to the absolute-success state: no code, empty message. pub fn clear_last_error(&mut self) { self.code = None; self.msg = CString::new("").expect("empty string must be valid for CString"); } /// Return whether an outcome has been recorded since the last clear. pub fn has_last_message(&self) -> bool { self.code.is_some() } /// Return the recorded code, or [`CERROR_OK`] (absolute success) if none was recorded. pub fn get_error_code(&self) -> CError { self.code.unwrap_or(CERROR_OK) } /// Return a pointer to the cached message string -- a NUL-terminated C string whose storage is /// private to this module and not shared with any other module's cache. If nothing was recorded /// the pointer addresses an empty string. The pointer stays valid until the next /// [`set_last_error`](Self::set_last_error) or [`clear_last_error`](Self::clear_last_error) on /// this thread. pub fn get_error_message(&self) -> CStrPtr { self.msg.as_ptr() } } thread_local! { static LAST_ERROR: RefCell = RefCell::new(LastError::new()); } /// Record an outcome for the current thread. /// /// Derives a message from `err`'s [`Display`] (interior NUL bytes are replaced with the replacement /// character) and stores `err.into()` as the code. Requires `E: Into + Display`. pub fn set_last_error + Display>(err: E) { LAST_ERROR.with(|e| e.borrow_mut().set_last_error(err)); } /// Reset the current thread's recorded outcome to the absolute-success state (no code, empty /// message). Should be called at the entry of every FFI function. pub fn clear_last_error() { LAST_ERROR.with(|e| e.borrow_mut().clear_last_error()); } /// Return whether an outcome has been recorded for the current thread since the last clear. pub fn has_last_message() -> bool { LAST_ERROR.with(|e| e.borrow().has_last_message()) } /// Return the current thread's recorded code, or [`CERROR_OK`] (absolute success) if none was /// recorded. pub fn get_error_code() -> CError { LAST_ERROR.with(|e| e.borrow().get_error_code()) } /// Return a pointer to the current thread's cached message string -- a NUL-terminated C string /// whose storage is private to this module and not shared with any other module's cache. If nothing /// was recorded the pointer addresses an empty string. The pointer stays valid until the next /// [`set_last_error`] or [`clear_last_error`] on this thread. pub fn get_error_message() -> CStrPtr { LAST_ERROR.with(|e| e.borrow().get_error_message()) }