pub mod tenant;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SuperAdminToken {
_private: (),
}
impl SuperAdminToken {
pub fn for_system_process(_reason: &str) -> Self {
Self { _private: () }
}
pub fn for_webhook(_source: &str) -> Self {
Self { _private: () }
}
pub fn for_auth(_operation: &str) -> Self {
Self { _private: () }
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct RlsContext {
pub tenant_id: String,
pub agent_id: String,
is_super_admin: bool,
is_global: bool,
user_id: String,
}
impl RlsContext {
pub fn tenant(tenant_id: &str) -> Self {
Self {
tenant_id: tenant_id.to_string(),
agent_id: String::new(),
is_super_admin: false,
is_global: false,
user_id: String::new(),
}
}
pub fn agent(agent_id: &str) -> Self {
Self {
tenant_id: String::new(),
agent_id: agent_id.to_string(),
is_super_admin: false,
is_global: false,
user_id: String::new(),
}
}
pub fn tenant_and_agent(tenant_id: &str, agent_id: &str) -> Self {
Self {
tenant_id: tenant_id.to_string(),
agent_id: agent_id.to_string(),
is_super_admin: false,
is_global: false,
user_id: String::new(),
}
}
pub fn global() -> Self {
Self {
tenant_id: String::new(),
agent_id: String::new(),
is_super_admin: false,
is_global: true,
user_id: String::new(),
}
}
pub fn super_admin(_token: SuperAdminToken) -> Self {
let nil = "00000000-0000-0000-0000-000000000000".to_string();
Self {
tenant_id: nil,
agent_id: String::new(),
is_super_admin: true,
is_global: false,
user_id: String::new(),
}
}
pub fn empty() -> Self {
Self {
tenant_id: String::new(),
agent_id: String::new(),
is_super_admin: false,
is_global: false,
user_id: String::new(),
}
}
pub fn user(user_id: &str) -> Self {
Self {
tenant_id: String::new(),
agent_id: String::new(),
is_super_admin: false,
is_global: false,
user_id: user_id.to_string(),
}
}
pub fn has_tenant(&self) -> bool {
!self.tenant_id.is_empty()
}
pub fn has_agent(&self) -> bool {
!self.agent_id.is_empty()
}
pub fn has_user(&self) -> bool {
!self.user_id.is_empty()
}
pub fn user_id(&self) -> &str {
&self.user_id
}
pub fn bypasses_rls(&self) -> bool {
self.is_super_admin
}
pub fn is_global(&self) -> bool {
self.is_global
}
}
impl std::fmt::Display for RlsContext {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.is_super_admin {
write!(f, "RlsContext(super_admin)")
} else if self.is_global {
write!(f, "RlsContext(global)")
} else if !self.tenant_id.is_empty() {
write!(f, "RlsContext(tenant={})", self.tenant_id)
} else {
write!(f, "RlsContext(none)")
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_tenant_context() {
let ctx = RlsContext::tenant("t-123");
assert_eq!(ctx.tenant_id, "t-123");
assert!(ctx.agent_id.is_empty());
assert!(!ctx.bypasses_rls());
assert!(ctx.has_tenant());
}
#[test]
fn test_agent_context_sets_tenant() {
let ctx = RlsContext::agent("ag-456");
assert!(ctx.tenant_id.is_empty());
assert_eq!(ctx.agent_id, "ag-456");
assert!(ctx.has_agent());
}
#[test]
fn test_super_admin_via_named_constructors() {
let token = SuperAdminToken::for_system_process("test");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
let token = SuperAdminToken::for_webhook("test");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
let token = SuperAdminToken::for_auth("test");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_tenant_and_agent() {
let ctx = RlsContext::tenant_and_agent("tenant-1", "ag-2");
assert_eq!(ctx.tenant_id, "tenant-1");
assert!(ctx.has_agent());
assert!(!ctx.bypasses_rls());
}
#[test]
fn test_display() {
let token = SuperAdminToken::for_system_process("test_display");
assert_eq!(
RlsContext::super_admin(token).to_string(),
"RlsContext(super_admin)"
);
assert_eq!(RlsContext::tenant("x").to_string(), "RlsContext(tenant=x)");
}
#[test]
fn test_equality() {
let a = RlsContext::tenant("t-1");
let b = RlsContext::tenant("t-1");
let c = RlsContext::tenant("t-2");
assert_eq!(a, b);
assert_ne!(a, c);
}
#[test]
fn test_empty_context() {
let ctx = RlsContext::empty();
assert!(!ctx.has_tenant());
assert!(!ctx.has_agent());
assert!(!ctx.bypasses_rls());
assert!(!ctx.is_global());
}
#[test]
fn test_global_context() {
let ctx = RlsContext::global();
assert!(!ctx.has_tenant());
assert!(!ctx.has_agent());
assert!(!ctx.bypasses_rls());
assert!(ctx.is_global());
assert_eq!(ctx.to_string(), "RlsContext(global)");
}
#[test]
fn test_for_system_process() {
let token = SuperAdminToken::for_system_process("cron::check_expired_holds");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_for_webhook() {
let token = SuperAdminToken::for_webhook("xendit_callback");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_for_auth() {
let token = SuperAdminToken::for_auth("login");
let ctx = RlsContext::super_admin(token);
assert!(ctx.bypasses_rls());
}
#[test]
fn test_all_constructors_produce_equal_tokens() {
let a = SuperAdminToken::for_system_process("a");
let b = SuperAdminToken::for_webhook("b");
let c = SuperAdminToken::for_auth("c");
assert_eq!(a, b);
assert_eq!(b, c);
}
#[test]
fn test_user_context() {
let ctx = RlsContext::user("550e8400-e29b-41d4-a716-446655440000");
assert!(!ctx.has_tenant());
assert!(!ctx.has_agent());
assert!(!ctx.bypasses_rls());
assert!(!ctx.is_global());
assert!(ctx.has_user());
assert_eq!(ctx.user_id(), "550e8400-e29b-41d4-a716-446655440000");
}
#[test]
fn test_user_context_display() {
let ctx = RlsContext::user("u-123");
assert_eq!(ctx.to_string(), "RlsContext(none)");
}
#[test]
fn test_other_constructors_have_no_user() {
assert!(!RlsContext::tenant("t-1").has_user());
assert!(!RlsContext::global().has_user());
assert!(!RlsContext::empty().has_user());
let token = SuperAdminToken::for_auth("test");
assert!(!RlsContext::super_admin(token).has_user());
}
}