scim_server/multi_tenant/
provider.rs1use crate::resource::RequestContext;
8#[cfg(test)]
9use crate::resource::TenantContext;
10
11pub trait TenantValidator {
16 fn validate_tenant_context(
18 &self,
19 expected_tenant_id: &str,
20 context: &RequestContext,
21 ) -> Result<(), String> {
22 match context.tenant_id() {
23 Some(actual_tenant_id) if actual_tenant_id == expected_tenant_id => Ok(()),
24 Some(actual_tenant_id) => Err(format!(
25 "Tenant mismatch: context has '{}', operation requested '{}'",
26 actual_tenant_id, expected_tenant_id
27 )),
28 None => Err(format!(
29 "Multi-tenant operation requested '{}' but context has no tenant",
30 expected_tenant_id
31 )),
32 }
33 }
34
35 fn validate_single_tenant_context(&self, context: &RequestContext) -> Result<(), String> {
37 match context.tenant_id() {
38 None => Ok(()),
39 Some(tenant_id) => Err(format!(
40 "Single-tenant operation but context has tenant '{}'",
41 tenant_id
42 )),
43 }
44 }
45
46 fn require_tenant_context(&self, context: &RequestContext) -> Result<(), String> {
48 match context.tenant_context {
49 Some(_) => Ok(()),
50 None => Err("Multi-tenant operation requires tenant context".to_string()),
51 }
52 }
53}
54
55impl<T> TenantValidator for T {}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61
62 struct MockValidator;
63
64 #[test]
65 fn test_tenant_validator_success() {
66 let validator = MockValidator;
67 let tenant_context = TenantContext {
68 tenant_id: "test-tenant".to_string(),
69 client_id: "client".to_string(),
70 permissions: Default::default(),
71 isolation_level: Default::default(),
72 };
73 let context = RequestContext::with_tenant_generated_id(tenant_context);
74
75 assert!(
77 validator
78 .validate_tenant_context("test-tenant", &context)
79 .is_ok()
80 );
81
82 assert!(validator.require_tenant_context(&context).is_ok());
84 }
85
86 #[test]
87 fn test_tenant_validator_failure() {
88 let validator = MockValidator;
89 let tenant_context = TenantContext {
90 tenant_id: "test-tenant".to_string(),
91 client_id: "client".to_string(),
92 permissions: Default::default(),
93 isolation_level: Default::default(),
94 };
95 let context = RequestContext::with_tenant_generated_id(tenant_context);
96
97 let result = validator.validate_tenant_context("different-tenant", &context);
99 assert!(result.is_err());
100 assert!(result.unwrap_err().contains("Tenant mismatch"));
101 }
102
103 #[test]
104 fn test_single_tenant_validation() {
105 let validator = MockValidator;
106
107 let context = RequestContext::with_generated_id();
109 assert!(validator.validate_single_tenant_context(&context).is_ok());
110
111 let tenant_context = TenantContext {
113 tenant_id: "test-tenant".to_string(),
114 client_id: "client".to_string(),
115 permissions: Default::default(),
116 isolation_level: Default::default(),
117 };
118 let multi_context = RequestContext::with_tenant_generated_id(tenant_context);
119 assert!(
120 validator
121 .validate_single_tenant_context(&multi_context)
122 .is_err()
123 );
124 }
125
126 #[test]
127 fn test_require_tenant_context() {
128 let validator = MockValidator;
129
130 let single_context = RequestContext::with_generated_id();
132 assert!(validator.require_tenant_context(&single_context).is_err());
133
134 let tenant_context = TenantContext {
136 tenant_id: "test-tenant".to_string(),
137 client_id: "client".to_string(),
138 permissions: Default::default(),
139 isolation_level: Default::default(),
140 };
141 let multi_context = RequestContext::with_tenant_generated_id(tenant_context);
142 assert!(validator.require_tenant_context(&multi_context).is_ok());
143 }
144}