scirs2_core/config/
production.rs

1//! # Production-Level Configuration Management
2//!
3//! This module provides comprehensive configuration management for production deployments,
4//! including environment-specific settings, validation, hot reloading, and feature flags.
5
6use crate::error::{CoreError, CoreResult, ErrorContext};
7use std::collections::HashMap;
8use std::env;
9use std::fmt;
10use std::fs;
11use std::path::Path;
12use std::str::FromStr;
13use std::sync::RwLock;
14use std::time::SystemTime;
15
16/// Configuration source types
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub enum ConfigSource {
19    /// Environment variables
20    Environment,
21    /// Configuration file (JSON, YAML, TOML)
22    File(String),
23    /// Command line arguments
24    CommandLine,
25    /// Default values
26    Default,
27    /// Runtime override
28    Override,
29}
30
31/// Configuration value with metadata
32#[derive(Debug, Clone)]
33pub struct ConfigValue {
34    /// The actual value
35    pub value: String,
36    /// Source of the configuration value
37    pub source: ConfigSource,
38    /// Timestamp when the value was set
39    pub timestamp: SystemTime,
40    /// Whether the value is sensitive (will be masked in logs)
41    pub is_sensitive: bool,
42    /// Description of what this configuration does
43    pub description: Option<String>,
44}
45
46/// Configuration entry with validation
47#[derive(Debug, Clone)]
48pub struct ConfigEntry {
49    /// Configuration key
50    pub key: String,
51    /// Current value
52    pub value: ConfigValue,
53    /// Validation function name
54    pub validator: Option<String>,
55    /// Whether this configuration can be changed at runtime
56    pub hot_reloadable: bool,
57    /// Default value
58    pub defaultvalue: Option<String>,
59    /// Environment variable name (if different from key)
60    pub env_var: Option<String>,
61}
62
63/// Environment type for configuration
64#[derive(Debug, Clone, PartialEq, Eq, Hash)]
65pub enum Environment {
66    /// Development environment
67    Development,
68    /// Testing environment
69    Testing,
70    /// Staging environment
71    Staging,
72    /// Production environment
73    Production,
74    /// Custom environment
75    Custom(String),
76}
77
78impl fmt::Display for Environment {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        match self {
81            Environment::Development => write!(f, "development"),
82            Environment::Testing => write!(f, "testing"),
83            Environment::Staging => write!(f, "staging"),
84            Environment::Production => write!(f, "production"),
85            Environment::Custom(name) => write!(f, "{name}"),
86        }
87    }
88}
89
90impl std::str::FromStr for Environment {
91    type Err = std::convert::Infallible;
92
93    /// Parse environment from string
94    fn from_str(s: &str) -> Result<Self, Self::Err> {
95        let env = match s.to_lowercase().as_str() {
96            "dev" | "development" => Environment::Development,
97            "test" | "testing" => Environment::Testing,
98            "stage" | "staging" => Environment::Staging,
99            "prod" | "production" => Environment::Production,
100            name => Environment::Custom(name.to_string()),
101        };
102        Ok(env)
103    }
104}
105
106/// Feature flag configuration
107#[derive(Debug, Clone)]
108pub struct FeatureFlag {
109    /// Feature name
110    pub name: String,
111    /// Whether the feature is enabled
112    pub enabled: bool,
113    /// Rollout percentage (0-100)
114    pub rollout_percentage: f64,
115    /// Target environments
116    pub environments: Vec<Environment>,
117    /// Target user groups
118    pub user_groups: Vec<String>,
119    /// Description of the feature
120    pub description: Option<String>,
121    /// Timestamp when the flag was last modified
122    pub last_modified: SystemTime,
123}
124
125/// Configuration validation result
126#[derive(Debug, Clone)]
127pub struct ValidationResult {
128    /// Whether validation passed
129    pub is_valid: bool,
130    /// Validation errors
131    pub errors: Vec<String>,
132    /// Validation warnings
133    pub warnings: Vec<String>,
134}
135
136/// Trait for configuration validators
137pub trait ConfigValidator: Send + Sync {
138    /// Validate a configuration value
139    fn validate(&self, value: &str) -> ValidationResult;
140
141    /// Get the name of this validator
142    fn name(&self) -> &str;
143
144    /// Get help text for this validator
145    fn help(&self) -> &str;
146}
147
148/// Built-in validators
149/// Validates positive integers
150pub struct PositiveIntValidator;
151
152impl ConfigValidator for PositiveIntValidator {
153    fn validate(&self, value: &str) -> ValidationResult {
154        match value.parse::<i64>() {
155            Ok(n) if n > 0 => ValidationResult {
156                is_valid: true,
157                errors: Vec::new(),
158                warnings: Vec::new(),
159            },
160            Ok(n) => ValidationResult {
161                is_valid: false,
162                errors: vec![format!("Value must be positive, got {n}")],
163                warnings: Vec::new(),
164            },
165            Err(_) => ValidationResult {
166                is_valid: false,
167                errors: vec![format!("Invalid integer: {value}")],
168                warnings: Vec::new(),
169            },
170        }
171    }
172
173    fn name(&self) -> &str {
174        "positive_int"
175    }
176
177    fn help(&self) -> &str {
178        "Must be a positive integer greater than 0"
179    }
180}
181
182/// Validates port numbers
183pub struct PortValidator;
184
185impl ConfigValidator for PortValidator {
186    fn validate(&self, value: &str) -> ValidationResult {
187        match value.parse::<u16>() {
188            Ok(0) => ValidationResult {
189                is_valid: false,
190                errors: vec!["Port 0 is not allowed".to_string()],
191                warnings: Vec::new(),
192            },
193            Ok(port) => ValidationResult {
194                is_valid: true,
195                errors: Vec::new(),
196                warnings: if port < 1024 {
197                    vec!["Port number is below 1024 (requires root privileges)".to_string()]
198                } else {
199                    Vec::new()
200                },
201            },
202            Err(_) => ValidationResult {
203                is_valid: false,
204                errors: vec![format!("Invalid port number: {value}")],
205                warnings: Vec::new(),
206            },
207        }
208    }
209
210    fn name(&self) -> &str {
211        "port"
212    }
213
214    fn help(&self) -> &str {
215        "Must be a valid port number (1-65535)"
216    }
217}
218
219/// Validates URL format
220pub struct UrlValidator;
221
222impl ConfigValidator for UrlValidator {
223    fn validate(&self, value: &str) -> ValidationResult {
224        // Simple URL validation (in production, use a proper URL parsing library)
225        if value.starts_with("http://") || value.starts_with("https://") {
226            ValidationResult {
227                is_valid: true,
228                errors: Vec::new(),
229                warnings: if value.starts_with("http://") {
230                    vec!["Using HTTP instead of HTTPS may be insecure".to_string()]
231                } else {
232                    Vec::new()
233                },
234            }
235        } else {
236            ValidationResult {
237                is_valid: false,
238                errors: vec![format!("Invalid URL format: {value}")],
239                warnings: Vec::new(),
240            }
241        }
242    }
243
244    fn name(&self) -> &str {
245        "url"
246    }
247
248    fn help(&self) -> &str {
249        "Must be a valid HTTP or HTTPS URL"
250    }
251}
252
253/// Production configuration manager
254pub struct ProductionConfig {
255    /// Configuration entries
256    entries: RwLock<HashMap<String, ConfigEntry>>,
257    /// Feature flags
258    feature_flags: RwLock<HashMap<String, FeatureFlag>>,
259    /// Available validators
260    validators: HashMap<String, Box<dyn ConfigValidator>>,
261    /// Current environment
262    environment: Environment,
263    /// Configuration file watchers
264    file_watchers: RwLock<HashMap<String, SystemTime>>,
265    /// Hot reload enabled
266    hot_reload_enabled: bool,
267}
268
269impl ProductionConfig {
270    /// Register a configuration key-value pair
271    pub fn register_config(&mut self, key: &str, value: String) -> CoreResult<()> {
272        // Use the set method to register configuration
273        self.set(key, value.as_str())
274    }
275
276    /// Create a new production configuration manager
277    pub fn new() -> Self {
278        let mut validators: HashMap<String, Box<dyn ConfigValidator>> = HashMap::new();
279        validators.insert("positive_int".to_string(), Box::new(PositiveIntValidator));
280        validators.insert("port".to_string(), Box::new(PortValidator));
281        validators.insert("url".to_string(), Box::new(UrlValidator));
282
283        // Determine environment from ENV var
284        let env_str = env::var("SCIRS_ENV").unwrap_or_else(|_| "development".to_string());
285        let environment = Environment::from_str(&env_str).unwrap_or(Environment::Development);
286
287        Self {
288            entries: RwLock::new(HashMap::new()),
289            feature_flags: RwLock::new(HashMap::new()),
290            validators,
291            environment,
292            file_watchers: RwLock::new(HashMap::new()),
293            hot_reload_enabled: true,
294        }
295    }
296
297    /// Load configuration from environment variables
298    pub fn load_from_env(&self) -> CoreResult<()> {
299        let mut entries = self.entries.write().map_err(|_| {
300            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
301        })?;
302
303        // Load all environment variables with SCIRS_ prefix
304        for (key, value) in env::vars() {
305            if key.starts_with("SCIRS_") {
306                let config_key = key
307                    .strip_prefix("SCIRS_")
308                    .expect("Operation failed")
309                    .to_lowercase();
310
311                let config_value = ConfigValue {
312                    value: value.clone(),
313                    source: ConfigSource::Environment,
314                    timestamp: SystemTime::now(),
315                    is_sensitive: self.is_sensitive_key(&config_key),
316                    description: None,
317                };
318
319                let entry = ConfigEntry {
320                    key: config_key.clone(),
321                    value: config_value,
322                    validator: None,
323                    hot_reloadable: false, // Env vars are not hot reloadable
324                    defaultvalue: None,
325                    env_var: Some(key),
326                };
327
328                entries.insert(config_key, entry);
329            }
330        }
331
332        Ok(())
333    }
334
335    /// Load configuration from file
336    pub fn load_from_file<P: AsRef<Path>>(&self, path: P) -> CoreResult<()> {
337        let path = path.as_ref();
338        let content = fs::read_to_string(path).map_err(|e| {
339            CoreError::ConfigError(ErrorContext::new(format!(
340                "Failed to read config file {path}: {e}",
341                path = path.display()
342            )))
343        })?;
344
345        // Track file modification time for hot reloading
346        if let Ok(metadata) = fs::metadata(path) {
347            if let Ok(modified) = metadata.modified() {
348                if let Ok(mut watchers) = self.file_watchers.write() {
349                    watchers.insert(path.to_string_lossy().to_string(), modified);
350                }
351            }
352        }
353
354        // Parse configuration based on file extension
355        match path.extension().and_then(|ext| ext.to_str()) {
356            Some("json") => self.parse_json_config(&content),
357            Some("yaml" | "yml") => self.parse_yaml_config(&content),
358            Some("toml") => self.parse_toml_config(&content),
359            _ => Err(CoreError::ConfigError(ErrorContext::new(format!(
360                "Unsupported config file format: {path}",
361                path = path.display()
362            )))),
363        }
364    }
365
366    /// Parse JSON configuration
367    fn parse_json_config(&self, content: &str) -> CoreResult<()> {
368        // In a real implementation, use serde_json
369        Err(CoreError::ConfigError(ErrorContext::new(
370            "JSON parsing not implemented in this example",
371        )))
372    }
373
374    /// Parse YAML configuration
375    fn parse_yaml_config(&self, content: &str) -> CoreResult<()> {
376        // In a real implementation, use serde_yaml
377        Err(CoreError::ConfigError(ErrorContext::new(
378            "YAML parsing not implemented in this example",
379        )))
380    }
381
382    /// Parse TOML configuration
383    fn parse_toml_config(&self, content: &str) -> CoreResult<()> {
384        // In a real implementation, use toml crate
385        Err(CoreError::ConfigError(ErrorContext::new(
386            "TOML parsing not implemented in this example",
387        )))
388    }
389
390    /// Set a configuration value
391    pub fn set<S: Into<String>>(&self, key: S, value: S) -> CoreResult<()> {
392        let key = key.into();
393        let value = value.into();
394
395        // Validate the value if a validator is specified
396        if let Some(entry) = self.get_entry(&key)? {
397            if let Some(validator_name) = &entry.validator {
398                if let Some(validator) = self.validators.get(validator_name) {
399                    let validation_result = validator.validate(&value);
400                    if !validation_result.is_valid {
401                        return Err(CoreError::ConfigError(ErrorContext::new(format!(
402                            "Validation failed for {key}: {errors}",
403                            errors = validation_result.errors.join(", ")
404                        ))));
405                    }
406                }
407            }
408        }
409
410        let mut entries = self.entries.write().map_err(|_| {
411            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
412        })?;
413
414        let config_value = ConfigValue {
415            value,
416            source: ConfigSource::Override,
417            timestamp: SystemTime::now(),
418            is_sensitive: self.is_sensitive_key(&key),
419            description: None,
420        };
421
422        if let Some(entry) = entries.get_mut(&key) {
423            entry.value = config_value;
424        } else {
425            let entry = ConfigEntry {
426                key: key.clone(),
427                value: config_value,
428                validator: None,
429                hot_reloadable: true,
430                defaultvalue: None,
431                env_var: None,
432            };
433            entries.insert(key, entry);
434        }
435
436        Ok(())
437    }
438
439    /// Get a configuration value
440    pub fn get(&self, key: &str) -> CoreResult<Option<String>> {
441        let entries = self.entries.read().map_err(|_| {
442            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
443        })?;
444
445        Ok(entries.get(key).map(|entry| entry.value.value.clone()))
446    }
447
448    /// Get a configuration entry with metadata
449    pub fn get_entry(&self, key: &str) -> CoreResult<Option<ConfigEntry>> {
450        let entries = self.entries.read().map_err(|_| {
451            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
452        })?;
453
454        Ok(entries.get(key).cloned())
455    }
456
457    /// Get a configuration value with type conversion
458    pub fn get_typed<T>(&self, key: &str) -> CoreResult<Option<T>>
459    where
460        T: std::str::FromStr,
461        T::Err: fmt::Display,
462    {
463        if let Some(value_str) = self.get(key)? {
464            match value_str.parse::<T>() {
465                Ok(value) => Ok(Some(value)),
466                Err(e) => Err(CoreError::ConfigError(ErrorContext::new(format!(
467                    "Failed to parse config value '{value_str}' for key '{key}': {e}"
468                )))),
469            }
470        } else {
471            Ok(None)
472        }
473    }
474
475    /// Get a configuration value with default
476    pub fn get_or_default<T>(&self, key: &str, default: T) -> CoreResult<T>
477    where
478        T: std::str::FromStr + Clone,
479        T::Err: fmt::Display,
480    {
481        match self.get_typed::<T>(key)? {
482            Some(value) => Ok(value),
483            None => Ok(default),
484        }
485    }
486
487    /// Register a configuration entry with validation
488    pub fn register(
489        &mut self,
490        key: String,
491        defaultvalue: Option<String>,
492        validator: Option<String>,
493        reloadable: bool,
494        description: Option<String>,
495    ) -> CoreResult<()> {
496        let mut entries = self.entries.write().map_err(|_| {
497            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
498        })?;
499
500        // Check if already exists
501        if entries.contains_key(&key) {
502            return Ok(()); // Already registered
503        }
504
505        let value = ConfigValue {
506            value: defaultvalue.clone().unwrap_or_default(),
507            source: ConfigSource::Default,
508            timestamp: SystemTime::now(),
509            is_sensitive: self.is_sensitive_key(&key),
510            description: description.clone(),
511        };
512
513        let entry = ConfigEntry {
514            key: key.clone(),
515            value,
516            validator,
517            hot_reloadable: reloadable,
518            defaultvalue,
519            env_var: Some(format!("SCIRS_{}", key.to_uppercase())),
520        };
521
522        entries.insert(key, entry);
523        Ok(())
524    }
525
526    /// Check if a configuration key is sensitive
527    fn is_sensitive_key(&self, key: &str) -> bool {
528        let sensitive_patterns = ["password", "secret", "key", "token", "credential", "auth"];
529        let key_lower = key.to_lowercase();
530        sensitive_patterns
531            .iter()
532            .any(|pattern| key_lower.contains(pattern))
533    }
534
535    /// Get current environment
536    pub const fn environment(&self) -> &Environment {
537        &self.environment
538    }
539
540    /// Set feature flag
541    pub fn set_feature_flag(
542        &mut self,
543        name: String,
544        enabled: bool,
545        rollout_percentage: f64,
546    ) -> CoreResult<()> {
547        let mut flags = self.feature_flags.write().map_err(|_| {
548            CoreError::ConfigError(ErrorContext::new("Failed to acquire feature flags lock"))
549        })?;
550
551        let flag = FeatureFlag {
552            name: name.clone(),
553            enabled,
554            rollout_percentage: rollout_percentage.clamp(0.0, 100.0),
555            environments: vec![self.environment.clone()],
556            user_groups: Vec::new(),
557            description: None,
558            last_modified: SystemTime::now(),
559        };
560
561        flags.insert(name, flag);
562        Ok(())
563    }
564
565    /// Check if feature is enabled
566    pub fn is_feature_enabled(&self, name: &str) -> bool {
567        if let Ok(flags) = self.feature_flags.read() {
568            if let Some(flag) = flags.get(name) {
569                return flag.enabled
570                    && flag.environments.contains(&self.environment)
571                    && self.check_rollout_percentage(flag.rollout_percentage);
572            }
573        }
574        false
575    }
576
577    /// Check rollout percentage (simplified implementation)
578    fn check_rollout_percentage(&self, percentage: f64) -> bool {
579        // In a real implementation, this would use consistent hashing
580        // based on user ID or session ID
581        use std::collections::hash_map::DefaultHasher;
582        use std::hash::{Hash, Hasher};
583
584        let mut hasher = DefaultHasher::new();
585        SystemTime::now().hash(&mut hasher);
586        let hash = hasher.finish();
587
588        (hash % 100) < percentage as u64
589    }
590
591    /// Validate all configuration
592    pub fn validate_all(&self) -> CoreResult<HashMap<String, ValidationResult>> {
593        let entries = self.entries.read().map_err(|_| {
594            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
595        })?;
596
597        let mut results = HashMap::new();
598
599        for (key, entry) in entries.iter() {
600            if let Some(validator_name) = &entry.validator {
601                if let Some(validator) = self.validators.get(validator_name) {
602                    let result = validator.validate(&entry.value.value);
603                    results.insert(key.clone(), result);
604                }
605            }
606        }
607
608        Ok(results)
609    }
610
611    /// Get configuration summary for monitoring
612    pub fn get_summary(&self) -> CoreResult<ConfigSummary> {
613        let entries = self.entries.read().map_err(|_| {
614            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
615        })?;
616
617        let flags = self.feature_flags.read().map_err(|_| {
618            CoreError::ConfigError(ErrorContext::new("Failed to acquire feature flags lock"))
619        })?;
620
621        let total_configs = entries.len();
622        let env_configs = entries
623            .values()
624            .filter(|e| e.value.source == ConfigSource::Environment)
625            .count();
626        let file_configs = entries
627            .values()
628            .filter(|e| matches!(e.value.source, ConfigSource::File(_)))
629            .count();
630        let override_configs = entries
631            .values()
632            .filter(|e| e.value.source == ConfigSource::Override)
633            .count();
634        let sensitive_configs = entries.values().filter(|e| e.value.is_sensitive).count();
635
636        let total_flags = flags.len();
637        let enabled_flags = flags.values().filter(|f| f.enabled).count();
638
639        Ok(ConfigSummary {
640            environment: self.environment.clone(),
641            total_configs,
642            env_configs,
643            file_configs,
644            override_configs,
645            sensitive_configs,
646            total_flags,
647            enabled_flags,
648            hot_reload_enabled: self.hot_reload_enabled,
649        })
650    }
651
652    /// Reload configuration from files (hot reload)
653    pub fn reload(&self) -> CoreResult<Vec<String>> {
654        if !self.hot_reload_enabled {
655            return Err(CoreError::ConfigError(ErrorContext::new(
656                "Hot reload is disabled",
657            )));
658        }
659
660        let mut reloaded_files = Vec::new();
661
662        if let Ok(watchers) = self.file_watchers.read() {
663            for (file_path, last_modified) in watchers.iter() {
664                if let Ok(metadata) = fs::metadata(file_path) {
665                    if let Ok(current_modified) = metadata.modified() {
666                        if current_modified > *last_modified {
667                            // File has been modified, reload it
668                            if let Err(e) = self.load_from_file(file_path) {
669                                eprintln!("Failed to reload config file {file_path}: {e}");
670                            } else {
671                                reloaded_files.push(file_path.clone());
672                            }
673                        }
674                    }
675                }
676            }
677        }
678
679        Ok(reloaded_files)
680    }
681}
682
683impl Default for ProductionConfig {
684    fn default() -> Self {
685        Self::new()
686    }
687}
688
689/// Configuration summary for monitoring
690#[derive(Debug, Clone)]
691pub struct ConfigSummary {
692    pub environment: Environment,
693    pub total_configs: usize,
694    pub env_configs: usize,
695    pub file_configs: usize,
696    pub override_configs: usize,
697    pub sensitive_configs: usize,
698    pub total_flags: usize,
699    pub enabled_flags: usize,
700    pub hot_reload_enabled: bool,
701}
702
703impl fmt::Display for ConfigSummary {
704    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
705        writeln!(f, "Configuration Summary:")?;
706        writeln!(f, "  Environment: {}", self.environment)?;
707        writeln!(f, "  Total configurations: {}", self.total_configs)?;
708        writeln!(f, "    From environment: {}", self.env_configs)?;
709        writeln!(f, "    From files: {}", self.file_configs)?;
710        writeln!(f, "    Runtime overrides: {}", self.override_configs)?;
711        writeln!(f, "    Sensitive configs: {}", self.sensitive_configs)?;
712        writeln!(f, "  Feature flags:")?;
713        writeln!(f, "    Total: {}", self.total_flags)?;
714        writeln!(f, "    Enabled: {}", self.enabled_flags)?;
715        writeln!(
716            f,
717            "  Hot reload: {}",
718            if self.hot_reload_enabled {
719                "enabled"
720            } else {
721                "disabled"
722            }
723        )?;
724        Ok(())
725    }
726}
727
728/// Global configuration instance
729static GLOBAL_CONFIG: std::sync::LazyLock<ProductionConfig> = std::sync::LazyLock::new(|| {
730    let mut config = ProductionConfig::new();
731
732    // Load from environment on startup
733    if let Err(e) = config.load_from_env() {
734        eprintln!("Warning: Failed to load configuration from environment: {e}");
735    }
736
737    // Register common configurations
738    let _ = config.register(
739        "log_level".to_string(),
740        Some("info".to_string()),
741        None,
742        true,
743        Some("Logging level (trace, debug, info, warn, error)".to_string()),
744    );
745
746    let _ = config.register(
747        "max_memory_mb".to_string(),
748        Some("1024".to_string()),
749        Some("positive_int".to_string()),
750        true,
751        Some("Maximum memory usage in megabytes".to_string()),
752    );
753
754    let _ = config.register(
755        "worker_threads".to_string(),
756        Some("4".to_string()),
757        Some("positive_int".to_string()),
758        false,
759        Some("Number of worker threads".to_string()),
760    );
761
762    config
763});
764
765/// Get the global configuration instance
766#[allow(dead_code)]
767pub fn global_config() -> &'static ProductionConfig {
768    &GLOBAL_CONFIG
769}
770
771/// Configuration convenience macros
772/// Get a configuration value with type conversion
773#[macro_export]
774macro_rules! config_get {
775    ($key:expr) => {
776        $crate::config::production::global_config().get($key)
777    };
778    ($key:expr, $type:ty) => {
779        $crate::config::production::global_config().get_typed::<$type>($key)
780    };
781    ($key:expr, $type:ty, $default:expr) => {
782        $crate::config::production::global_config().get_or_default::<$type>($key, $default)
783    };
784}
785
786/// Set a configuration value
787#[macro_export]
788macro_rules! config_set {
789    ($key:expr, $value:expr) => {
790        $crate::config::production::global_config().set($key, $value)
791    };
792}
793
794/// Check if a feature is enabled
795#[macro_export]
796macro_rules! feature_enabled {
797    ($feature:expr) => {
798        $crate::config::production::global_config().is_feature_enabled($feature)
799    };
800}
801
802#[cfg(test)]
803mod tests {
804    use super::*;
805
806    #[test]
807    fn test_environment_parsing() {
808        assert_eq!(
809            Environment::from_str("development"),
810            Ok(Environment::Development)
811        );
812        assert_eq!(Environment::from_str("dev"), Ok(Environment::Development));
813        assert_eq!(
814            Environment::from_str("production"),
815            Ok(Environment::Production)
816        );
817        assert_eq!(
818            Environment::from_str("custom"),
819            Ok(Environment::Custom("custom".to_string()))
820        );
821    }
822
823    #[test]
824    fn test_validators() {
825        let validator = PositiveIntValidator;
826
827        let result = validator.validate("42");
828        assert!(result.is_valid);
829
830        let result = validator.validate("-5");
831        assert!(!result.is_valid);
832
833        let result = validator.validate("not_a_number");
834        assert!(!result.is_valid);
835    }
836
837    #[test]
838    fn test_config_operations() {
839        let config = ProductionConfig::new();
840
841        // Test setting and getting
842        config
843            .set("test_key", "test_value")
844            .expect("Operation failed");
845        assert_eq!(
846            config.get("test_key").expect("Operation failed"),
847            Some("test_value".to_string())
848        );
849
850        // Test typed get
851        config.set("test_number", "42").expect("Operation failed");
852        assert_eq!(
853            config
854                .get_typed::<i32>("test_number")
855                .expect("Operation failed"),
856            Some(42)
857        );
858
859        // Test default
860        assert_eq!(
861            config
862                .get_or_default("missing_key", 100i32)
863                .expect("Operation failed"),
864            100
865        );
866    }
867
868    #[test]
869    fn test_feature_flags() {
870        let mut config = ProductionConfig::new();
871
872        config
873            .set_feature_flag("test_feature".to_string(), true, 100.0)
874            .expect("Operation failed");
875        assert!(config.is_feature_enabled("test_feature"));
876
877        config
878            .set_feature_flag("disabled_feature".to_string(), false, 100.0)
879            .expect("Operation failed");
880        assert!(!config.is_feature_enabled("disabled_feature"));
881    }
882
883    #[test]
884    fn test_sensitive_key_detection() {
885        let config = ProductionConfig::new();
886
887        assert!(config.is_sensitive_key("api_password"));
888        assert!(config.is_sensitive_key("SECRET_KEY"));
889        assert!(config.is_sensitive_key("auth_token"));
890        assert!(!config.is_sensitive_key("log_level"));
891        assert!(!config.is_sensitive_key("max_connections"));
892    }
893
894    #[test]
895    fn test_config_registration() {
896        let mut config = ProductionConfig::new();
897
898        config
899            .register(
900                "test_config".to_string(),
901                Some("defaultvalue".to_string()),
902                Some("positive_int".to_string()),
903                true,
904                Some("Test configuration".to_string()),
905            )
906            .expect("Operation failed");
907
908        let entry = config
909            .get_entry("test_config")
910            .expect("Operation failed")
911            .expect("Operation failed");
912        assert_eq!(entry.defaultvalue, Some("defaultvalue".to_string()));
913        assert_eq!(entry.validator, Some("positive_int".to_string()));
914        assert!(entry.hot_reloadable);
915    }
916}