use crate::multi_tenancy::types::{MultiTenancyError, MultiTenancyResult};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub enum IsolationLevel {
Namespace,
SeparateIndex,
Dedicated,
}
impl IsolationLevel {
pub fn strength(&self) -> u8 {
match self {
Self::Namespace => 3,
Self::SeparateIndex => 7,
Self::Dedicated => 10,
}
}
pub fn efficiency(&self) -> u8 {
match self {
Self::Namespace => 10,
Self::SeparateIndex => 6,
Self::Dedicated => 3,
}
}
pub fn for_tier(tier: &str) -> Self {
match tier {
"free" | "trial" => Self::Namespace,
"pro" | "business" => Self::SeparateIndex,
"enterprise" | "dedicated" => Self::Dedicated,
_ => Self::Namespace,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct IsolationStrategy {
pub level: IsolationLevel,
pub encryption_at_rest: bool,
pub encryption_in_transit: bool,
pub namespace_separator: String,
pub max_namespace_depth: usize,
}
impl IsolationStrategy {
pub fn new(level: IsolationLevel) -> Self {
Self {
level,
encryption_at_rest: false,
encryption_in_transit: true,
namespace_separator: ":".to_string(),
max_namespace_depth: 5,
}
}
pub fn free_tier() -> Self {
Self::new(IsolationLevel::Namespace)
}
pub fn pro_tier() -> Self {
let mut strategy = Self::new(IsolationLevel::SeparateIndex);
strategy.encryption_at_rest = true;
strategy
}
pub fn enterprise_tier() -> Self {
let mut strategy = Self::new(IsolationLevel::Dedicated);
strategy.encryption_at_rest = true;
strategy
}
pub fn with_encryption(mut self, at_rest: bool, in_transit: bool) -> Self {
self.encryption_at_rest = at_rest;
self.encryption_in_transit = in_transit;
self
}
pub fn with_separator(mut self, separator: impl Into<String>) -> Self {
self.namespace_separator = separator.into();
self
}
}
pub struct NamespaceManager {
namespaces: Arc<RwLock<HashMap<String, Namespace>>>,
strategy: IsolationStrategy,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Namespace {
pub tenant_id: String,
pub prefix: String,
pub sub_namespaces: Vec<String>,
pub metadata: HashMap<String, String>,
}
impl Namespace {
pub fn new(tenant_id: impl Into<String>, prefix: impl Into<String>) -> Self {
Self {
tenant_id: tenant_id.into(),
prefix: prefix.into(),
sub_namespaces: Vec::new(),
metadata: HashMap::new(),
}
}
pub fn create_sub_namespace(&mut self, name: impl Into<String>, separator: &str) -> String {
let sub = format!("{}{}{}", self.prefix, separator, name.into());
self.sub_namespaces.push(sub.clone());
sub
}
pub fn qualify_key(&self, key: &str, separator: &str) -> String {
format!("{}{}{}", self.prefix, separator, key)
}
pub fn owns_key(&self, key: &str) -> bool {
key.starts_with(&self.prefix)
}
}
impl NamespaceManager {
pub fn new(strategy: IsolationStrategy) -> Self {
Self {
namespaces: Arc::new(RwLock::new(HashMap::new())),
strategy,
}
}
pub fn register_tenant(&self, tenant_id: impl Into<String>) -> MultiTenancyResult<String> {
let tenant_id = tenant_id.into();
let prefix = self.generate_namespace_prefix(&tenant_id);
let namespace = Namespace::new(tenant_id.clone(), prefix.clone());
let mut namespaces =
self.namespaces
.write()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
if namespaces.contains_key(&tenant_id) {
return Err(MultiTenancyError::TenantAlreadyExists {
tenant_id: tenant_id.clone(),
});
}
namespaces.insert(tenant_id, namespace);
Ok(prefix)
}
pub fn unregister_tenant(&self, tenant_id: &str) -> MultiTenancyResult<()> {
let mut namespaces =
self.namespaces
.write()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
namespaces
.remove(tenant_id)
.ok_or_else(|| MultiTenancyError::TenantNotFound {
tenant_id: tenant_id.to_string(),
})?;
Ok(())
}
pub fn get_prefix(&self, tenant_id: &str) -> MultiTenancyResult<String> {
let namespaces = self
.namespaces
.read()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
namespaces
.get(tenant_id)
.map(|ns| ns.prefix.clone())
.ok_or_else(|| MultiTenancyError::TenantNotFound {
tenant_id: tenant_id.to_string(),
})
}
pub fn qualify_key(&self, tenant_id: &str, key: &str) -> MultiTenancyResult<String> {
let namespaces = self
.namespaces
.read()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
let namespace =
namespaces
.get(tenant_id)
.ok_or_else(|| MultiTenancyError::TenantNotFound {
tenant_id: tenant_id.to_string(),
})?;
Ok(namespace.qualify_key(key, &self.strategy.namespace_separator))
}
pub fn extract_tenant_id(&self, namespaced_key: &str) -> MultiTenancyResult<String> {
let namespaces = self
.namespaces
.read()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
for (tenant_id, namespace) in namespaces.iter() {
if namespace.owns_key(namespaced_key) {
return Ok(tenant_id.clone());
}
}
Err(MultiTenancyError::IsolationViolation {
message: format!("No tenant owns key: {}", namespaced_key),
})
}
pub fn validate_access(&self, tenant_id: &str, key: &str) -> MultiTenancyResult<bool> {
let namespaces = self
.namespaces
.read()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
let namespace =
namespaces
.get(tenant_id)
.ok_or_else(|| MultiTenancyError::TenantNotFound {
tenant_id: tenant_id.to_string(),
})?;
Ok(namespace.owns_key(key))
}
pub fn create_sub_namespace(
&self,
tenant_id: &str,
name: impl Into<String>,
) -> MultiTenancyResult<String> {
let mut namespaces =
self.namespaces
.write()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
let namespace =
namespaces
.get_mut(tenant_id)
.ok_or_else(|| MultiTenancyError::TenantNotFound {
tenant_id: tenant_id.to_string(),
})?;
if namespace.sub_namespaces.len() >= self.strategy.max_namespace_depth {
return Err(MultiTenancyError::InvalidConfiguration {
message: "Maximum namespace depth exceeded".to_string(),
});
}
Ok(namespace.create_sub_namespace(name, &self.strategy.namespace_separator))
}
pub fn list_namespaces(&self) -> MultiTenancyResult<Vec<String>> {
let namespaces = self
.namespaces
.read()
.map_err(|e| MultiTenancyError::InternalError {
message: format!("Lock error: {}", e),
})?;
Ok(namespaces.keys().cloned().collect())
}
fn generate_namespace_prefix(&self, tenant_id: &str) -> String {
let sanitized: String = tenant_id
.chars()
.map(|c| match c {
'-' | '.' | '/' => '_',
c => c,
})
.collect();
format!("tenant_{}", sanitized)
}
pub fn strategy(&self) -> &IsolationStrategy {
&self.strategy
}
}
#[cfg(test)]
mod tests {
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
use super::*;
#[test]
fn test_isolation_levels() {
assert_eq!(IsolationLevel::Namespace.strength(), 3);
assert_eq!(IsolationLevel::SeparateIndex.strength(), 7);
assert_eq!(IsolationLevel::Dedicated.strength(), 10);
assert_eq!(IsolationLevel::Namespace.efficiency(), 10);
assert_eq!(IsolationLevel::Dedicated.efficiency(), 3);
}
#[test]
fn test_isolation_level_for_tier() {
assert_eq!(IsolationLevel::for_tier("free"), IsolationLevel::Namespace);
assert_eq!(
IsolationLevel::for_tier("pro"),
IsolationLevel::SeparateIndex
);
assert_eq!(
IsolationLevel::for_tier("enterprise"),
IsolationLevel::Dedicated
);
}
#[test]
fn test_isolation_strategy() {
let strategy = IsolationStrategy::free_tier();
assert_eq!(strategy.level, IsolationLevel::Namespace);
assert!(!strategy.encryption_at_rest);
let strategy = IsolationStrategy::pro_tier();
assert_eq!(strategy.level, IsolationLevel::SeparateIndex);
assert!(strategy.encryption_at_rest);
let strategy = IsolationStrategy::enterprise_tier();
assert_eq!(strategy.level, IsolationLevel::Dedicated);
assert!(strategy.encryption_at_rest);
}
#[test]
fn test_namespace_creation() {
let ns = Namespace::new("tenant1", "tenant_tenant1");
assert_eq!(ns.tenant_id, "tenant1");
assert_eq!(ns.prefix, "tenant_tenant1");
assert!(ns.sub_namespaces.is_empty());
}
#[test]
fn test_namespace_qualification() {
let ns = Namespace::new("tenant1", "tenant_tenant1");
let qualified = ns.qualify_key("vector123", ":");
assert_eq!(qualified, "tenant_tenant1:vector123");
assert!(ns.owns_key(&qualified));
assert!(!ns.owns_key("other_tenant:vector123"));
}
#[test]
fn test_namespace_manager() -> Result<()> {
let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
let manager = NamespaceManager::new(strategy);
let prefix = manager.register_tenant("tenant1")?;
assert!(prefix.contains("tenant_tenant1"));
let retrieved_prefix = manager.get_prefix("tenant1")?;
assert_eq!(prefix, retrieved_prefix);
let qualified = manager.qualify_key("tenant1", "vector123")?;
assert!(qualified.starts_with(&prefix));
assert!(qualified.contains("vector123"));
let __val = manager.validate_access("tenant1", &qualified)?;
assert!(__val);
assert!(!manager.validate_access("tenant1", "other_tenant:key")?);
let extracted = manager.extract_tenant_id(&qualified)?;
assert_eq!(extracted, "tenant1");
manager.unregister_tenant("tenant1")?;
assert!(manager.get_prefix("tenant1").is_err());
Ok(())
}
#[test]
fn test_sub_namespaces() -> Result<()> {
let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
let manager = NamespaceManager::new(strategy);
manager.register_tenant("tenant1")?;
let sub = manager.create_sub_namespace("tenant1", "vectors")?;
assert!(sub.contains("tenant_tenant1"));
assert!(sub.contains("vectors"));
let sub2 = manager.create_sub_namespace("tenant1", "embeddings")?;
assert!(sub2.contains("embeddings"));
assert_ne!(sub, sub2);
Ok(())
}
#[test]
fn test_namespace_manager_errors() -> Result<()> {
let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
let manager = NamespaceManager::new(strategy);
assert!(manager.get_prefix("nonexistent").is_err());
assert!(manager.qualify_key("nonexistent", "key").is_err());
manager.register_tenant("tenant1")?;
assert!(manager.register_tenant("tenant1").is_err());
assert!(manager.unregister_tenant("nonexistent").is_err());
Ok(())
}
#[test]
fn test_list_namespaces() -> Result<()> {
let strategy = IsolationStrategy::new(IsolationLevel::Namespace);
let manager = NamespaceManager::new(strategy);
assert_eq!(manager.list_namespaces().expect("test value").len(), 0);
manager.register_tenant("tenant1")?;
manager.register_tenant("tenant2")?;
let namespaces = manager.list_namespaces()?;
assert_eq!(namespaces.len(), 2);
assert!(namespaces.contains(&"tenant1".to_string()));
assert!(namespaces.contains(&"tenant2".to_string()));
Ok(())
}
}