use serde::{Deserialize, Serialize};
use std::fmt;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Namespace {
tenant_id: String,
environment: String,
}
impl Namespace {
pub fn new(tenant_id: impl Into<String>, environment: impl Into<String>) -> Self {
Self {
tenant_id: tenant_id.into(),
environment: environment.into(),
}
}
pub fn tenant_id(&self) -> &str {
&self.tenant_id
}
pub fn environment(&self) -> &str {
&self.environment
}
pub fn prefix(&self) -> String {
format!("tenant:{}:env:{}", self.tenant_id, self.environment)
}
pub fn qualify_key(&self, key: &str) -> String {
format!("{}:{}", self.prefix(), key)
}
}
impl fmt::Display for Namespace {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.prefix())
}
}
impl Default for Namespace {
fn default() -> Self {
Self {
tenant_id: "global".to_string(),
environment: "development".to_string(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_namespace_creation() {
let ns = Namespace::new("tenant-123", "production");
assert_eq!(ns.tenant_id(), "tenant-123");
assert_eq!(ns.environment(), "production");
}
#[test]
fn test_namespace_prefix() {
let ns = Namespace::new("acme", "prod");
assert_eq!(ns.prefix(), "tenant:acme:env:prod");
}
#[test]
fn test_namespace_qualify_key() {
let ns = Namespace::new("acme", "prod");
let qualified = ns.qualify_key("rl:user:123:rule1");
assert_eq!(qualified, "tenant:acme:env:prod:rl:user:123:rule1");
}
#[test]
fn test_namespace_display() {
let ns = Namespace::new("tenant-1", "staging");
assert_eq!(format!("{}", ns), "tenant:tenant-1:env:staging");
}
#[test]
fn test_namespace_default() {
let ns = Namespace::default();
assert_eq!(ns.tenant_id(), "global");
assert_eq!(ns.environment(), "development");
}
#[test]
fn test_namespace_equality() {
let ns1 = Namespace::new("tenant-1", "prod");
let ns2 = Namespace::new("tenant-1", "prod");
let ns3 = Namespace::new("tenant-2", "prod");
assert_eq!(ns1, ns2);
assert_ne!(ns1, ns3);
}
#[test]
fn test_namespace_hash() {
use ahash::AHashSet;
let ns1 = Namespace::new("tenant-1", "prod");
let ns2 = Namespace::new("tenant-1", "prod");
let ns3 = Namespace::new("tenant-2", "prod");
let mut set = AHashSet::new();
set.insert(ns1.clone());
set.insert(ns2.clone());
set.insert(ns3.clone());
assert_eq!(set.len(), 2);
assert!(set.contains(&ns1));
assert!(set.contains(&ns3));
}
#[test]
fn test_namespace_serialization() {
let ns = Namespace::new("tenant-123", "production");
let json = serde_json::to_string(&ns).unwrap();
let deserialized: Namespace = serde_json::from_str(&json).unwrap();
assert_eq!(ns, deserialized);
}
#[test]
fn test_namespace_with_empty_strings() {
let ns = Namespace::new("", "");
assert_eq!(ns.tenant_id(), "");
assert_eq!(ns.environment(), "");
assert_eq!(ns.prefix(), "tenant::env:");
}
#[test]
fn test_namespace_with_special_characters() {
let ns = Namespace::new("tenant_a/b:c@d!", "prod-v1.2.3");
assert_eq!(ns.tenant_id(), "tenant_a/b:c@d!");
assert_eq!(ns.environment(), "prod-v1.2.3");
assert_eq!(ns.prefix(), "tenant:tenant_a/b:c@d!:env:prod-v1.2.3");
}
#[test]
fn test_namespace_qualify_key_edge_cases() {
let ns = Namespace::new("acme", "prod");
assert_eq!(ns.qualify_key(""), "tenant:acme:env:prod:");
assert_eq!(ns.qualify_key("::"), "tenant:acme:env:prod:::");
}
#[test]
fn test_namespace_display_matches_prefix() {
let ns = Namespace::new("tenant-1", "staging");
assert_eq!(format!("{}", ns), ns.prefix());
}
#[test]
fn test_namespace_clone_independent() {
let ns1 = Namespace::new("original", "prod");
let ns2 = ns1.clone();
assert_eq!(ns1, ns2);
let json2 = serde_json::to_string(&ns2).unwrap();
let ns2_roundtrip: Namespace = serde_json::from_str(&json2).unwrap();
assert_eq!(ns1, ns2_roundtrip);
}
}