//! Environment-specific configuration management. use crate::{ConfigError, ConfigResult, ConfigValue}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use tracing::{debug, info}; /// Deployment environment types. #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] pub enum Environment { Development, Testing, Staging, Production, Custom(String), } impl Default for Environment { fn default() -> Self { // Try to detect from environment variable if let Ok(env_str) = std::env::var("RTX_ENV") { env_str.parse().unwrap_or(Environment::Development) } else { Environment::Development } } } impl std::str::FromStr for Environment { type Err = ConfigError; fn from_str(s: &str) -> Result { match s.to_lowercase().as_str() { "development" | "dev" => Ok(Environment::Development), "testing" | "test" => Ok(Environment::Testing), "staging" | "stage" => Ok(Environment::Staging), "production" | "prod" => Ok(Environment::Production), other => Ok(Environment::Custom(other.to_string())), } } } impl std::fmt::Display for Environment { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Environment::Development => write!(f, "development"), Environment::Testing => write!(f, "testing"), Environment::Staging => write!(f, "staging"), Environment::Production => write!(f, "production"), Environment::Custom(name) => write!(f, "{}", name), } } } /// Environment-specific configuration. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct EnvironmentConfig { /// Environment name pub environment: Environment, /// Environment-specific values pub values: HashMap, /// Inherit from base environment pub inherit_from: Option, /// Environment description pub description: Option, /// Environment-specific feature flags pub features: HashMap, } impl EnvironmentConfig { /// Create a new environment configuration. pub fn new(environment: Environment) -> Self { Self { environment, values: HashMap::new(), inherit_from: None, description: None, features: HashMap::new(), } } /// Set inheritance from another environment. pub fn inherit_from(mut self, parent: Environment) -> Self { self.inherit_from = Some(parent); self } /// Add a configuration value. pub fn with_value(mut self, key: impl Into, value: ConfigValue) -> Self { self.values.insert(key.into(), value); self } /// Add a feature flag. pub fn with_feature(mut self, feature: impl Into, enabled: bool) -> Self { self.features.insert(feature.into(), enabled); self } /// Set description. pub fn with_description(mut self, description: impl Into) -> Self { self.description = Some(description.into()); self } } /// Environment configuration manager. #[derive(Debug)] pub struct EnvironmentManager { /// Current environment current: Environment, /// Environment configurations configs: HashMap, } impl EnvironmentManager { /// Create a new environment manager. pub fn new(current: Environment) -> Self { let mut manager = Self { current: current.clone(), configs: HashMap::new(), }; // Add default configurations manager.add_default_configs(); info!("Environment manager initialized for: {}", current); manager } /// Add an environment configuration. pub fn add_config(&mut self, config: EnvironmentConfig) { let env = config.environment.clone(); self.configs.insert(env.clone(), config); debug!("Added configuration for environment: {}", env); } /// Get the current environment. pub fn current(&self) -> &Environment { &self.current } /// Switch to a different environment. pub fn switch_to(&mut self, environment: Environment) -> ConfigResult<()> { if !self.configs.contains_key(&environment) { return Err(ConfigError::EnvironmentError { name: environment.to_string(), details: "Environment configuration not found".to_string(), }); } self.current = environment.clone(); info!("Switched to environment: {}", environment); Ok(()) } /// Get resolved configuration for current environment. pub fn get_resolved_config(&self) -> ConfigResult> { self.resolve_environment_config(&self.current) } /// Get resolved configuration for a specific environment. pub fn resolve_environment_config( &self, env: &Environment, ) -> ConfigResult> { let mut resolved_values = HashMap::new(); // Resolve configuration with inheritance self.resolve_with_inheritance( env, &mut resolved_values, &mut std::collections::HashSet::new(), )?; Ok(resolved_values) } /// Check if a feature is enabled in the current environment. pub fn is_feature_enabled(&self, feature: &str) -> bool { if let Ok(config) = self.get_current_config() { config.features.get(feature).copied().unwrap_or(false) } else { false } } /// Get all available environments. pub fn available_environments(&self) -> Vec<&Environment> { self.configs.keys().collect() } /// Get current environment configuration. fn get_current_config(&self) -> ConfigResult<&EnvironmentConfig> { self.configs .get(&self.current) .ok_or_else(|| ConfigError::EnvironmentError { name: self.current.to_string(), details: "Current environment configuration not found".to_string(), }) } /// Resolve configuration with inheritance chain. fn resolve_with_inheritance( &self, env: &Environment, resolved: &mut HashMap, visited: &mut std::collections::HashSet, ) -> ConfigResult<()> { // Check for circular inheritance if visited.contains(env) { return Err(ConfigError::EnvironmentError { name: env.to_string(), details: "Circular inheritance detected".to_string(), }); } visited.insert(env.clone()); let config = self .configs .get(env) .ok_or_else(|| ConfigError::EnvironmentError { name: env.to_string(), details: "Environment configuration not found".to_string(), })?; // First resolve parent configuration if let Some(ref parent) = config.inherit_from { self.resolve_with_inheritance(parent, resolved, visited)?; } // Then apply current environment values (overriding parent values) for (key, value) in &config.values { resolved.insert(key.clone(), value.clone()); } visited.remove(env); Ok(()) } /// Add default environment configurations. fn add_default_configs(&mut self) { // Development environment let dev_config = EnvironmentConfig::new(Environment::Development) .with_description("Development environment with debug settings") .with_value("log.level", ConfigValue::String("debug".to_string())) .with_value("debug", ConfigValue::Boolean(true)) .with_value("metrics.enabled", ConfigValue::Boolean(false)) .with_feature("hot_reload", true) .with_feature("debug_api", true); // Testing environment let test_config = EnvironmentConfig::new(Environment::Testing) .with_description("Testing environment for automated tests") .inherit_from(Environment::Development) .with_value("log.level", ConfigValue::String("info".to_string())) .with_value( "database.url", ConfigValue::String("sqlite::memory:".to_string()), ) .with_feature("hot_reload", false) .with_feature("test_mode", true); // Staging environment let staging_config = EnvironmentConfig::new(Environment::Staging) .with_description("Staging environment for pre-production testing") .with_value("log.level", ConfigValue::String("info".to_string())) .with_value("debug", ConfigValue::Boolean(false)) .with_value("metrics.enabled", ConfigValue::Boolean(true)) .with_feature("hot_reload", false) .with_feature("performance_monitoring", true); // Production environment let prod_config = EnvironmentConfig::new(Environment::Production) .with_description("Production environment") .with_value("log.level", ConfigValue::String("warn".to_string())) .with_value("debug", ConfigValue::Boolean(false)) .with_value("metrics.enabled", ConfigValue::Boolean(true)) .with_value("security.strict_mode", ConfigValue::Boolean(true)) .with_feature("hot_reload", false) .with_feature("performance_monitoring", true) .with_feature("security_logging", true); self.add_config(dev_config); self.add_config(test_config); self.add_config(staging_config); self.add_config(prod_config); } } impl Default for EnvironmentManager { fn default() -> Self { Self::new(Environment::default()) } } #[cfg(test)] mod tests { use super::*; #[test] fn test_environment_parsing() { assert_eq!( "development".parse::().unwrap(), Environment::Development ); assert_eq!( "dev".parse::().unwrap(), Environment::Development ); assert_eq!( "production".parse::().unwrap(), Environment::Production ); assert_eq!( "prod".parse::().unwrap(), Environment::Production ); assert_eq!( "custom".parse::().unwrap(), Environment::Custom("custom".to_string()) ); } #[test] fn test_environment_display() { assert_eq!(Environment::Development.to_string(), "development"); assert_eq!(Environment::Production.to_string(), "production"); assert_eq!( Environment::Custom("custom".to_string()).to_string(), "custom" ); } #[test] fn test_environment_config_creation() { let config = EnvironmentConfig::new(Environment::Development) .with_value("test.key", ConfigValue::String("test_value".to_string())) .with_feature("test_feature", true) .with_description("Test environment"); assert_eq!(config.environment, Environment::Development); assert_eq!( config.values.get("test.key").unwrap(), &ConfigValue::String("test_value".to_string()) ); assert_eq!(config.features.get("test_feature").unwrap(), &true); assert_eq!(config.description.as_ref().unwrap(), "Test environment"); } #[test] fn test_environment_manager_basic() { let manager = EnvironmentManager::new(Environment::Development); assert_eq!(manager.current(), &Environment::Development); assert!(manager.available_environments().len() >= 4); // dev, test, staging, prod } #[test] fn test_environment_inheritance() { let mut manager = EnvironmentManager::new(Environment::Development); // Create a custom environment that inherits from development let custom_config = EnvironmentConfig::new(Environment::Custom("custom".to_string())) .inherit_from(Environment::Development) .with_value( "custom.setting", ConfigValue::String("custom_value".to_string()), ); manager.add_config(custom_config); let resolved = manager .resolve_environment_config(&Environment::Custom("custom".to_string())) .unwrap(); // Should have inherited values from development assert!(resolved.contains_key("debug")); assert!(resolved.contains_key("log.level")); // Should have custom values assert_eq!( resolved.get("custom.setting").unwrap(), &ConfigValue::String("custom_value".to_string()) ); } #[test] fn test_feature_flags() { let manager = EnvironmentManager::new(Environment::Development); assert!(manager.is_feature_enabled("hot_reload")); // Development should have this enabled assert!(manager.is_feature_enabled("debug_api")); // Development should have this enabled let mut prod_manager = EnvironmentManager::new(Environment::Production); prod_manager.switch_to(Environment::Production).unwrap(); assert!(!prod_manager.is_feature_enabled("hot_reload")); // Production should not have this assert!(prod_manager.is_feature_enabled("security_logging")); // Production should have this } #[test] fn test_circular_inheritance_detection() { let mut manager = EnvironmentManager::new(Environment::Development); // Create circular inheritance: A -> B -> A let env_a = Environment::Custom("a".to_string()); let env_b = Environment::Custom("b".to_string()); let config_a = EnvironmentConfig::new(env_a.clone()).inherit_from(env_b.clone()); let config_b = EnvironmentConfig::new(env_b.clone()).inherit_from(env_a.clone()); manager.add_config(config_a); manager.add_config(config_b); let result = manager.resolve_environment_config(&env_a); assert!(result.is_err()); if let Err(ConfigError::EnvironmentError { details, .. }) = result { assert!(details.contains("Circular inheritance")); } else { panic!("Expected circular inheritance error"); } } #[test] fn test_environment_switching() { let mut manager = EnvironmentManager::new(Environment::Development); assert_eq!(manager.current(), &Environment::Development); manager.switch_to(Environment::Production).unwrap(); assert_eq!(manager.current(), &Environment::Production); // Test switching to non-existent environment let result = manager.switch_to(Environment::Custom("nonexistent".to_string())); assert!(result.is_err()); } }