Files
rustytorch/crates/production/rtx-config/src/environment.rs
T
2026-03-04 00:08:42 +00:00

442 lines
15 KiB
Rust

//! 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<Self, Self::Err> {
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<String, ConfigValue>,
/// Inherit from base environment
pub inherit_from: Option<Environment>,
/// Environment description
pub description: Option<String>,
/// Environment-specific feature flags
pub features: HashMap<String, bool>,
}
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<String>, value: ConfigValue) -> Self {
self.values.insert(key.into(), value);
self
}
/// Add a feature flag.
pub fn with_feature(mut self, feature: impl Into<String>, enabled: bool) -> Self {
self.features.insert(feature.into(), enabled);
self
}
/// Set description.
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
}
/// Environment configuration manager.
#[derive(Debug)]
pub struct EnvironmentManager {
/// Current environment
current: Environment,
/// Environment configurations
configs: HashMap<Environment, EnvironmentConfig>,
}
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<HashMap<String, ConfigValue>> {
self.resolve_environment_config(&self.current)
}
/// Get resolved configuration for a specific environment.
pub fn resolve_environment_config(
&self,
env: &Environment,
) -> ConfigResult<HashMap<String, ConfigValue>> {
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<String, ConfigValue>,
visited: &mut std::collections::HashSet<Environment>,
) -> 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::<Environment>().unwrap(),
Environment::Development
);
assert_eq!(
"dev".parse::<Environment>().unwrap(),
Environment::Development
);
assert_eq!(
"production".parse::<Environment>().unwrap(),
Environment::Production
);
assert_eq!(
"prod".parse::<Environment>().unwrap(),
Environment::Production
);
assert_eq!(
"custom".parse::<Environment>().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());
}
}