use std::sync::atomic::{AtomicU64, Ordering};
pub type Xid = u64;
#[derive(Debug, Clone)]
pub struct WriteConsent {
pub(crate) kind: crate::runtime::write_gate::WriteKind,
pub(crate) _seal: WriteConsentSeal,
}
#[derive(Debug, Clone)]
pub struct WriteConsentSeal {
_private: std::marker::PhantomData<*const ()>,
}
unsafe impl Send for WriteConsentSeal {}
unsafe impl Sync for WriteConsentSeal {}
impl WriteConsentSeal {
pub(crate) fn new() -> Self {
Self {
_private: std::marker::PhantomData,
}
}
}
#[derive(Debug, Clone)]
pub struct OperationContext {
pub xid: Option<Xid>,
pub connection_id: Option<u64>,
pub audit_principal: String,
pub request_id: String,
pub write_consent: Option<WriteConsent>,
pub tenant: Option<String>,
}
impl OperationContext {
pub fn implicit() -> Self {
Self {
xid: None,
connection_id: None,
audit_principal: "anonymous".to_string(),
request_id: mint_request_id(),
write_consent: None,
tenant: None,
}
}
pub fn read_only(request_id: impl Into<String>) -> Self {
Self {
xid: None,
connection_id: None,
audit_principal: "anonymous".to_string(),
request_id: request_id.into(),
write_consent: None,
tenant: None,
}
}
pub fn writing(consent: WriteConsent, request_id: impl Into<String>) -> Self {
Self {
xid: None,
connection_id: None,
audit_principal: "anonymous".to_string(),
request_id: request_id.into(),
write_consent: Some(consent),
tenant: None,
}
}
pub fn with_principal(mut self, principal: impl Into<String>) -> Self {
self.audit_principal = principal.into();
self
}
pub fn with_connection(mut self, connection_id: u64) -> Self {
self.connection_id = Some(connection_id);
self
}
pub fn with_xid(mut self, xid: Xid) -> Self {
self.xid = Some(xid);
self
}
pub fn with_tenant(mut self, tenant: impl Into<String>) -> Self {
self.tenant = Some(tenant.into());
self
}
pub fn require_write_consent(&self) -> Result<&WriteConsent, crate::api::RedDBError> {
self.write_consent.as_ref().ok_or_else(|| {
crate::api::RedDBError::ReadOnly(
"operation context is missing WriteConsent — handler must call WriteGate::check"
.to_string(),
)
})
}
}
fn mint_request_id() -> String {
static SEQ: AtomicU64 = AtomicU64::new(0);
let seq = SEQ.fetch_add(1, Ordering::Relaxed);
let now_us = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map(|d| d.as_micros() as u64)
.unwrap_or(0);
format!("req-{now_us}-{seq}")
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn implicit_has_no_write_consent() {
let ctx = OperationContext::implicit();
assert!(ctx.write_consent.is_none());
assert_eq!(ctx.audit_principal, "anonymous");
assert!(!ctx.request_id.is_empty());
}
#[test]
fn read_only_constructor_carries_supplied_request_id() {
let ctx = OperationContext::read_only("req-abc");
assert_eq!(ctx.request_id, "req-abc");
assert!(ctx.write_consent.is_none());
}
#[test]
fn require_write_consent_errors_when_missing() {
let ctx = OperationContext::implicit();
let err = ctx.require_write_consent().unwrap_err();
assert!(matches!(err, crate::api::RedDBError::ReadOnly(_)));
}
#[test]
fn request_ids_are_monotonic_within_process() {
let a = mint_request_id();
let b = mint_request_id();
assert_ne!(a, b);
}
#[test]
fn builder_setters_compose() {
let ctx = OperationContext::read_only("req-1")
.with_principal("operator")
.with_connection(42)
.with_xid(7)
.with_tenant("acme");
assert_eq!(ctx.audit_principal, "operator");
assert_eq!(ctx.connection_id, Some(42));
assert_eq!(ctx.xid, Some(7));
assert_eq!(ctx.tenant.as_deref(), Some("acme"));
}
}