prax_query/tenant/
config.rs1use super::resolver::TenantResolver;
4use super::strategy::{DatabaseConfig, IsolationStrategy, RowLevelConfig, SchemaConfig};
5use std::sync::Arc;
6
7#[derive(Clone)]
9pub struct TenantConfig {
10 pub strategy: IsolationStrategy,
12 pub require_tenant: bool,
14 pub default_tenant: Option<String>,
16 pub allow_bypass: bool,
18 pub resolver: Option<Arc<dyn TenantResolver>>,
20 pub enforce_on_writes: bool,
22 pub log_tenant_context: bool,
24}
25
26impl std::fmt::Debug for TenantConfig {
27 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
28 f.debug_struct("TenantConfig")
29 .field("strategy", &self.strategy)
30 .field("require_tenant", &self.require_tenant)
31 .field("default_tenant", &self.default_tenant)
32 .field("allow_bypass", &self.allow_bypass)
33 .field("enforce_on_writes", &self.enforce_on_writes)
34 .field("log_tenant_context", &self.log_tenant_context)
35 .finish()
36 }
37}
38
39impl TenantConfig {
40 pub fn row_level(column: impl Into<String>) -> Self {
42 Self {
43 strategy: IsolationStrategy::row_level(column),
44 require_tenant: true,
45 default_tenant: None,
46 allow_bypass: true,
47 resolver: None,
48 enforce_on_writes: true,
49 log_tenant_context: false,
50 }
51 }
52
53 pub fn schema_based() -> Self {
55 Self {
56 strategy: IsolationStrategy::schema_based(),
57 require_tenant: true,
58 default_tenant: None,
59 allow_bypass: true,
60 resolver: None,
61 enforce_on_writes: true,
62 log_tenant_context: false,
63 }
64 }
65
66 pub fn database_based() -> Self {
68 Self {
69 strategy: IsolationStrategy::database_based(),
70 require_tenant: true,
71 default_tenant: None,
72 allow_bypass: true,
73 resolver: None,
74 enforce_on_writes: true,
75 log_tenant_context: false,
76 }
77 }
78
79 pub fn builder() -> TenantConfigBuilder {
81 TenantConfigBuilder::default()
82 }
83
84 pub fn with_default_tenant(mut self, tenant: impl Into<String>) -> Self {
86 self.default_tenant = Some(tenant.into());
87 self
88 }
89
90 pub fn optional(mut self) -> Self {
92 self.require_tenant = false;
93 self
94 }
95
96 pub fn without_bypass(mut self) -> Self {
98 self.allow_bypass = false;
99 self
100 }
101
102 pub fn with_resolver<R: TenantResolver + 'static>(mut self, resolver: R) -> Self {
104 self.resolver = Some(Arc::new(resolver));
105 self
106 }
107
108 pub fn with_logging(mut self) -> Self {
110 self.log_tenant_context = true;
111 self
112 }
113
114 pub fn row_level_config(&self) -> Option<&RowLevelConfig> {
116 self.strategy.row_level_config()
117 }
118
119 pub fn schema_config(&self) -> Option<&SchemaConfig> {
121 self.strategy.schema_config()
122 }
123
124 pub fn database_config(&self) -> Option<&DatabaseConfig> {
126 self.strategy.database_config()
127 }
128}
129
130#[derive(Default)]
132pub struct TenantConfigBuilder {
133 strategy: Option<IsolationStrategy>,
134 require_tenant: bool,
135 default_tenant: Option<String>,
136 allow_bypass: bool,
137 resolver: Option<Arc<dyn TenantResolver>>,
138 enforce_on_writes: bool,
139 log_tenant_context: bool,
140}
141
142impl TenantConfigBuilder {
143 pub fn new() -> Self {
145 Self {
146 require_tenant: true,
147 allow_bypass: true,
148 enforce_on_writes: true,
149 ..Default::default()
150 }
151 }
152
153 pub fn strategy(mut self, strategy: IsolationStrategy) -> Self {
155 self.strategy = Some(strategy);
156 self
157 }
158
159 pub fn row_level(mut self, config: RowLevelConfig) -> Self {
161 self.strategy = Some(IsolationStrategy::RowLevel(config));
162 self
163 }
164
165 pub fn schema(mut self, config: SchemaConfig) -> Self {
167 self.strategy = Some(IsolationStrategy::Schema(config));
168 self
169 }
170
171 pub fn database(mut self, config: DatabaseConfig) -> Self {
173 self.strategy = Some(IsolationStrategy::Database(config));
174 self
175 }
176
177 pub fn require_tenant(mut self, require: bool) -> Self {
179 self.require_tenant = require;
180 self
181 }
182
183 pub fn default_tenant(mut self, tenant: impl Into<String>) -> Self {
185 self.default_tenant = Some(tenant.into());
186 self
187 }
188
189 pub fn allow_bypass(mut self, allow: bool) -> Self {
191 self.allow_bypass = allow;
192 self
193 }
194
195 pub fn resolver<R: TenantResolver + 'static>(mut self, resolver: R) -> Self {
197 self.resolver = Some(Arc::new(resolver));
198 self
199 }
200
201 pub fn enforce_on_writes(mut self, enforce: bool) -> Self {
203 self.enforce_on_writes = enforce;
204 self
205 }
206
207 pub fn log_context(mut self, log: bool) -> Self {
209 self.log_tenant_context = log;
210 self
211 }
212
213 pub fn build(self) -> TenantConfig {
215 TenantConfig {
216 strategy: self
217 .strategy
218 .unwrap_or_else(|| IsolationStrategy::row_level("tenant_id")),
219 require_tenant: self.require_tenant,
220 default_tenant: self.default_tenant,
221 allow_bypass: self.allow_bypass,
222 resolver: self.resolver,
223 enforce_on_writes: self.enforce_on_writes,
224 log_tenant_context: self.log_tenant_context,
225 }
226 }
227}
228
229#[cfg(test)]
230mod tests {
231 use super::*;
232
233 #[test]
234 fn test_row_level_config() {
235 let config = TenantConfig::row_level("org_id")
236 .with_default_tenant("default")
237 .with_logging();
238
239 assert!(config.strategy.is_row_level());
240 assert_eq!(config.default_tenant, Some("default".to_string()));
241 assert!(config.log_tenant_context);
242 }
243
244 #[test]
245 fn test_schema_config() {
246 let config = TenantConfig::schema_based().optional().without_bypass();
247
248 assert!(config.strategy.is_schema_based());
249 assert!(!config.require_tenant);
250 assert!(!config.allow_bypass);
251 }
252
253 #[test]
254 fn test_builder() {
255 let config = TenantConfig::builder()
256 .row_level(RowLevelConfig::new("tenant_id").with_database_rls())
257 .default_tenant("system")
258 .log_context(true)
259 .build();
260
261 assert!(config.strategy.is_row_level());
262 assert!(config.row_level_config().unwrap().use_database_rls);
263 }
264}