use crate::error::CoreError;
use rust_decimal::Decimal;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::{Duration, Instant};
use uuid::Uuid;
pub type TenantId = Uuid;
pub struct TenantManager {
tenants: Arc<RwLock<HashMap<TenantId, Tenant>>>,
configs: Arc<RwLock<HashMap<TenantId, TenantConfig>>>,
}
#[derive(Debug, Clone)]
pub struct Tenant {
pub id: TenantId,
pub name: String,
pub status: TenantStatus,
pub created_at: Instant,
pub quotas: ResourceQuotas,
pub usage: ResourceUsage,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TenantStatus {
Active,
Suspended,
Archived,
}
#[derive(Debug, Clone)]
pub struct ResourceQuotas {
pub max_users: usize,
pub max_tokens: usize,
pub max_daily_volume: Decimal,
pub max_api_calls_per_minute: usize,
pub max_storage_bytes: usize,
pub max_concurrent_connections: usize,
}
#[derive(Debug, Clone)]
pub struct ResourceUsage {
pub current_users: usize,
pub current_tokens: usize,
pub daily_volume: Decimal,
pub api_calls_current_minute: usize,
pub current_storage_bytes: usize,
pub current_connections: usize,
pub last_reset: Instant,
}
#[derive(Debug, Clone)]
pub struct TenantConfig {
pub branding: BrandingConfig,
pub fee_structure: Option<FeeStructure>,
pub custom_domain: Option<String>,
pub features: HashMap<String, bool>,
pub api_key_prefix: String,
}
#[derive(Debug, Clone)]
pub struct BrandingConfig {
pub platform_name: String,
pub logo_url: Option<String>,
pub primary_color: String,
pub secondary_color: String,
pub support_email: String,
}
#[derive(Debug, Clone)]
pub struct FeeStructure {
pub trading_fee: Decimal,
pub withdrawal_fee: Decimal,
pub minimum_fee: Decimal,
}
impl TenantManager {
pub fn new() -> Self {
Self {
tenants: Arc::new(RwLock::new(HashMap::new())),
configs: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn register_tenant(
&self,
name: String,
quotas: ResourceQuotas,
) -> Result<TenantId, CoreError> {
let id = Uuid::new_v4();
let tenant = Tenant {
id,
name: name.clone(),
status: TenantStatus::Active,
created_at: Instant::now(),
quotas,
usage: ResourceUsage {
current_users: 0,
current_tokens: 0,
daily_volume: Decimal::ZERO,
api_calls_current_minute: 0,
current_storage_bytes: 0,
current_connections: 0,
last_reset: Instant::now(),
},
};
let config = TenantConfig {
branding: BrandingConfig {
platform_name: name,
logo_url: None,
primary_color: "#3B82F6".to_string(),
secondary_color: "#8B5CF6".to_string(),
support_email: "support@example.com".to_string(),
},
fee_structure: None,
custom_domain: None,
features: HashMap::new(),
api_key_prefix: format!("tenant_{}_", &id.to_string()[..8]),
};
self.tenants.write().unwrap().insert(id, tenant);
self.configs.write().unwrap().insert(id, config);
Ok(id)
}
pub fn get_tenant(&self, tenant_id: &TenantId) -> Option<Tenant> {
self.tenants.read().unwrap().get(tenant_id).cloned()
}
pub fn update_status(
&self,
tenant_id: &TenantId,
status: TenantStatus,
) -> Result<(), CoreError> {
let mut tenants = self.tenants.write().unwrap();
if let Some(tenant) = tenants.get_mut(tenant_id) {
tenant.status = status;
Ok(())
} else {
Err(CoreError::NotFound("Tenant not found".to_string()))
}
}
pub fn check_quota(
&self,
tenant_id: &TenantId,
resource: ResourceType,
amount: usize,
) -> Result<bool, CoreError> {
let tenants = self.tenants.read().unwrap();
if let Some(tenant) = tenants.get(tenant_id) {
if tenant.status != TenantStatus::Active {
return Ok(false);
}
let available = match resource {
ResourceType::Users => {
tenant.quotas.max_users > tenant.usage.current_users + amount
}
ResourceType::Tokens => {
tenant.quotas.max_tokens > tenant.usage.current_tokens + amount
}
ResourceType::ApiCalls => {
tenant.quotas.max_api_calls_per_minute
> tenant.usage.api_calls_current_minute + amount
}
ResourceType::Storage => {
tenant.quotas.max_storage_bytes > tenant.usage.current_storage_bytes + amount
}
ResourceType::Connections => {
tenant.quotas.max_concurrent_connections
> tenant.usage.current_connections + amount
}
};
Ok(available)
} else {
Err(CoreError::NotFound("Tenant not found".to_string()))
}
}
pub fn increment_usage(
&self,
tenant_id: &TenantId,
resource: ResourceType,
amount: usize,
) -> Result<(), CoreError> {
let mut tenants = self.tenants.write().unwrap();
if let Some(tenant) = tenants.get_mut(tenant_id) {
match resource {
ResourceType::Users => tenant.usage.current_users += amount,
ResourceType::Tokens => tenant.usage.current_tokens += amount,
ResourceType::ApiCalls => tenant.usage.api_calls_current_minute += amount,
ResourceType::Storage => tenant.usage.current_storage_bytes += amount,
ResourceType::Connections => tenant.usage.current_connections += amount,
}
Ok(())
} else {
Err(CoreError::NotFound("Tenant not found".to_string()))
}
}
pub fn decrement_usage(
&self,
tenant_id: &TenantId,
resource: ResourceType,
amount: usize,
) -> Result<(), CoreError> {
let mut tenants = self.tenants.write().unwrap();
if let Some(tenant) = tenants.get_mut(tenant_id) {
match resource {
ResourceType::Users => {
tenant.usage.current_users = tenant.usage.current_users.saturating_sub(amount)
}
ResourceType::Tokens => {
tenant.usage.current_tokens = tenant.usage.current_tokens.saturating_sub(amount)
}
ResourceType::ApiCalls => {
tenant.usage.api_calls_current_minute =
tenant.usage.api_calls_current_minute.saturating_sub(amount)
}
ResourceType::Storage => {
tenant.usage.current_storage_bytes =
tenant.usage.current_storage_bytes.saturating_sub(amount)
}
ResourceType::Connections => {
tenant.usage.current_connections =
tenant.usage.current_connections.saturating_sub(amount)
}
}
Ok(())
} else {
Err(CoreError::NotFound("Tenant not found".to_string()))
}
}
pub fn reset_periodic_usage(&self, tenant_id: &TenantId) -> Result<(), CoreError> {
let mut tenants = self.tenants.write().unwrap();
if let Some(tenant) = tenants.get_mut(tenant_id) {
tenant.usage.api_calls_current_minute = 0;
tenant.usage.last_reset = Instant::now();
Ok(())
} else {
Err(CoreError::NotFound("Tenant not found".to_string()))
}
}
pub fn get_config(&self, tenant_id: &TenantId) -> Option<TenantConfig> {
self.configs.read().unwrap().get(tenant_id).cloned()
}
pub fn update_config(
&self,
tenant_id: &TenantId,
config: TenantConfig,
) -> Result<(), CoreError> {
let mut configs = self.configs.write().unwrap();
if self.tenants.read().unwrap().contains_key(tenant_id) {
configs.insert(*tenant_id, config);
Ok(())
} else {
Err(CoreError::NotFound("Tenant not found".to_string()))
}
}
pub fn get_utilization(&self, tenant_id: &TenantId) -> Option<ResourceUtilization> {
let tenants = self.tenants.read().unwrap();
tenants.get(tenant_id).map(|tenant| {
let users_pct =
(tenant.usage.current_users as f64 / tenant.quotas.max_users as f64) * 100.0;
let tokens_pct =
(tenant.usage.current_tokens as f64 / tenant.quotas.max_tokens as f64) * 100.0;
let api_pct = (tenant.usage.api_calls_current_minute as f64
/ tenant.quotas.max_api_calls_per_minute as f64)
* 100.0;
let storage_pct = (tenant.usage.current_storage_bytes as f64
/ tenant.quotas.max_storage_bytes as f64)
* 100.0;
let connections_pct = (tenant.usage.current_connections as f64
/ tenant.quotas.max_concurrent_connections as f64)
* 100.0;
ResourceUtilization {
users_percentage: users_pct,
tokens_percentage: tokens_pct,
api_calls_percentage: api_pct,
storage_percentage: storage_pct,
connections_percentage: connections_pct,
}
})
}
pub fn list_tenants(&self) -> Vec<TenantId> {
self.tenants.read().unwrap().keys().copied().collect()
}
pub fn tenant_count(&self) -> usize {
self.tenants.read().unwrap().len()
}
}
impl Default for TenantManager {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ResourceType {
Users,
Tokens,
ApiCalls,
Storage,
Connections,
}
#[derive(Debug, Clone)]
pub struct ResourceUtilization {
pub users_percentage: f64,
pub tokens_percentage: f64,
pub api_calls_percentage: f64,
pub storage_percentage: f64,
pub connections_percentage: f64,
}
pub struct TenantIsolation {
tenant_id: TenantId,
}
impl TenantIsolation {
pub fn new(tenant_id: TenantId) -> Self {
Self { tenant_id }
}
pub fn verify(&self, resource_tenant_id: &TenantId) -> Result<(), CoreError> {
if self.tenant_id == *resource_tenant_id {
Ok(())
} else {
Err(CoreError::Validation(
"Cross-tenant access denied".to_string(),
))
}
}
pub fn tenant_id(&self) -> TenantId {
self.tenant_id
}
}
pub struct TenantRateLimiter {
limits: Arc<RwLock<HashMap<TenantId, RateLimit>>>,
}
#[derive(Debug, Clone)]
struct RateLimit {
requests: usize,
window_start: Instant,
window_duration: Duration,
max_requests: usize,
}
impl TenantRateLimiter {
pub fn new() -> Self {
Self {
limits: Arc::new(RwLock::new(HashMap::new())),
}
}
pub fn configure(&self, tenant_id: TenantId, max_requests: usize, window_duration: Duration) {
let mut limits = self.limits.write().unwrap();
limits.insert(
tenant_id,
RateLimit {
requests: 0,
window_start: Instant::now(),
window_duration,
max_requests,
},
);
}
pub fn check_rate_limit(&self, tenant_id: &TenantId) -> Result<bool, CoreError> {
let mut limits = self.limits.write().unwrap();
if let Some(limit) = limits.get_mut(tenant_id) {
let now = Instant::now();
if now.duration_since(limit.window_start) >= limit.window_duration {
limit.requests = 0;
limit.window_start = now;
}
if limit.requests < limit.max_requests {
limit.requests += 1;
Ok(true)
} else {
Ok(false)
}
} else {
Err(CoreError::NotFound("Rate limit not configured".to_string()))
}
}
pub fn remaining_requests(&self, tenant_id: &TenantId) -> Option<usize> {
let limits = self.limits.read().unwrap();
limits
.get(tenant_id)
.map(|limit| limit.max_requests.saturating_sub(limit.requests))
}
}
impl Default for TenantRateLimiter {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use rust_decimal_macros::dec;
#[test]
fn test_tenant_registration() {
let manager = TenantManager::new();
let quotas = ResourceQuotas {
max_users: 100,
max_tokens: 50,
max_daily_volume: dec!(1000000),
max_api_calls_per_minute: 1000,
max_storage_bytes: 1024 * 1024 * 100, max_concurrent_connections: 50,
};
let tenant_id = manager
.register_tenant("Test Tenant".to_string(), quotas)
.unwrap();
let tenant = manager.get_tenant(&tenant_id).unwrap();
assert_eq!(tenant.name, "Test Tenant");
assert_eq!(tenant.status, TenantStatus::Active);
}
#[test]
fn test_quota_checking() {
let manager = TenantManager::new();
let quotas = ResourceQuotas {
max_users: 10,
max_tokens: 5,
max_daily_volume: dec!(1000),
max_api_calls_per_minute: 100,
max_storage_bytes: 1024,
max_concurrent_connections: 5,
};
let tenant_id = manager.register_tenant("Test".to_string(), quotas).unwrap();
assert!(
manager
.check_quota(&tenant_id, ResourceType::Users, 5)
.unwrap()
);
manager
.increment_usage(&tenant_id, ResourceType::Users, 8)
.unwrap();
assert!(
!manager
.check_quota(&tenant_id, ResourceType::Users, 5)
.unwrap()
);
}
#[test]
fn test_tenant_isolation() {
let tenant1 = Uuid::new_v4();
let tenant2 = Uuid::new_v4();
let isolation = TenantIsolation::new(tenant1);
assert!(isolation.verify(&tenant1).is_ok());
assert!(isolation.verify(&tenant2).is_err());
}
#[test]
fn test_rate_limiter() {
let limiter = TenantRateLimiter::new();
let tenant_id = Uuid::new_v4();
limiter.configure(tenant_id, 5, Duration::from_secs(1));
for _ in 0..5 {
assert!(limiter.check_rate_limit(&tenant_id).unwrap());
}
assert!(!limiter.check_rate_limit(&tenant_id).unwrap());
std::thread::sleep(Duration::from_millis(1100));
assert!(limiter.check_rate_limit(&tenant_id).unwrap());
}
#[test]
fn test_tenant_status_update() {
let manager = TenantManager::new();
let quotas = ResourceQuotas {
max_users: 10,
max_tokens: 5,
max_daily_volume: dec!(1000),
max_api_calls_per_minute: 100,
max_storage_bytes: 1024,
max_concurrent_connections: 5,
};
let tenant_id = manager.register_tenant("Test".to_string(), quotas).unwrap();
manager
.update_status(&tenant_id, TenantStatus::Suspended)
.unwrap();
let tenant = manager.get_tenant(&tenant_id).unwrap();
assert_eq!(tenant.status, TenantStatus::Suspended);
assert!(
!manager
.check_quota(&tenant_id, ResourceType::Users, 1)
.unwrap()
);
}
#[test]
fn test_resource_utilization() {
let manager = TenantManager::new();
let quotas = ResourceQuotas {
max_users: 100,
max_tokens: 50,
max_daily_volume: dec!(1000),
max_api_calls_per_minute: 1000,
max_storage_bytes: 1000,
max_concurrent_connections: 10,
};
let tenant_id = manager.register_tenant("Test".to_string(), quotas).unwrap();
manager
.increment_usage(&tenant_id, ResourceType::Users, 50)
.unwrap();
let utilization = manager.get_utilization(&tenant_id).unwrap();
assert_eq!(utilization.users_percentage, 50.0);
}
}