use std::cell::RefCell;
#[derive(Debug, Clone)]
pub struct AuditCtx {
pub auth_user_id: String,
pub auth_user_name: String,
pub sql_text: String,
}
thread_local! {
static CURRENT: RefCell<Option<AuditCtx>> = const { RefCell::new(None) };
}
pub struct AuditScope {
_private: (),
}
impl AuditScope {
pub fn new(ctx: AuditCtx) -> Self {
CURRENT.with(|c| *c.borrow_mut() = Some(ctx));
Self { _private: () }
}
}
impl Drop for AuditScope {
fn drop(&mut self) {
CURRENT.with(|c| c.borrow_mut().take());
}
}
pub fn current() -> Option<AuditCtx> {
CURRENT.with(|c| c.borrow().clone())
}
#[cfg(test)]
mod tests {
use super::*;
fn ctx(user: &str, sql: &str) -> AuditCtx {
AuditCtx {
auth_user_id: user.into(),
auth_user_name: user.into(),
sql_text: sql.into(),
}
}
#[test]
fn no_context_by_default() {
assert!(current().is_none());
}
#[test]
fn scope_installs_and_clears() {
{
let _g = AuditScope::new(ctx("alice", "CREATE COLLECTION x"));
let seen = current().expect("scope sets context");
assert_eq!(seen.auth_user_name, "alice");
assert_eq!(seen.sql_text, "CREATE COLLECTION x");
}
assert!(current().is_none());
}
#[test]
fn inner_scope_shadows_outer() {
let _outer = AuditScope::new(ctx("root", "outer"));
{
let _inner = AuditScope::new(ctx("bob", "inner"));
assert_eq!(current().unwrap().auth_user_name, "bob");
}
assert!(current().is_none());
}
}