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.strip_prefix("SCIRS_").unwrap().to_lowercase();
307
308                let config_value = ConfigValue {
309                    value: value.clone(),
310                    source: ConfigSource::Environment,
311                    timestamp: SystemTime::now(),
312                    is_sensitive: self.is_sensitive_key(&config_key),
313                    description: None,
314                };
315
316                let entry = ConfigEntry {
317                    key: config_key.clone(),
318                    value: config_value,
319                    validator: None,
320                    hot_reloadable: false, // Env vars are not hot reloadable
321                    defaultvalue: None,
322                    env_var: Some(key),
323                };
324
325                entries.insert(config_key, entry);
326            }
327        }
328
329        Ok(())
330    }
331
332    /// Load configuration from file
333    pub fn load_from_file<P: AsRef<Path>>(&self, path: P) -> CoreResult<()> {
334        let path = path.as_ref();
335        let content = fs::read_to_string(path).map_err(|e| {
336            CoreError::ConfigError(ErrorContext::new(format!(
337                "Failed to read config file {path}: {e}",
338                path = path.display()
339            )))
340        })?;
341
342        // Track file modification time for hot reloading
343        if let Ok(metadata) = fs::metadata(path) {
344            if let Ok(modified) = metadata.modified() {
345                if let Ok(mut watchers) = self.file_watchers.write() {
346                    watchers.insert(path.to_string_lossy().to_string(), modified);
347                }
348            }
349        }
350
351        // Parse configuration based on file extension
352        match path.extension().and_then(|ext| ext.to_str()) {
353            Some("json") => self.parse_json_config(&content),
354            Some("yaml" | "yml") => self.parse_yaml_config(&content),
355            Some("toml") => self.parse_toml_config(&content),
356            _ => Err(CoreError::ConfigError(ErrorContext::new(format!(
357                "Unsupported config file format: {path}",
358                path = path.display()
359            )))),
360        }
361    }
362
363    /// Parse JSON configuration
364    fn parse_json_config(&self, content: &str) -> CoreResult<()> {
365        // In a real implementation, use serde_json
366        Err(CoreError::ConfigError(ErrorContext::new(
367            "JSON parsing not implemented in this example",
368        )))
369    }
370
371    /// Parse YAML configuration
372    fn parse_yaml_config(&self, content: &str) -> CoreResult<()> {
373        // In a real implementation, use serde_yaml
374        Err(CoreError::ConfigError(ErrorContext::new(
375            "YAML parsing not implemented in this example",
376        )))
377    }
378
379    /// Parse TOML configuration
380    fn parse_toml_config(&self, content: &str) -> CoreResult<()> {
381        // In a real implementation, use toml crate
382        Err(CoreError::ConfigError(ErrorContext::new(
383            "TOML parsing not implemented in this example",
384        )))
385    }
386
387    /// Set a configuration value
388    pub fn set<S: Into<String>>(&self, key: S, value: S) -> CoreResult<()> {
389        let key = key.into();
390        let value = value.into();
391
392        // Validate the value if a validator is specified
393        if let Some(entry) = self.get_entry(&key)? {
394            if let Some(validator_name) = &entry.validator {
395                if let Some(validator) = self.validators.get(validator_name) {
396                    let validation_result = validator.validate(&value);
397                    if !validation_result.is_valid {
398                        return Err(CoreError::ConfigError(ErrorContext::new(format!(
399                            "Validation failed for {key}: {errors}",
400                            errors = validation_result.errors.join(", ")
401                        ))));
402                    }
403                }
404            }
405        }
406
407        let mut entries = self.entries.write().map_err(|_| {
408            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
409        })?;
410
411        let config_value = ConfigValue {
412            value,
413            source: ConfigSource::Override,
414            timestamp: SystemTime::now(),
415            is_sensitive: self.is_sensitive_key(&key),
416            description: None,
417        };
418
419        if let Some(entry) = entries.get_mut(&key) {
420            entry.value = config_value;
421        } else {
422            let entry = ConfigEntry {
423                key: key.clone(),
424                value: config_value,
425                validator: None,
426                hot_reloadable: true,
427                defaultvalue: None,
428                env_var: None,
429            };
430            entries.insert(key, entry);
431        }
432
433        Ok(())
434    }
435
436    /// Get a configuration value
437    pub fn get(&self, key: &str) -> CoreResult<Option<String>> {
438        let entries = self.entries.read().map_err(|_| {
439            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
440        })?;
441
442        Ok(entries.get(key).map(|entry| entry.value.value.clone()))
443    }
444
445    /// Get a configuration entry with metadata
446    pub fn get_entry(&self, key: &str) -> CoreResult<Option<ConfigEntry>> {
447        let entries = self.entries.read().map_err(|_| {
448            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
449        })?;
450
451        Ok(entries.get(key).cloned())
452    }
453
454    /// Get a configuration value with type conversion
455    pub fn get_typed<T>(&self, key: &str) -> CoreResult<Option<T>>
456    where
457        T: std::str::FromStr,
458        T::Err: fmt::Display,
459    {
460        if let Some(value_str) = self.get(key)? {
461            match value_str.parse::<T>() {
462                Ok(value) => Ok(Some(value)),
463                Err(e) => Err(CoreError::ConfigError(ErrorContext::new(format!(
464                    "Failed to parse config value '{value_str}' for key '{key}': {e}"
465                )))),
466            }
467        } else {
468            Ok(None)
469        }
470    }
471
472    /// Get a configuration value with default
473    pub fn get_or_default<T>(&self, key: &str, default: T) -> CoreResult<T>
474    where
475        T: std::str::FromStr + Clone,
476        T::Err: fmt::Display,
477    {
478        match self.get_typed::<T>(key)? {
479            Some(value) => Ok(value),
480            None => Ok(default),
481        }
482    }
483
484    /// Register a configuration entry with validation
485    pub fn register(
486        &mut self,
487        key: String,
488        defaultvalue: Option<String>,
489        validator: Option<String>,
490        reloadable: bool,
491        description: Option<String>,
492    ) -> CoreResult<()> {
493        let mut entries = self.entries.write().map_err(|_| {
494            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
495        })?;
496
497        // Check if already exists
498        if entries.contains_key(&key) {
499            return Ok(()); // Already registered
500        }
501
502        let value = ConfigValue {
503            value: defaultvalue.clone().unwrap_or_default(),
504            source: ConfigSource::Default,
505            timestamp: SystemTime::now(),
506            is_sensitive: self.is_sensitive_key(&key),
507            description: description.clone(),
508        };
509
510        let entry = ConfigEntry {
511            key: key.clone(),
512            value,
513            validator,
514            hot_reloadable: reloadable,
515            defaultvalue,
516            env_var: Some(format!("SCIRS_{}", key.to_uppercase())),
517        };
518
519        entries.insert(key, entry);
520        Ok(())
521    }
522
523    /// Check if a configuration key is sensitive
524    fn is_sensitive_key(&self, key: &str) -> bool {
525        let sensitive_patterns = ["password", "secret", "key", "token", "credential", "auth"];
526        let key_lower = key.to_lowercase();
527        sensitive_patterns
528            .iter()
529            .any(|pattern| key_lower.contains(pattern))
530    }
531
532    /// Get current environment
533    pub const fn environment(&self) -> &Environment {
534        &self.environment
535    }
536
537    /// Set feature flag
538    pub fn set_feature_flag(
539        &mut self,
540        name: String,
541        enabled: bool,
542        rollout_percentage: f64,
543    ) -> CoreResult<()> {
544        let mut flags = self.feature_flags.write().map_err(|_| {
545            CoreError::ConfigError(ErrorContext::new("Failed to acquire feature flags lock"))
546        })?;
547
548        let flag = FeatureFlag {
549            name: name.clone(),
550            enabled,
551            rollout_percentage: rollout_percentage.clamp(0.0, 100.0),
552            environments: vec![self.environment.clone()],
553            user_groups: Vec::new(),
554            description: None,
555            last_modified: SystemTime::now(),
556        };
557
558        flags.insert(name, flag);
559        Ok(())
560    }
561
562    /// Check if feature is enabled
563    pub fn is_feature_enabled(&self, name: &str) -> bool {
564        if let Ok(flags) = self.feature_flags.read() {
565            if let Some(flag) = flags.get(name) {
566                return flag.enabled
567                    && flag.environments.contains(&self.environment)
568                    && self.check_rollout_percentage(flag.rollout_percentage);
569            }
570        }
571        false
572    }
573
574    /// Check rollout percentage (simplified implementation)
575    fn check_rollout_percentage(&self, percentage: f64) -> bool {
576        // In a real implementation, this would use consistent hashing
577        // based on user ID or session ID
578        use std::collections::hash_map::DefaultHasher;
579        use std::hash::{Hash, Hasher};
580
581        let mut hasher = DefaultHasher::new();
582        SystemTime::now().hash(&mut hasher);
583        let hash = hasher.finish();
584
585        (hash % 100) < percentage as u64
586    }
587
588    /// Validate all configuration
589    pub fn validate_all(&self) -> CoreResult<HashMap<String, ValidationResult>> {
590        let entries = self.entries.read().map_err(|_| {
591            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
592        })?;
593
594        let mut results = HashMap::new();
595
596        for (key, entry) in entries.iter() {
597            if let Some(validator_name) = &entry.validator {
598                if let Some(validator) = self.validators.get(validator_name) {
599                    let result = validator.validate(&entry.value.value);
600                    results.insert(key.clone(), result);
601                }
602            }
603        }
604
605        Ok(results)
606    }
607
608    /// Get configuration summary for monitoring
609    pub fn get_summary(&self) -> CoreResult<ConfigSummary> {
610        let entries = self.entries.read().map_err(|_| {
611            CoreError::ConfigError(ErrorContext::new("Failed to acquire config lock"))
612        })?;
613
614        let flags = self.feature_flags.read().map_err(|_| {
615            CoreError::ConfigError(ErrorContext::new("Failed to acquire feature flags lock"))
616        })?;
617
618        let total_configs = entries.len();
619        let env_configs = entries
620            .values()
621            .filter(|e| e.value.source == ConfigSource::Environment)
622            .count();
623        let file_configs = entries
624            .values()
625            .filter(|e| matches!(e.value.source, ConfigSource::File(_)))
626            .count();
627        let override_configs = entries
628            .values()
629            .filter(|e| e.value.source == ConfigSource::Override)
630            .count();
631        let sensitive_configs = entries.values().filter(|e| e.value.is_sensitive).count();
632
633        let total_flags = flags.len();
634        let enabled_flags = flags.values().filter(|f| f.enabled).count();
635
636        Ok(ConfigSummary {
637            environment: self.environment.clone(),
638            total_configs,
639            env_configs,
640            file_configs,
641            override_configs,
642            sensitive_configs,
643            total_flags,
644            enabled_flags,
645            hot_reload_enabled: self.hot_reload_enabled,
646        })
647    }
648
649    /// Reload configuration from files (hot reload)
650    pub fn reload(&self) -> CoreResult<Vec<String>> {
651        if !self.hot_reload_enabled {
652            return Err(CoreError::ConfigError(ErrorContext::new(
653                "Hot reload is disabled",
654            )));
655        }
656
657        let mut reloaded_files = Vec::new();
658
659        if let Ok(watchers) = self.file_watchers.read() {
660            for (file_path, last_modified) in watchers.iter() {
661                if let Ok(metadata) = fs::metadata(file_path) {
662                    if let Ok(current_modified) = metadata.modified() {
663                        if current_modified > *last_modified {
664                            // File has been modified, reload it
665                            if let Err(e) = self.load_from_file(file_path) {
666                                eprintln!("Failed to reload config file {file_path}: {e}");
667                            } else {
668                                reloaded_files.push(file_path.clone());
669                            }
670                        }
671                    }
672                }
673            }
674        }
675
676        Ok(reloaded_files)
677    }
678}
679
680impl Default for ProductionConfig {
681    fn default() -> Self {
682        Self::new()
683    }
684}
685
686/// Configuration summary for monitoring
687#[derive(Debug, Clone)]
688pub struct ConfigSummary {
689    pub environment: Environment,
690    pub total_configs: usize,
691    pub env_configs: usize,
692    pub file_configs: usize,
693    pub override_configs: usize,
694    pub sensitive_configs: usize,
695    pub total_flags: usize,
696    pub enabled_flags: usize,
697    pub hot_reload_enabled: bool,
698}
699
700impl fmt::Display for ConfigSummary {
701    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
702        writeln!(f, "Configuration Summary:")?;
703        writeln!(f, "  Environment: {}", self.environment)?;
704        writeln!(f, "  Total configurations: {}", self.total_configs)?;
705        writeln!(f, "    From environment: {}", self.env_configs)?;
706        writeln!(f, "    From files: {}", self.file_configs)?;
707        writeln!(f, "    Runtime overrides: {}", self.override_configs)?;
708        writeln!(f, "    Sensitive configs: {}", self.sensitive_configs)?;
709        writeln!(f, "  Feature flags:")?;
710        writeln!(f, "    Total: {}", self.total_flags)?;
711        writeln!(f, "    Enabled: {}", self.enabled_flags)?;
712        writeln!(
713            f,
714            "  Hot reload: {}",
715            if self.hot_reload_enabled {
716                "enabled"
717            } else {
718                "disabled"
719            }
720        )?;
721        Ok(())
722    }
723}
724
725/// Global configuration instance
726static GLOBAL_CONFIG: std::sync::LazyLock<ProductionConfig> = std::sync::LazyLock::new(|| {
727    let mut config = ProductionConfig::new();
728
729    // Load from environment on startup
730    if let Err(e) = config.load_from_env() {
731        eprintln!("Warning: Failed to load configuration from environment: {e}");
732    }
733
734    // Register common configurations
735    let _ = config.register(
736        "log_level".to_string(),
737        Some("info".to_string()),
738        None,
739        true,
740        Some("Logging level (trace, debug, info, warn, error)".to_string()),
741    );
742
743    let _ = config.register(
744        "max_memory_mb".to_string(),
745        Some("1024".to_string()),
746        Some("positive_int".to_string()),
747        true,
748        Some("Maximum memory usage in megabytes".to_string()),
749    );
750
751    let _ = config.register(
752        "worker_threads".to_string(),
753        Some("4".to_string()),
754        Some("positive_int".to_string()),
755        false,
756        Some("Number of worker threads".to_string()),
757    );
758
759    config
760});
761
762/// Get the global configuration instance
763#[allow(dead_code)]
764pub fn global_config() -> &'static ProductionConfig {
765    &GLOBAL_CONFIG
766}
767
768/// Configuration convenience macros
769/// Get a configuration value with type conversion
770#[macro_export]
771macro_rules! config_get {
772    ($key:expr) => {
773        $crate::config::production::global_config().get($key)
774    };
775    ($key:expr, $type:ty) => {
776        $crate::config::production::global_config().get_typed::<$type>($key)
777    };
778    ($key:expr, $type:ty, $default:expr) => {
779        $crate::config::production::global_config().get_or_default::<$type>($key, $default)
780    };
781}
782
783/// Set a configuration value
784#[macro_export]
785macro_rules! config_set {
786    ($key:expr, $value:expr) => {
787        $crate::config::production::global_config().set($key, $value)
788    };
789}
790
791/// Check if a feature is enabled
792#[macro_export]
793macro_rules! feature_enabled {
794    ($feature:expr) => {
795        $crate::config::production::global_config().is_feature_enabled($feature)
796    };
797}
798
799#[cfg(test)]
800mod tests {
801    use super::*;
802
803    #[test]
804    fn test_environment_parsing() {
805        assert_eq!(
806            Environment::from_str("development"),
807            Ok(Environment::Development)
808        );
809        assert_eq!(Environment::from_str("dev"), Ok(Environment::Development));
810        assert_eq!(
811            Environment::from_str("production"),
812            Ok(Environment::Production)
813        );
814        assert_eq!(
815            Environment::from_str("custom"),
816            Ok(Environment::Custom("custom".to_string()))
817        );
818    }
819
820    #[test]
821    fn test_validators() {
822        let validator = PositiveIntValidator;
823
824        let result = validator.validate("42");
825        assert!(result.is_valid);
826
827        let result = validator.validate("-5");
828        assert!(!result.is_valid);
829
830        let result = validator.validate("not_a_number");
831        assert!(!result.is_valid);
832    }
833
834    #[test]
835    fn test_config_operations() {
836        let config = ProductionConfig::new();
837
838        // Test setting and getting
839        config.set("test_key", "test_value").unwrap();
840        assert_eq!(
841            config.get("test_key").unwrap(),
842            Some("test_value".to_string())
843        );
844
845        // Test typed get
846        config.set("test_number", "42").unwrap();
847        assert_eq!(config.get_typed::<i32>("test_number").unwrap(), Some(42));
848
849        // Test default
850        assert_eq!(config.get_or_default("missing_key", 100i32).unwrap(), 100);
851    }
852
853    #[test]
854    fn test_feature_flags() {
855        let mut config = ProductionConfig::new();
856
857        config
858            .set_feature_flag("test_feature".to_string(), true, 100.0)
859            .unwrap();
860        assert!(config.is_feature_enabled("test_feature"));
861
862        config
863            .set_feature_flag("disabled_feature".to_string(), false, 100.0)
864            .unwrap();
865        assert!(!config.is_feature_enabled("disabled_feature"));
866    }
867
868    #[test]
869    fn test_sensitive_key_detection() {
870        let config = ProductionConfig::new();
871
872        assert!(config.is_sensitive_key("api_password"));
873        assert!(config.is_sensitive_key("SECRET_KEY"));
874        assert!(config.is_sensitive_key("auth_token"));
875        assert!(!config.is_sensitive_key("log_level"));
876        assert!(!config.is_sensitive_key("max_connections"));
877    }
878
879    #[test]
880    fn test_config_registration() {
881        let mut config = ProductionConfig::new();
882
883        config
884            .register(
885                "test_config".to_string(),
886                Some("defaultvalue".to_string()),
887                Some("positive_int".to_string()),
888                true,
889                Some("Test configuration".to_string()),
890            )
891            .unwrap();
892
893        let entry = config.get_entry("test_config").unwrap().unwrap();
894        assert_eq!(entry.defaultvalue, Some("defaultvalue".to_string()));
895        assert_eq!(entry.validator, Some("positive_int".to_string()));
896        assert!(entry.hot_reloadable);
897    }
898}