use super::resolver::TenantResolver;
use super::strategy::{DatabaseConfig, IsolationStrategy, RowLevelConfig, SchemaConfig};
use std::sync::Arc;
#[derive(Clone)]
pub struct TenantConfig {
pub strategy: IsolationStrategy,
pub require_tenant: bool,
pub default_tenant: Option<String>,
pub allow_bypass: bool,
pub resolver: Option<Arc<dyn TenantResolver>>,
pub enforce_on_writes: bool,
pub log_tenant_context: bool,
}
impl std::fmt::Debug for TenantConfig {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TenantConfig")
.field("strategy", &self.strategy)
.field("require_tenant", &self.require_tenant)
.field("default_tenant", &self.default_tenant)
.field("allow_bypass", &self.allow_bypass)
.field("enforce_on_writes", &self.enforce_on_writes)
.field("log_tenant_context", &self.log_tenant_context)
.finish()
}
}
impl TenantConfig {
pub fn row_level(column: impl Into<String>) -> Self {
Self {
strategy: IsolationStrategy::row_level(column),
require_tenant: true,
default_tenant: None,
allow_bypass: true,
resolver: None,
enforce_on_writes: true,
log_tenant_context: false,
}
}
pub fn schema_based() -> Self {
Self {
strategy: IsolationStrategy::schema_based(),
require_tenant: true,
default_tenant: None,
allow_bypass: true,
resolver: None,
enforce_on_writes: true,
log_tenant_context: false,
}
}
pub fn database_based() -> Self {
Self {
strategy: IsolationStrategy::database_based(),
require_tenant: true,
default_tenant: None,
allow_bypass: true,
resolver: None,
enforce_on_writes: true,
log_tenant_context: false,
}
}
pub fn builder() -> TenantConfigBuilder {
TenantConfigBuilder::default()
}
pub fn with_default_tenant(mut self, tenant: impl Into<String>) -> Self {
self.default_tenant = Some(tenant.into());
self
}
pub fn optional(mut self) -> Self {
self.require_tenant = false;
self
}
pub fn without_bypass(mut self) -> Self {
self.allow_bypass = false;
self
}
pub fn with_resolver<R: TenantResolver + 'static>(mut self, resolver: R) -> Self {
self.resolver = Some(Arc::new(resolver));
self
}
pub fn with_logging(mut self) -> Self {
self.log_tenant_context = true;
self
}
pub fn row_level_config(&self) -> Option<&RowLevelConfig> {
self.strategy.row_level_config()
}
pub fn schema_config(&self) -> Option<&SchemaConfig> {
self.strategy.schema_config()
}
pub fn database_config(&self) -> Option<&DatabaseConfig> {
self.strategy.database_config()
}
}
#[derive(Default)]
pub struct TenantConfigBuilder {
strategy: Option<IsolationStrategy>,
require_tenant: bool,
default_tenant: Option<String>,
allow_bypass: bool,
resolver: Option<Arc<dyn TenantResolver>>,
enforce_on_writes: bool,
log_tenant_context: bool,
}
impl TenantConfigBuilder {
pub fn new() -> Self {
Self {
require_tenant: true,
allow_bypass: true,
enforce_on_writes: true,
..Default::default()
}
}
pub fn strategy(mut self, strategy: IsolationStrategy) -> Self {
self.strategy = Some(strategy);
self
}
pub fn row_level(mut self, config: RowLevelConfig) -> Self {
self.strategy = Some(IsolationStrategy::RowLevel(config));
self
}
pub fn schema(mut self, config: SchemaConfig) -> Self {
self.strategy = Some(IsolationStrategy::Schema(config));
self
}
pub fn database(mut self, config: DatabaseConfig) -> Self {
self.strategy = Some(IsolationStrategy::Database(config));
self
}
pub fn require_tenant(mut self, require: bool) -> Self {
self.require_tenant = require;
self
}
pub fn default_tenant(mut self, tenant: impl Into<String>) -> Self {
self.default_tenant = Some(tenant.into());
self
}
pub fn allow_bypass(mut self, allow: bool) -> Self {
self.allow_bypass = allow;
self
}
pub fn resolver<R: TenantResolver + 'static>(mut self, resolver: R) -> Self {
self.resolver = Some(Arc::new(resolver));
self
}
pub fn enforce_on_writes(mut self, enforce: bool) -> Self {
self.enforce_on_writes = enforce;
self
}
pub fn log_context(mut self, log: bool) -> Self {
self.log_tenant_context = log;
self
}
pub fn build(self) -> TenantConfig {
TenantConfig {
strategy: self
.strategy
.unwrap_or_else(|| IsolationStrategy::row_level("tenant_id")),
require_tenant: self.require_tenant,
default_tenant: self.default_tenant,
allow_bypass: self.allow_bypass,
resolver: self.resolver,
enforce_on_writes: self.enforce_on_writes,
log_tenant_context: self.log_tenant_context,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_row_level_config() {
let config = TenantConfig::row_level("org_id")
.with_default_tenant("default")
.with_logging();
assert!(config.strategy.is_row_level());
assert_eq!(config.default_tenant, Some("default".to_string()));
assert!(config.log_tenant_context);
}
#[test]
fn test_schema_config() {
let config = TenantConfig::schema_based().optional().without_bypass();
assert!(config.strategy.is_schema_based());
assert!(!config.require_tenant);
assert!(!config.allow_bypass);
}
#[test]
fn test_builder() {
let config = TenantConfig::builder()
.row_level(RowLevelConfig::new("tenant_id").with_database_rls())
.default_tenant("system")
.log_context(true)
.build();
assert!(config.strategy.is_row_level());
assert!(config.row_level_config().unwrap().use_database_rls);
}
}