1use std::sync::{Arc, RwLock};
12use std::collections::HashMap;
13use std::time::{Duration, Instant};
14use chrono::{DateTime, Utc};
15use serde::{Deserialize, Serialize};
16
17pub type ChangeListener = Box<dyn Fn(&ConfigurationChangeEvent) + Send + Sync>;
19use serde_json::Value;
20
21use super::tenant::TenantId;
22use crate::error::{EventualiError, Result};
23
24#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
26#[derive(Default)]
27pub enum ConfigurationEnvironment {
28 Development,
29 Staging,
30 #[default]
31 Production,
32 Testing,
33}
34
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(tag = "type", content = "value")]
39pub enum ConfigurationValue {
40 String(String),
41 Integer(i64),
42 Float(f64),
43 Boolean(bool),
44 Array(Vec<ConfigurationValue>),
45 Object(HashMap<String, ConfigurationValue>),
46}
47
48impl ConfigurationValue {
49 pub fn validate(&self, schema: &ConfigurationSchema) -> Result<()> {
51 match (self, schema) {
52 (ConfigurationValue::String(s), ConfigurationSchema::String { min_length, max_length, pattern }) => {
53 if let Some(min) = min_length {
54 if s.len() < *min {
55 return Err(EventualiError::Tenant(format!("String too short: {} < {}", s.len(), min)));
56 }
57 }
58 if let Some(max) = max_length {
59 if s.len() > *max {
60 return Err(EventualiError::Tenant(format!("String too long: {} > {}", s.len(), max)));
61 }
62 }
63 if let Some(pattern) = pattern {
64 if !regex::Regex::new(pattern).unwrap().is_match(s) {
65 return Err(EventualiError::Tenant(format!("String doesn't match pattern: {pattern}")));
66 }
67 }
68 Ok(())
69 },
70 (ConfigurationValue::Integer(i), ConfigurationSchema::Integer { min, max }) => {
71 if let Some(min_val) = min {
72 if i < min_val {
73 return Err(EventualiError::Tenant(format!("Integer too small: {i} < {min_val}")));
74 }
75 }
76 if let Some(max_val) = max {
77 if i > max_val {
78 return Err(EventualiError::Tenant(format!("Integer too large: {i} > {max_val}")));
79 }
80 }
81 Ok(())
82 },
83 (ConfigurationValue::Float(f), ConfigurationSchema::Float { min, max }) => {
84 if let Some(min_val) = min {
85 if f < min_val {
86 return Err(EventualiError::Tenant(format!("Float too small: {f} < {min_val}")));
87 }
88 }
89 if let Some(max_val) = max {
90 if f > max_val {
91 return Err(EventualiError::Tenant(format!("Float too large: {f} > {max_val}")));
92 }
93 }
94 Ok(())
95 },
96 (ConfigurationValue::Boolean(_), ConfigurationSchema::Boolean) => Ok(()),
97 (ConfigurationValue::Array(items), ConfigurationSchema::Array { item_schema, min_items, max_items }) => {
98 if let Some(min) = min_items {
99 if items.len() < *min {
100 return Err(EventualiError::Tenant(format!("Array too small: {} < {}", items.len(), min)));
101 }
102 }
103 if let Some(max) = max_items {
104 if items.len() > *max {
105 return Err(EventualiError::Tenant(format!("Array too large: {} > {}", items.len(), max)));
106 }
107 }
108 for item in items {
109 item.validate(item_schema)?;
110 }
111 Ok(())
112 },
113 (ConfigurationValue::Object(obj), ConfigurationSchema::Object { properties, required }) => {
114 for req_field in required {
116 if !obj.contains_key(req_field) {
117 return Err(EventualiError::Tenant(format!("Required field missing: {req_field}")));
118 }
119 }
120 for (key, value) in obj {
122 if let Some(prop_schema) = properties.get(key) {
123 value.validate(prop_schema)?;
124 }
125 }
126 Ok(())
127 },
128 _ => Err(EventualiError::Tenant("Configuration type mismatch".to_string())),
129 }
130 }
131
132 pub fn to_json(&self) -> Value {
134 match self {
135 ConfigurationValue::String(s) => Value::String(s.clone()),
136 ConfigurationValue::Integer(i) => Value::Number(serde_json::Number::from(*i)),
137 ConfigurationValue::Float(f) => Value::Number(serde_json::Number::from_f64(*f).unwrap_or_else(|| serde_json::Number::from(0))),
138 ConfigurationValue::Boolean(b) => Value::Bool(*b),
139 ConfigurationValue::Array(arr) => {
140 Value::Array(arr.iter().map(|v| v.to_json()).collect())
141 },
142 ConfigurationValue::Object(obj) => {
143 let mut map = serde_json::Map::new();
144 for (k, v) in obj {
145 map.insert(k.clone(), v.to_json());
146 }
147 Value::Object(map)
148 },
149 }
150 }
151
152 pub fn from_json(value: &Value) -> Self {
154 match value {
155 Value::String(s) => ConfigurationValue::String(s.clone()),
156 Value::Number(n) => {
157 if let Some(i) = n.as_i64() {
158 ConfigurationValue::Integer(i)
159 } else if let Some(f) = n.as_f64() {
160 ConfigurationValue::Float(f)
161 } else {
162 ConfigurationValue::Float(0.0)
163 }
164 },
165 Value::Bool(b) => ConfigurationValue::Boolean(*b),
166 Value::Array(arr) => {
167 ConfigurationValue::Array(arr.iter().map(ConfigurationValue::from_json).collect())
168 },
169 Value::Object(obj) => {
170 let mut map = HashMap::new();
171 for (k, v) in obj {
172 map.insert(k.clone(), ConfigurationValue::from_json(v));
173 }
174 ConfigurationValue::Object(map)
175 },
176 Value::Null => ConfigurationValue::String("".to_string()),
177 }
178 }
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize)]
183#[serde(tag = "type")]
184pub enum ConfigurationSchema {
185 String {
186 min_length: Option<usize>,
187 max_length: Option<usize>,
188 pattern: Option<String>,
189 },
190 Integer {
191 min: Option<i64>,
192 max: Option<i64>,
193 },
194 Float {
195 min: Option<f64>,
196 max: Option<f64>,
197 },
198 Boolean,
199 Array {
200 item_schema: Box<ConfigurationSchema>,
201 min_items: Option<usize>,
202 max_items: Option<usize>,
203 },
204 Object {
205 properties: HashMap<String, ConfigurationSchema>,
206 required: Vec<String>,
207 },
208}
209
210#[derive(Debug, Clone, Serialize, Deserialize)]
212pub struct ConfigurationEntry {
213 pub key: String,
214 pub value: ConfigurationValue,
215 pub schema: ConfigurationSchema,
216 pub environment: ConfigurationEnvironment,
217 pub description: Option<String>,
218 pub created_at: DateTime<Utc>,
219 pub updated_at: DateTime<Utc>,
220 pub version: u64,
221 pub is_sensitive: bool,
222 pub tags: Vec<String>,
223}
224
225impl ConfigurationEntry {
226 pub fn new(
227 key: String,
228 value: ConfigurationValue,
229 schema: ConfigurationSchema,
230 environment: ConfigurationEnvironment,
231 ) -> Result<Self> {
232 value.validate(&schema)?;
234
235 let now = Utc::now();
236 Ok(ConfigurationEntry {
237 key,
238 value,
239 schema,
240 environment,
241 description: None,
242 created_at: now,
243 updated_at: now,
244 version: 1,
245 is_sensitive: false,
246 tags: Vec::new(),
247 })
248 }
249
250 pub fn update_value(&mut self, new_value: ConfigurationValue) -> Result<()> {
251 new_value.validate(&self.schema)?;
253
254 self.value = new_value;
255 self.updated_at = Utc::now();
256 self.version += 1;
257 Ok(())
258 }
259}
260
261#[derive(Debug, Clone, Serialize, Deserialize)]
263pub struct ConfigurationTemplate {
264 pub name: String,
265 pub description: String,
266 pub entries: Vec<ConfigurationEntry>,
267 pub inheritance_chain: Vec<String>, pub created_at: DateTime<Utc>,
269 pub updated_at: DateTime<Utc>,
270}
271
272impl ConfigurationTemplate {
273 pub fn new(name: String, description: String) -> Self {
274 let now = Utc::now();
275 ConfigurationTemplate {
276 name,
277 description,
278 entries: Vec::new(),
279 inheritance_chain: Vec::new(),
280 created_at: now,
281 updated_at: now,
282 }
283 }
284
285 pub fn add_entry(&mut self, entry: ConfigurationEntry) {
286 self.entries.push(entry);
287 self.updated_at = Utc::now();
288 }
289
290 pub fn resolve_with_inheritance(&self, templates: &HashMap<String, ConfigurationTemplate>) -> Result<Vec<ConfigurationEntry>> {
292 let mut resolved_entries = Vec::new();
293 let mut resolved_keys = std::collections::HashSet::new();
294
295 for parent_name in &self.inheritance_chain {
297 if let Some(parent_template) = templates.get(parent_name) {
298 for entry in &parent_template.entries {
299 if !resolved_keys.contains(&entry.key) {
300 resolved_entries.push(entry.clone());
301 resolved_keys.insert(entry.key.clone());
302 }
303 }
304 }
305 }
306
307 for entry in &self.entries {
309 if let Some(existing_idx) = resolved_entries.iter().position(|e| e.key == entry.key) {
310 resolved_entries[existing_idx] = entry.clone();
311 } else {
312 resolved_entries.push(entry.clone());
313 }
314 }
315
316 Ok(resolved_entries)
317 }
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct ConfigurationChangeEvent {
323 pub tenant_id: TenantId,
324 pub key: String,
325 pub old_value: Option<ConfigurationValue>,
326 pub new_value: ConfigurationValue,
327 pub environment: ConfigurationEnvironment,
328 pub changed_by: String, pub change_reason: String,
330 pub timestamp: DateTime<Utc>,
331 pub rollback_point: bool,
332}
333
334#[derive(Debug, Clone, Serialize, Deserialize)]
336pub struct ConfigurationMetrics {
337 pub tenant_id: TenantId,
338 pub total_configurations: usize,
339 pub configurations_by_environment: HashMap<ConfigurationEnvironment, usize>,
340 pub last_change_timestamp: Option<DateTime<Utc>>,
341 pub total_changes_today: usize,
342 pub hot_reload_count: usize,
343 pub validation_errors_count: usize,
344 pub cache_hit_rate: f64,
345 pub average_retrieval_time_ms: f64,
346}
347
348#[derive(Debug)]
350struct ConfigurationCache {
351 entries: HashMap<String, (ConfigurationValue, Instant)>,
352 ttl: Duration,
353 hit_count: u64,
354 miss_count: u64,
355}
356
357impl ConfigurationCache {
358 fn new(ttl_seconds: u64) -> Self {
359 ConfigurationCache {
360 entries: HashMap::new(),
361 ttl: Duration::from_secs(ttl_seconds),
362 hit_count: 0,
363 miss_count: 0,
364 }
365 }
366
367 fn get(&mut self, key: &str) -> Option<ConfigurationValue> {
368 if let Some((value, timestamp)) = self.entries.get(key) {
369 if timestamp.elapsed() < self.ttl {
370 self.hit_count += 1;
371 return Some(value.clone());
372 } else {
373 self.entries.remove(key);
374 }
375 }
376 self.miss_count += 1;
377 None
378 }
379
380 fn set(&mut self, key: String, value: ConfigurationValue) {
381 self.entries.insert(key, (value, Instant::now()));
382 }
383
384 fn invalidate(&mut self, key: &str) {
385 self.entries.remove(key);
386 }
387
388 fn hit_rate(&self) -> f64 {
389 if self.hit_count + self.miss_count == 0 {
390 0.0
391 } else {
392 self.hit_count as f64 / (self.hit_count + self.miss_count) as f64
393 }
394 }
395
396 fn clear(&mut self) {
397 self.entries.clear();
398 }
399}
400
401pub struct TenantConfigurationManager {
403 tenant_id: TenantId,
404 configurations: Arc<RwLock<HashMap<(String, ConfigurationEnvironment), ConfigurationEntry>>>,
405 templates: Arc<RwLock<HashMap<String, ConfigurationTemplate>>>,
406 change_history: Arc<RwLock<Vec<ConfigurationChangeEvent>>>,
407 cache: Arc<RwLock<ConfigurationCache>>,
408 current_environment: ConfigurationEnvironment,
409 hot_reload_enabled: bool,
410 validation_enabled: bool,
411 change_listeners: Arc<RwLock<Vec<ChangeListener>>>,
412}
413
414impl TenantConfigurationManager {
415 pub fn new(tenant_id: TenantId) -> Self {
416 TenantConfigurationManager {
417 tenant_id,
418 configurations: Arc::new(RwLock::new(HashMap::new())),
419 templates: Arc::new(RwLock::new(HashMap::new())),
420 change_history: Arc::new(RwLock::new(Vec::new())),
421 cache: Arc::new(RwLock::new(ConfigurationCache::new(300))), current_environment: ConfigurationEnvironment::Production,
423 hot_reload_enabled: true,
424 validation_enabled: true,
425 change_listeners: Arc::new(RwLock::new(Vec::new())),
426 }
427 }
428
429 pub fn set_configuration(
431 &self,
432 key: String,
433 value: ConfigurationValue,
434 schema: ConfigurationSchema,
435 environment: Option<ConfigurationEnvironment>,
436 changed_by: String,
437 change_reason: String,
438 ) -> Result<()> {
439 let env = environment.unwrap_or_else(|| self.current_environment.clone());
440
441 if self.validation_enabled {
443 value.validate(&schema)?;
444 }
445
446 let mut configurations = self.configurations.write().unwrap();
447 let config_key = (key.clone(), env.clone());
448
449 let old_value = configurations.get(&config_key).map(|entry| entry.value.clone());
451
452 let entry = if let Some(existing) = configurations.get(&config_key) {
454 let mut updated = existing.clone();
455 updated.update_value(value.clone())?;
456 updated
457 } else {
458 ConfigurationEntry::new(key.clone(), value.clone(), schema, env.clone())?
459 };
460
461 configurations.insert(config_key, entry);
462 drop(configurations); {
466 let mut cache = self.cache.write().unwrap();
467 cache.invalidate(&key);
468 }
469
470 let change_event = ConfigurationChangeEvent {
472 tenant_id: self.tenant_id.clone(),
473 key: key.clone(),
474 old_value,
475 new_value: value,
476 environment: env,
477 changed_by,
478 change_reason,
479 timestamp: Utc::now(),
480 rollback_point: false,
481 };
482
483 {
485 let mut history = self.change_history.write().unwrap();
486 history.push(change_event.clone());
487
488 if history.len() > 1000 {
490 let excess = history.len() - 1000;
491 history.drain(0..excess);
492 }
493 }
494
495 if self.hot_reload_enabled {
497 let listeners = self.change_listeners.read().unwrap();
498 for listener in listeners.iter() {
499 listener(&change_event);
500 }
501 }
502
503 Ok(())
504 }
505
506 pub fn get_configuration(
508 &self,
509 key: &str,
510 environment: Option<ConfigurationEnvironment>,
511 ) -> Option<ConfigurationValue> {
512 let env = environment.unwrap_or_else(|| self.current_environment.clone());
513 let cache_key = format!("{key}:{env:?}");
514
515 {
517 let mut cache = self.cache.write().unwrap();
518 if let Some(cached_value) = cache.get(&cache_key) {
519 return Some(cached_value);
520 }
521 }
522
523 let configurations = self.configurations.read().unwrap();
525 if let Some(entry) = configurations.get(&(key.to_string(), env.clone())) {
526 let value = entry.value.clone();
527
528 {
530 let mut cache = self.cache.write().unwrap();
531 cache.set(cache_key, value.clone());
532 }
533
534 return Some(value);
535 }
536
537 let fallback_envs = match env {
539 ConfigurationEnvironment::Development => vec![ConfigurationEnvironment::Staging, ConfigurationEnvironment::Production],
540 ConfigurationEnvironment::Testing => vec![ConfigurationEnvironment::Development, ConfigurationEnvironment::Production],
541 ConfigurationEnvironment::Staging => vec![ConfigurationEnvironment::Production],
542 ConfigurationEnvironment::Production => vec![],
543 };
544
545 for fallback_env in fallback_envs {
546 if let Some(entry) = configurations.get(&(key.to_string(), fallback_env)) {
547 let value = entry.value.clone();
548
549 {
551 let mut cache = self.cache.write().unwrap();
552 cache.set(cache_key, value.clone());
553 }
554
555 return Some(value);
556 }
557 }
558
559 None
560 }
561
562 pub fn get_all_configurations(
564 &self,
565 environment: Option<ConfigurationEnvironment>,
566 ) -> HashMap<String, ConfigurationValue> {
567 let env = environment.unwrap_or_else(|| self.current_environment.clone());
568 let configurations = self.configurations.read().unwrap();
569
570 let mut result = HashMap::new();
571 for ((key, config_env), entry) in configurations.iter() {
572 if *config_env == env {
573 result.insert(key.clone(), entry.value.clone());
574 }
575 }
576
577 result
578 }
579
580 pub fn apply_template(&self, template_name: &str, environment: ConfigurationEnvironment) -> Result<usize> {
582 let templates = self.templates.read().unwrap();
583 let template = templates.get(template_name)
584 .ok_or_else(|| EventualiError::Tenant(format!("Template not found: {template_name}")))?;
585
586 let resolved_entries = template.resolve_with_inheritance(&templates)?;
587 drop(templates); let mut applied_count = 0;
590 for mut entry in resolved_entries {
591 entry.environment = environment.clone();
593
594 let config_key = (entry.key.clone(), environment.clone());
595 {
596 let mut configurations = self.configurations.write().unwrap();
597 configurations.insert(config_key, entry);
598 }
599
600 applied_count += 1;
601 }
602
603 {
605 let mut cache = self.cache.write().unwrap();
606 cache.clear();
607 }
608
609 Ok(applied_count)
610 }
611
612 pub fn create_template(&self, template: ConfigurationTemplate) -> Result<()> {
614 let mut templates = self.templates.write().unwrap();
615 templates.insert(template.name.clone(), template);
616 Ok(())
617 }
618
619 pub fn delete_configuration(
621 &self,
622 key: &str,
623 environment: Option<ConfigurationEnvironment>,
624 changed_by: String,
625 change_reason: String,
626 ) -> Result<bool> {
627 let env = environment.unwrap_or_else(|| self.current_environment.clone());
628 let config_key = (key.to_string(), env.clone());
629
630 let mut configurations = self.configurations.write().unwrap();
631 if let Some(removed_entry) = configurations.remove(&config_key) {
632 drop(configurations); {
636 let mut cache = self.cache.write().unwrap();
637 cache.invalidate(key);
638 }
639
640 let change_event = ConfigurationChangeEvent {
642 tenant_id: self.tenant_id.clone(),
643 key: key.to_string(),
644 old_value: Some(removed_entry.value),
645 new_value: ConfigurationValue::String("".to_string()), environment: env,
647 changed_by,
648 change_reason,
649 timestamp: Utc::now(),
650 rollback_point: false,
651 };
652
653 {
655 let mut history = self.change_history.write().unwrap();
656 history.push(change_event.clone());
657 }
658
659 if self.hot_reload_enabled {
661 let listeners = self.change_listeners.read().unwrap();
662 for listener in listeners.iter() {
663 listener(&change_event);
664 }
665 }
666
667 Ok(true)
668 } else {
669 Ok(false)
670 }
671 }
672
673 pub fn get_metrics(&self) -> ConfigurationMetrics {
675 let configurations = self.configurations.read().unwrap();
676 let history = self.change_history.read().unwrap();
677 let cache = self.cache.read().unwrap();
678
679 let mut configurations_by_environment = HashMap::new();
680 for ((_key, env), _entry) in configurations.iter() {
681 *configurations_by_environment.entry(env.clone()).or_insert(0) += 1;
682 }
683
684 let today = Utc::now().date_naive();
685 let total_changes_today = history.iter()
686 .filter(|event| event.timestamp.date_naive() == today)
687 .count();
688
689 let last_change_timestamp = history.last().map(|event| event.timestamp);
690
691 ConfigurationMetrics {
692 tenant_id: self.tenant_id.clone(),
693 total_configurations: configurations.len(),
694 configurations_by_environment,
695 last_change_timestamp,
696 total_changes_today,
697 hot_reload_count: 0, validation_errors_count: 0, cache_hit_rate: cache.hit_rate() * 100.0,
700 average_retrieval_time_ms: 1.0, }
702 }
703
704 pub fn get_change_history(&self, limit: Option<usize>) -> Vec<ConfigurationChangeEvent> {
706 let history = self.change_history.read().unwrap();
707 let limit = limit.unwrap_or(100);
708
709 if history.len() > limit {
710 history[history.len() - limit..].to_vec()
711 } else {
712 history.clone()
713 }
714 }
715
716 pub fn create_rollback_point(&self, changed_by: String, reason: String) -> Result<()> {
718 let rollback_event = ConfigurationChangeEvent {
719 tenant_id: self.tenant_id.clone(),
720 key: "ROLLBACK_POINT".to_string(),
721 old_value: None,
722 new_value: ConfigurationValue::String(reason.clone()),
723 environment: self.current_environment.clone(),
724 changed_by,
725 change_reason: reason,
726 timestamp: Utc::now(),
727 rollback_point: true,
728 };
729
730 let mut history = self.change_history.write().unwrap();
731 history.push(rollback_event);
732
733 Ok(())
734 }
735
736 pub fn rollback_to_point(&self, rollback_timestamp: DateTime<Utc>) -> Result<usize> {
738 let history = self.change_history.read().unwrap();
739
740 let _rollback_index = history.iter()
742 .position(|event| event.rollback_point && event.timestamp <= rollback_timestamp)
743 .ok_or_else(|| EventualiError::Tenant("Rollback point not found".to_string()))?;
744
745 drop(history); let rollback_count = 0;
749 Ok(rollback_count)
753 }
754
755 pub fn add_change_listener<F>(&self, listener: F)
757 where
758 F: Fn(&ConfigurationChangeEvent) + Send + Sync + 'static,
759 {
760 let mut listeners = self.change_listeners.write().unwrap();
761 listeners.push(Box::new(listener));
762 }
763
764 pub fn set_environment(&mut self, environment: ConfigurationEnvironment) {
766 self.current_environment = environment;
767
768 let mut cache = self.cache.write().unwrap();
770 cache.clear();
771 }
772
773 pub fn set_hot_reload_enabled(&mut self, enabled: bool) {
775 self.hot_reload_enabled = enabled;
776 }
777
778 pub fn set_validation_enabled(&mut self, enabled: bool) {
780 self.validation_enabled = enabled;
781 }
782
783 pub fn export_configurations(&self, environment: Option<ConfigurationEnvironment>) -> Value {
785 let configurations = self.get_all_configurations(environment);
786 let mut result = serde_json::Map::new();
787
788 for (key, value) in configurations {
789 result.insert(key, value.to_json());
790 }
791
792 Value::Object(result)
793 }
794
795 pub fn import_configurations(
797 &self,
798 json_data: &Value,
799 environment: ConfigurationEnvironment,
800 changed_by: String,
801 ) -> Result<usize> {
802 if let Value::Object(obj) = json_data {
803 let mut imported_count = 0;
804
805 for (key, value) in obj {
806 let config_value = ConfigurationValue::from_json(value);
807 let schema = match config_value {
809 ConfigurationValue::String(_) => ConfigurationSchema::String {
810 min_length: None,
811 max_length: None,
812 pattern: None,
813 },
814 ConfigurationValue::Integer(_) => ConfigurationSchema::Integer {
815 min: None,
816 max: None,
817 },
818 ConfigurationValue::Float(_) => ConfigurationSchema::Float {
819 min: None,
820 max: None,
821 },
822 ConfigurationValue::Boolean(_) => ConfigurationSchema::Boolean,
823 ConfigurationValue::Array(_) => ConfigurationSchema::Array {
824 item_schema: Box::new(ConfigurationSchema::String {
825 min_length: None,
826 max_length: None,
827 pattern: None,
828 }),
829 min_items: None,
830 max_items: None,
831 },
832 ConfigurationValue::Object(_) => ConfigurationSchema::Object {
833 properties: HashMap::new(),
834 required: Vec::new(),
835 },
836 };
837
838 self.set_configuration(
839 key.clone(),
840 config_value,
841 schema,
842 Some(environment.clone()),
843 changed_by.clone(),
844 "Imported from JSON".to_string(),
845 )?;
846
847 imported_count += 1;
848 }
849
850 Ok(imported_count)
851 } else {
852 Err(EventualiError::Tenant("Invalid JSON format for import".to_string()))
853 }
854 }
855}
856
857#[cfg(test)]
858mod tests {
859 use super::*;
860
861 #[test]
862 fn test_configuration_value_validation() {
863 let schema = ConfigurationSchema::String {
864 min_length: Some(3),
865 max_length: Some(10),
866 pattern: None,
867 };
868
869 let valid_value = ConfigurationValue::String("hello".to_string());
870 assert!(valid_value.validate(&schema).is_ok());
871
872 let invalid_value = ConfigurationValue::String("hi".to_string());
873 assert!(invalid_value.validate(&schema).is_err());
874 }
875
876 #[test]
877 fn test_configuration_manager_basic_operations() {
878 let tenant_id = TenantId::new("test-tenant".to_string()).unwrap();
879 let manager = TenantConfigurationManager::new(tenant_id);
880
881 let schema = ConfigurationSchema::String {
883 min_length: None,
884 max_length: None,
885 pattern: None,
886 };
887
888 manager.set_configuration(
889 "test_key".to_string(),
890 ConfigurationValue::String("test_value".to_string()),
891 schema,
892 None,
893 "test_user".to_string(),
894 "Testing".to_string(),
895 ).unwrap();
896
897 let value = manager.get_configuration("test_key", None);
899 assert!(value.is_some());
900
901 if let Some(ConfigurationValue::String(s)) = value {
902 assert_eq!(s, "test_value");
903 } else {
904 panic!("Expected string value");
905 }
906 }
907
908 #[test]
909 fn test_configuration_template() {
910 let mut template = ConfigurationTemplate::new(
911 "test_template".to_string(),
912 "Test template".to_string(),
913 );
914
915 let entry = ConfigurationEntry::new(
916 "test_key".to_string(),
917 ConfigurationValue::String("template_value".to_string()),
918 ConfigurationSchema::String {
919 min_length: None,
920 max_length: None,
921 pattern: None,
922 },
923 ConfigurationEnvironment::Development,
924 ).unwrap();
925
926 template.add_entry(entry);
927 assert_eq!(template.entries.len(), 1);
928 }
929}