//! Runtime configuration layer for environment-aware defaults. //! //! This module provides Tier 2 runtime configuration that complements the //! compile-time constants in `common::thresholds`. Values here can be overridden //! via environment variables to support different deployment environments //! (development, staging, production) without recompilation. //! //! # Architecture //! //! - Tier 1 (Compile-time): `common::thresholds` - Performance-critical constants //! - Tier 2 (Runtime): This module - Environment-aware operational parameters //! - Tier 3 (Database): Hot-reload via PostgreSQL NOTIFY/LISTEN //! //! # Environment Variables //! //! ## Database Configuration //! - `DATABASE_QUERY_TIMEOUT_MS` - Query timeout in milliseconds (default: environment-aware) //! - `DATABASE_CONNECTION_TIMEOUT_MS` - Connection timeout in milliseconds //! - `DATABASE_POOL_SIZE` - Connection pool size //! - `DATABASE_MAX_POOL_SIZE` - Maximum pool size //! - `DATABASE_ACQUIRE_TIMEOUT_MS` - Pool acquire timeout in milliseconds //! //! ## Cache Configuration //! - `CACHE_POSITION_TTL_SECS` - Position cache TTL in seconds //! - `CACHE_VAR_TTL_SECS` - VaR calculation cache TTL in seconds //! - `CACHE_COMPLIANCE_TTL_SECS` - Compliance check cache TTL in seconds //! - `CACHE_MARKET_DATA_TTL_SECS` - Market data cache TTL in seconds //! - `CACHE_MODEL_PREDICTION_TTL_SECS` - Model prediction cache TTL in seconds //! //! ## Network Configuration //! - `NETWORK_GRPC_CONNECT_TIMEOUT_SECS` - gRPC connect timeout in seconds //! - `NETWORK_GRPC_REQUEST_TIMEOUT_SECS` - gRPC request timeout in seconds //! - `NETWORK_KEEP_ALIVE_INTERVAL_SECS` - Keep-alive interval in seconds //! - `NETWORK_KEEP_ALIVE_TIMEOUT_SECS` - Keep-alive timeout in seconds //! - `NETWORK_MAX_CONCURRENT_CONNECTIONS` - Maximum concurrent connections //! //! ## Retry Configuration //! - `RETRY_INITIAL_DELAY_MS` - Initial retry delay in milliseconds //! - `RETRY_MAX_DELAY_SECS` - Maximum retry delay in seconds //! - `RETRY_MAX_ATTEMPTS` - Maximum retry attempts //! - `RETRY_BACKOFF_MULTIPLIER` - Backoff multiplier for exponential backoff //! //! ## Safety Configuration //! - `SAFETY_CHECK_TIMEOUT_MS` - Safety check timeout in milliseconds //! - `SAFETY_AUTO_RECOVERY_DELAY_SECS` - Auto-recovery delay in seconds //! - `SAFETY_LOSS_CHECK_INTERVAL_SECS` - Loss check interval in seconds //! - `SAFETY_POSITION_CHECK_INTERVAL_SECS` - Position check interval in seconds //! //! ## ML Configuration //! - `ML_MAX_BATCH_SIZE` - Maximum batch size for ML inference //! - `ML_INFERENCE_TIMEOUT_MS` - ML inference timeout in milliseconds //! - `ML_MODEL_CACHE_CLEANUP_INTERVAL_SECS` - Model cache cleanup interval //! - `ML_DRIFT_CHECK_INTERVAL_SECS` - Drift detection check interval //! //! ## Risk Configuration //! - `RISK_VAR_LOOKBACK_DAYS` - VaR lookback period in trading days //! - `RISK_VAR_CONFIDENCE` - VaR confidence level (0.0-1.0) //! - `RISK_MAX_DRAWDOWN_WARNING_PCT` - Max drawdown warning threshold //! //! # Example //! //! ```rust,no_run //! use config::runtime::{RuntimeConfig, Environment}; //! //! # fn main() -> Result<(), Box> { //! // Auto-detect environment and load from env vars //! let config = RuntimeConfig::from_env()?; //! //! // Or specify environment explicitly //! let prod_config = RuntimeConfig::from_env_with_environment(Environment::Production)?; //! //! // Or use defaults for specific environment //! let dev_config = RuntimeConfig::with_defaults(Environment::Development); //! //! println!("Database query timeout: {:?}", config.database.query_timeout); //! println!("Position cache TTL: {:?}", config.cache.position_ttl); //! # Ok(()) //! # } //! ``` use crate::error::{ConfigError, ConfigResult}; use serde::{Deserialize, Serialize}; use std::time::Duration; /// Deployment environment enumeration. /// /// Determines default values for runtime configuration parameters. /// /// Different environments have different performance vs safety trade-offs. #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] pub enum Environment { /// Development environment - Relaxed timeouts, verbose logging Development, /// Staging environment - Production-like settings with some debug features Staging, /// Production environment - Optimized for performance and reliability Production, } impl Environment { /// Detects the environment from the ENVIRONMENT environment variable. /// /// Falls back to Development if not set or invalid. pub fn detect() -> Self { match std::env::var("ENVIRONMENT") .unwrap_or_else(|_| "development".to_owned()) .to_lowercase() .as_str() { "production" | "prod" => Environment::Production, "staging" | "stage" => Environment::Staging, _ => Environment::Development, } } /// Returns true if this is a production environment. pub const fn is_production(&self) -> bool { matches!(self, Environment::Production) } /// Returns true if this is a development environment. pub const fn is_development(&self) -> bool { matches!(self, Environment::Development) } } /// Database runtime configuration. /// /// Controls database connection pooling, timeouts, and query execution limits. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct DatabaseRuntimeConfig { /// Query timeout for standard operations pub query_timeout: Duration, /// Connection establishment timeout pub connection_timeout: Duration, /// Pool acquire timeout pub acquire_timeout: Duration, /// Default pool size pub pool_size: u32, /// Maximum pool size pub max_pool_size: u32, /// Connection lifetime pub connection_lifetime: Duration, /// Idle timeout pub idle_timeout: Duration, } impl DatabaseRuntimeConfig { /// Creates configuration with environment-aware defaults. pub const fn with_defaults(env: Environment) -> Self { match env { Environment::Development => Self { query_timeout: Duration::from_millis(5000), // More relaxed for debugging connection_timeout: Duration::from_millis(500), acquire_timeout: Duration::from_millis(200), pool_size: 10, max_pool_size: 50, connection_lifetime: Duration::from_secs(1800), // 30 minutes idle_timeout: Duration::from_secs(600), // 10 minutes }, Environment::Staging => Self { query_timeout: Duration::from_millis(2000), connection_timeout: Duration::from_millis(200), acquire_timeout: Duration::from_millis(100), pool_size: 15, max_pool_size: 75, connection_lifetime: Duration::from_secs(3600), // 1 hour idle_timeout: Duration::from_secs(300), // 5 minutes }, Environment::Production => Self { query_timeout: Duration::from_millis(1000), // Tight timeout for HFT connection_timeout: Duration::from_millis(100), acquire_timeout: Duration::from_millis(50), pool_size: 20, max_pool_size: 100, connection_lifetime: Duration::from_secs(3600), // 1 hour idle_timeout: Duration::from_secs(300), // 5 minutes }, } } /// Loads from environment variables with fallback to defaults. /// /// # Errors /// Returns error if the operation fails pub fn from_env(env: Environment) -> ConfigResult { let defaults = Self::with_defaults(env); Ok(Self { query_timeout: parse_env_duration_ms( "DATABASE_QUERY_TIMEOUT_MS", defaults.query_timeout, )?, connection_timeout: parse_env_duration_ms( "DATABASE_CONNECTION_TIMEOUT_MS", defaults.connection_timeout, )?, acquire_timeout: parse_env_duration_ms( "DATABASE_ACQUIRE_TIMEOUT_MS", defaults.acquire_timeout, )?, pool_size: parse_env_u32("DATABASE_POOL_SIZE", defaults.pool_size)?, max_pool_size: parse_env_u32("DATABASE_MAX_POOL_SIZE", defaults.max_pool_size)?, connection_lifetime: parse_env_duration_secs( "DATABASE_CONNECTION_LIFETIME_SECS", defaults.connection_lifetime, )?, idle_timeout: parse_env_duration_secs( "DATABASE_IDLE_TIMEOUT_SECS", defaults.idle_timeout, )?, }) } /// Validates the configuration. /// /// # Errors /// Returns error if the operation fails pub fn validate(&self) -> ConfigResult<()> { if self.query_timeout.as_millis() == 0 { return Err(ConfigError::Invalid( "Query timeout must be positive".into(), )); } if self.pool_size == 0 { return Err(ConfigError::Invalid("Pool size must be positive".into())); } if self.pool_size > self.max_pool_size { return Err(ConfigError::Invalid( "Pool size cannot exceed max pool size".into(), )); } Ok(()) } } /// Cache TTL runtime configuration. /// /// Controls time-to-live values for various cache types. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CacheRuntimeConfig { /// Position cache TTL pub position_ttl: Duration, /// VaR calculation cache TTL pub var_ttl: Duration, /// Compliance check cache TTL pub compliance_ttl: Duration, /// Market data cache TTL pub market_data_ttl: Duration, /// Model prediction cache TTL pub model_prediction_ttl: Duration, } impl CacheRuntimeConfig { /// Creates configuration with environment-aware defaults. pub const fn with_defaults(env: Environment) -> Self { match env { Environment::Development => Self { position_ttl: Duration::from_secs(120), // Longer TTL for debugging var_ttl: Duration::from_secs(7200), // 2 hours compliance_ttl: Duration::from_secs(172_800), // 48 hours market_data_ttl: Duration::from_secs(600), // 10 minutes model_prediction_ttl: Duration::from_secs(120), // 2 minutes }, Environment::Staging => Self { position_ttl: Duration::from_secs(90), var_ttl: Duration::from_secs(5400), // 1.5 hours compliance_ttl: Duration::from_secs(129_600), // 36 hours market_data_ttl: Duration::from_secs(450), // 7.5 minutes model_prediction_ttl: Duration::from_secs(90), }, Environment::Production => Self { position_ttl: Duration::from_secs(60), // 1 minute for HFT var_ttl: Duration::from_secs(3600), // 1 hour compliance_ttl: Duration::from_secs(86400), // 24 hours market_data_ttl: Duration::from_secs(300), // 5 minutes model_prediction_ttl: Duration::from_secs(60), // 1 minute }, } } /// Loads from environment variables with fallback to defaults. /// /// # Errors /// Returns error if the operation fails pub fn from_env(env: Environment) -> ConfigResult { let defaults = Self::with_defaults(env); Ok(Self { position_ttl: parse_env_duration_secs( "CACHE_POSITION_TTL_SECS", defaults.position_ttl, )?, var_ttl: parse_env_duration_secs("CACHE_VAR_TTL_SECS", defaults.var_ttl)?, compliance_ttl: parse_env_duration_secs( "CACHE_COMPLIANCE_TTL_SECS", defaults.compliance_ttl, )?, market_data_ttl: parse_env_duration_secs( "CACHE_MARKET_DATA_TTL_SECS", defaults.market_data_ttl, )?, model_prediction_ttl: parse_env_duration_secs( "CACHE_MODEL_PREDICTION_TTL_SECS", defaults.model_prediction_ttl, )?, }) } /// Validates the configuration. /// /// # Errors /// Returns error if the operation fails pub fn validate(&self) -> ConfigResult<()> { if self.position_ttl.as_secs() == 0 { return Err(ConfigError::Invalid("Position TTL must be positive".into())); } if self.var_ttl.as_secs() == 0 { return Err(ConfigError::Invalid("VaR TTL must be positive".into())); } Ok(()) } } /// Network timeout runtime configuration. /// /// Controls gRPC and network-related timeouts. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct TimeoutConfig { /// gRPC connect timeout pub grpc_connect_timeout: Duration, /// gRPC request timeout pub grpc_request_timeout: Duration, /// Keep-alive interval pub keep_alive_interval: Duration, /// Keep-alive timeout pub keep_alive_timeout: Duration, /// Maximum concurrent connections pub max_concurrent_connections: u32, } impl TimeoutConfig { /// Creates configuration with environment-aware defaults. pub const fn with_defaults(env: Environment) -> Self { match env { Environment::Development => Self { grpc_connect_timeout: Duration::from_secs(10), grpc_request_timeout: Duration::from_secs(30), keep_alive_interval: Duration::from_secs(60), keep_alive_timeout: Duration::from_secs(10), max_concurrent_connections: 50, }, Environment::Staging => Self { grpc_connect_timeout: Duration::from_secs(7), grpc_request_timeout: Duration::from_secs(20), keep_alive_interval: Duration::from_secs(45), keep_alive_timeout: Duration::from_secs(7), max_concurrent_connections: 75, }, Environment::Production => Self { grpc_connect_timeout: Duration::from_secs(5), grpc_request_timeout: Duration::from_secs(10), keep_alive_interval: Duration::from_secs(30), keep_alive_timeout: Duration::from_secs(5), max_concurrent_connections: 100, }, } } /// Loads from environment variables with fallback to defaults. /// /// # Errors /// Returns error if the operation fails pub fn from_env(env: Environment) -> ConfigResult { let defaults = Self::with_defaults(env); Ok(Self { grpc_connect_timeout: parse_env_duration_secs( "NETWORK_GRPC_CONNECT_TIMEOUT_SECS", defaults.grpc_connect_timeout, )?, grpc_request_timeout: parse_env_duration_secs( "NETWORK_GRPC_REQUEST_TIMEOUT_SECS", defaults.grpc_request_timeout, )?, keep_alive_interval: parse_env_duration_secs( "NETWORK_KEEP_ALIVE_INTERVAL_SECS", defaults.keep_alive_interval, )?, keep_alive_timeout: parse_env_duration_secs( "NETWORK_KEEP_ALIVE_TIMEOUT_SECS", defaults.keep_alive_timeout, )?, max_concurrent_connections: parse_env_u32( "NETWORK_MAX_CONCURRENT_CONNECTIONS", defaults.max_concurrent_connections, )?, }) } /// Validates the configuration. /// /// # Errors /// Returns error if the operation fails pub fn validate(&self) -> ConfigResult<()> { if self.grpc_connect_timeout.as_secs() == 0 { return Err(ConfigError::Invalid( "gRPC connect timeout must be positive".into(), )); } if self.max_concurrent_connections == 0 { return Err(ConfigError::Invalid( "Max concurrent connections must be positive".into(), )); } Ok(()) } } /// Operational limits runtime configuration. /// /// Controls retry behavior, safety checks, ML parameters, and risk calculations. #[derive(Debug, Clone, Serialize, Deserialize)] pub struct LimitsConfig { // Retry configuration /// Initial retry delay pub retry_initial_delay: Duration, /// Maximum retry delay pub retry_max_delay: Duration, /// Maximum retry attempts pub retry_max_attempts: u32, /// Backoff multiplier pub retry_backoff_multiplier: f32, // Safety configuration /// Safety check timeout pub safety_check_timeout: Duration, /// Auto-recovery delay pub safety_auto_recovery_delay: Duration, /// Loss check interval pub safety_loss_check_interval: Duration, /// Position check interval pub safety_position_check_interval: Duration, // ML configuration /// Maximum batch size for ML inference pub ml_max_batch_size: usize, /// ML inference timeout pub ml_inference_timeout: Duration, /// Model cache cleanup interval pub ml_cache_cleanup_interval: Duration, /// Drift detection check interval pub ml_drift_check_interval: Duration, // Risk configuration /// VaR lookback period in trading days pub risk_var_lookback_days: usize, /// VaR confidence level pub risk_var_confidence: f64, /// Max drawdown warning threshold (percentage) pub risk_max_drawdown_warning_pct: u8, } impl LimitsConfig { /// Creates configuration with environment-aware defaults. pub const fn with_defaults(env: Environment) -> Self { match env { Environment::Development => Self { // Retry retry_initial_delay: Duration::from_millis(200), retry_max_delay: Duration::from_secs(60), retry_max_attempts: 5, retry_backoff_multiplier: 2.0, // Safety safety_check_timeout: Duration::from_millis(50), safety_auto_recovery_delay: Duration::from_secs(60), safety_loss_check_interval: Duration::from_secs(30), safety_position_check_interval: Duration::from_secs(15), // ML ml_max_batch_size: 1024, ml_inference_timeout: Duration::from_millis(200), ml_cache_cleanup_interval: Duration::from_secs(7200), // 2 hours ml_drift_check_interval: Duration::from_secs(600), // 10 minutes // Risk risk_var_lookback_days: 252, risk_var_confidence: 0.95, risk_max_drawdown_warning_pct: 20, }, Environment::Staging => Self { // Retry retry_initial_delay: Duration::from_millis(150), retry_max_delay: Duration::from_secs(45), retry_max_attempts: 4, retry_backoff_multiplier: 1.75, // Safety safety_check_timeout: Duration::from_millis(25), safety_auto_recovery_delay: Duration::from_secs(900), // 15 minutes safety_loss_check_interval: Duration::from_secs(15), safety_position_check_interval: Duration::from_secs(7), // ML ml_max_batch_size: 4096, ml_inference_timeout: Duration::from_millis(150), ml_cache_cleanup_interval: Duration::from_secs(5400), // 1.5 hours ml_drift_check_interval: Duration::from_secs(450), // 7.5 minutes // Risk risk_var_lookback_days: 252, risk_var_confidence: 0.95, risk_max_drawdown_warning_pct: 17, }, Environment::Production => Self { // Retry retry_initial_delay: Duration::from_millis(100), retry_max_delay: Duration::from_secs(30), retry_max_attempts: 3, retry_backoff_multiplier: 1.5, // Safety safety_check_timeout: Duration::from_millis(5), safety_auto_recovery_delay: Duration::from_secs(1800), // 30 minutes safety_loss_check_interval: Duration::from_secs(5), safety_position_check_interval: Duration::from_secs(2), // ML ml_max_batch_size: 8192, ml_inference_timeout: Duration::from_millis(100), ml_cache_cleanup_interval: Duration::from_secs(3600), // 1 hour ml_drift_check_interval: Duration::from_secs(300), // 5 minutes // Risk risk_var_lookback_days: 252, risk_var_confidence: 0.95, risk_max_drawdown_warning_pct: 15, }, } } /// Loads from environment variables with fallback to defaults. /// /// # Errors /// Returns error if the operation fails pub fn from_env(env: Environment) -> ConfigResult { let defaults = Self::with_defaults(env); Ok(Self { // Retry retry_initial_delay: parse_env_duration_ms( "RETRY_INITIAL_DELAY_MS", defaults.retry_initial_delay, )?, retry_max_delay: parse_env_duration_secs( "RETRY_MAX_DELAY_SECS", defaults.retry_max_delay, )?, retry_max_attempts: parse_env_u32("RETRY_MAX_ATTEMPTS", defaults.retry_max_attempts)?, retry_backoff_multiplier: parse_env_f32( "RETRY_BACKOFF_MULTIPLIER", defaults.retry_backoff_multiplier, )?, // Safety safety_check_timeout: parse_env_duration_ms( "SAFETY_CHECK_TIMEOUT_MS", defaults.safety_check_timeout, )?, safety_auto_recovery_delay: parse_env_duration_secs( "SAFETY_AUTO_RECOVERY_DELAY_SECS", defaults.safety_auto_recovery_delay, )?, safety_loss_check_interval: parse_env_duration_secs( "SAFETY_LOSS_CHECK_INTERVAL_SECS", defaults.safety_loss_check_interval, )?, safety_position_check_interval: parse_env_duration_secs( "SAFETY_POSITION_CHECK_INTERVAL_SECS", defaults.safety_position_check_interval, )?, // ML ml_max_batch_size: parse_env_usize("ML_MAX_BATCH_SIZE", defaults.ml_max_batch_size)?, ml_inference_timeout: parse_env_duration_ms( "ML_INFERENCE_TIMEOUT_MS", defaults.ml_inference_timeout, )?, ml_cache_cleanup_interval: parse_env_duration_secs( "ML_MODEL_CACHE_CLEANUP_INTERVAL_SECS", defaults.ml_cache_cleanup_interval, )?, ml_drift_check_interval: parse_env_duration_secs( "ML_DRIFT_CHECK_INTERVAL_SECS", defaults.ml_drift_check_interval, )?, // Risk risk_var_lookback_days: parse_env_usize( "RISK_VAR_LOOKBACK_DAYS", defaults.risk_var_lookback_days, )?, risk_var_confidence: parse_env_f64( "RISK_VAR_CONFIDENCE", defaults.risk_var_confidence, )?, risk_max_drawdown_warning_pct: parse_env_u8( "RISK_MAX_DRAWDOWN_WARNING_PCT", defaults.risk_max_drawdown_warning_pct, )?, }) } /// Validates the configuration. /// /// # Errors /// Returns error if the operation fails pub fn validate(&self) -> ConfigResult<()> { if self.retry_max_attempts == 0 { return Err(ConfigError::Invalid( "Retry max attempts must be positive".into(), )); } if self.retry_backoff_multiplier <= 1.0 { return Err(ConfigError::Invalid( "Backoff multiplier must be > 1.0".into(), )); } if self.ml_max_batch_size == 0 { return Err(ConfigError::Invalid( "ML max batch size must be positive".into(), )); } if self.risk_var_confidence < 0.0_f64 || self.risk_var_confidence > 1.0_f64 { return Err(ConfigError::Invalid( "VaR confidence must be between 0.0 and 1.0".into(), )); } if self.risk_var_lookback_days == 0 { return Err(ConfigError::Invalid( "VaR lookback days must be positive".into(), )); } Ok(()) } } /// Complete runtime configuration for the Foxhunt trading system. /// /// Aggregates all runtime configuration categories with environment-aware defaults /// and environment variable overrides. #[derive(Debug, Clone, Serialize, Deserialize)] #[allow(clippy::module_name_repetitions)] pub struct RuntimeConfig { /// Detected or specified environment pub environment: Environment, /// Database configuration pub database: DatabaseRuntimeConfig, /// Cache configuration pub cache: CacheRuntimeConfig, /// Timeout configuration pub timeouts: TimeoutConfig, /// Limits and operational parameters pub limits: LimitsConfig, } impl RuntimeConfig { /// Creates runtime configuration by auto-detecting environment and loading from env vars. /// /// # Errors /// /// Returns ConfigError if environment variables contain invalid values or /// if validation fails. pub fn from_env() -> ConfigResult { let environment = Environment::detect(); Self::from_env_with_environment(environment) } /// Creates runtime configuration with specified environment and loads from env vars. /// /// # Arguments /// /// * `environment` - The deployment environment to use for defaults /// /// # Errors /// /// Returns ConfigError if environment variables contain invalid values or /// if validation fails. pub fn from_env_with_environment(environment: Environment) -> ConfigResult { let config = Self { environment, database: DatabaseRuntimeConfig::from_env(environment)?, cache: CacheRuntimeConfig::from_env(environment)?, timeouts: TimeoutConfig::from_env(environment)?, limits: LimitsConfig::from_env(environment)?, }; config.validate()?; Ok(config) } /// Creates runtime configuration with environment-specific defaults. /// /// Does not read from environment variables. Useful for testing or /// when you want pure default values. /// /// # Arguments /// /// * `environment` - The deployment environment to use for defaults pub const fn with_defaults(environment: Environment) -> Self { Self { environment, database: DatabaseRuntimeConfig::with_defaults(environment), cache: CacheRuntimeConfig::with_defaults(environment), timeouts: TimeoutConfig::with_defaults(environment), limits: LimitsConfig::with_defaults(environment), } } /// Validates the entire runtime configuration. /// /// # Errors /// /// Returns ConfigError if any configuration values are invalid. pub fn validate(&self) -> ConfigResult<()> { self.database.validate()?; self.cache.validate()?; self.timeouts.validate()?; self.limits.validate()?; Ok(()) } } // Helper functions for parsing environment variables fn parse_env_duration_ms(key: &str, default: Duration) -> ConfigResult { match std::env::var(key) { Ok(val) => { let ms = val.parse::().map_err(|e| { ConfigError::Invalid(format!("Invalid duration for {}: {}", key, e)) })?; Ok(Duration::from_millis(ms)) } Err(_) => Ok(default), } } fn parse_env_duration_secs(key: &str, default: Duration) -> ConfigResult { match std::env::var(key) { Ok(val) => { let secs = val.parse::().map_err(|e| { ConfigError::Invalid(format!("Invalid duration for {}: {}", key, e)) })?; Ok(Duration::from_secs(secs)) } Err(_) => Ok(default), } } fn parse_env_u32(key: &str, default: u32) -> ConfigResult { match std::env::var(key) { Ok(val) => val .parse::() .map_err(|e| ConfigError::Invalid(format!("Invalid u32 for {}: {}", key, e))), Err(_) => Ok(default), } } fn parse_env_u8(key: &str, default: u8) -> ConfigResult { match std::env::var(key) { Ok(val) => val .parse::() .map_err(|e| ConfigError::Invalid(format!("Invalid u8 for {}: {}", key, e))), Err(_) => Ok(default), } } fn parse_env_usize(key: &str, default: usize) -> ConfigResult { match std::env::var(key) { Ok(val) => val .parse::() .map_err(|e| ConfigError::Invalid(format!("Invalid usize for {}: {}", key, e))), Err(_) => Ok(default), } } fn parse_env_f32(key: &str, default: f32) -> ConfigResult { match std::env::var(key) { Ok(val) => val .parse::() .map_err(|e| ConfigError::Invalid(format!("Invalid f32 for {}: {}", key, e))), Err(_) => Ok(default), } } fn parse_env_f64(key: &str, default: f64) -> ConfigResult { match std::env::var(key) { Ok(val) => val .parse::() .map_err(|e| ConfigError::Invalid(format!("Invalid f64 for {}: {}", key, e))), Err(_) => Ok(default), } } #[cfg(test)] mod tests { use super::*; #[test] fn test_environment_detection() { // Should default to Development let env = Environment::detect(); assert!(matches!( env, Environment::Development | Environment::Production | Environment::Staging )); } #[test] fn test_environment_is_production() { assert!(Environment::Production.is_production()); assert!(!Environment::Development.is_production()); assert!(!Environment::Staging.is_production()); } #[test] fn test_environment_is_development() { assert!(Environment::Development.is_development()); assert!(!Environment::Production.is_development()); assert!(!Environment::Staging.is_development()); } #[test] fn test_runtime_config_with_defaults() { let config = RuntimeConfig::with_defaults(Environment::Production); assert_eq!(config.environment, Environment::Production); assert!(config.database.query_timeout.as_millis() > 0); assert!(config.cache.position_ttl.as_secs() > 0); } #[test] fn test_runtime_config_validation() { let config = RuntimeConfig::with_defaults(Environment::Development); assert!(config.validate().is_ok()); } #[test] fn test_database_config_defaults() { let dev_config = DatabaseRuntimeConfig::with_defaults(Environment::Development); let prod_config = DatabaseRuntimeConfig::with_defaults(Environment::Production); // Production should have tighter timeouts assert!(prod_config.query_timeout < dev_config.query_timeout); assert!(prod_config.connection_timeout < dev_config.connection_timeout); } #[test] fn test_cache_config_defaults() { let dev_config = CacheRuntimeConfig::with_defaults(Environment::Development); let prod_config = CacheRuntimeConfig::with_defaults(Environment::Production); // Production should have shorter TTLs for HFT assert!(prod_config.position_ttl < dev_config.position_ttl); assert!(prod_config.var_ttl < dev_config.var_ttl); } #[test] fn test_timeout_config_defaults() { let dev_config = TimeoutConfig::with_defaults(Environment::Development); let prod_config = TimeoutConfig::with_defaults(Environment::Production); // Production should have tighter timeouts assert!(prod_config.grpc_request_timeout < dev_config.grpc_request_timeout); assert!(prod_config.grpc_connect_timeout < dev_config.grpc_connect_timeout); } #[test] fn test_limits_config_defaults() { let dev_config = LimitsConfig::with_defaults(Environment::Development); let prod_config = LimitsConfig::with_defaults(Environment::Production); // Production should have more aggressive settings assert!(prod_config.safety_check_timeout < dev_config.safety_check_timeout); assert!(prod_config.ml_inference_timeout < dev_config.ml_inference_timeout); } #[test] fn test_database_config_validation() { let mut config = DatabaseRuntimeConfig::with_defaults(Environment::Production); assert!(config.validate().is_ok()); config.query_timeout = Duration::from_millis(0); assert!(config.validate().is_err()); config = DatabaseRuntimeConfig::with_defaults(Environment::Production); config.pool_size = 0; assert!(config.validate().is_err()); config = DatabaseRuntimeConfig::with_defaults(Environment::Production); config.pool_size = 200; config.max_pool_size = 100; assert!(config.validate().is_err()); } #[test] fn test_cache_config_validation() { let mut config = CacheRuntimeConfig::with_defaults(Environment::Production); assert!(config.validate().is_ok()); config.position_ttl = Duration::from_secs(0); assert!(config.validate().is_err()); } #[test] fn test_limits_config_validation() { let mut config = LimitsConfig::with_defaults(Environment::Production); assert!(config.validate().is_ok()); config.retry_max_attempts = 0; assert!(config.validate().is_err()); config = LimitsConfig::with_defaults(Environment::Production); config.retry_backoff_multiplier = 0.5; assert!(config.validate().is_err()); config = LimitsConfig::with_defaults(Environment::Production); config.risk_var_confidence = 1.5; assert!(config.validate().is_err()); } #[test] fn test_staging_environment_defaults() { let config = RuntimeConfig::with_defaults(Environment::Staging); // Staging should be between dev and prod let dev_config = RuntimeConfig::with_defaults(Environment::Development); let prod_config = RuntimeConfig::with_defaults(Environment::Production); assert!(config.database.query_timeout > prod_config.database.query_timeout); assert!(config.database.query_timeout < dev_config.database.query_timeout); } }