use super::types::*; use super::Config; use std::env; impl Config { /// Load configuration directly from environment variables. /// /// This follows a fail-fast approach: missing critical environment /// variables will bubble up an error, preventing the server from starting /// with an invalid state. pub fn from_env() -> Result { Ok(Config { server: ServerConfig { host: env::var("SERVER_HOST").unwrap_or_else(|_| "0.0.0.0".to_string()), port: env::var("SERVER_PORT") .unwrap_or_else(|_| "8000".to_string()) .parse() .map_err(|e| format!("Invalid SERVER_PORT: {}", e))?, workers: env::var("SERVER_WORKERS") .unwrap_or_else(|_| "4".to_string()) .parse() .map_err(|e| format!("Invalid SERVER_WORKERS: {}", e))?, request_timeout_secs: env::var("REQUEST_TIMEOUT_SECS") .unwrap_or_else(|_| "30".to_string()) .parse() .map_err(|e| format!("Invalid REQUEST_TIMEOUT_SECS: {}", e))?, max_concurrent_requests: env::var("MAX_CONCURRENT_REQUESTS") .unwrap_or_else(|_| "500".to_string()) .parse() .map_err(|e| format!("Invalid MAX_CONCURRENT_REQUESTS: {}", e))?, }, database: DatabaseConfig { url: env::var("DATABASE_URL").map_err(|_| "DATABASE_URL not set".to_string())?, max_connections: env::var("DB_MAX_CONNECTIONS") .unwrap_or_else(|_| "20".to_string()) .parse() .map_err(|e| format!("Invalid DB_MAX_CONNECTIONS: {}", e))?, min_connections: env::var("DB_MIN_CONNECTIONS") .unwrap_or_else(|_| "5".to_string()) .parse() .map_err(|e| format!("Invalid DB_MIN_CONNECTIONS: {}", e))?, connection_timeout_secs: env::var("DB_CONNECTION_TIMEOUT_SECS") .unwrap_or_else(|_| "10".to_string()) .parse() .map_err(|e| format!("Invalid DB_CONNECTION_TIMEOUT_SECS: {}", e))?, idle_timeout_secs: env::var("DB_IDLE_TIMEOUT_SECS") .unwrap_or_else(|_| "600".to_string()) .parse() .map_err(|e| format!("Invalid DB_IDLE_TIMEOUT_SECS: {}", e))?, }, services: ServicesConfig { razorpay_gateway: RazorpayConfig { base_url: env::var("RAZORPAY_BASE_URL") .unwrap_or_else(|_| "https://api.razorpay.com/v1".to_string()), timeout_secs: env::var("RAZORPAY_TIMEOUT_SECS") .unwrap_or_else(|_| "10".to_string()) .parse() .map_err(|e| format!("Invalid RAZORPAY_TIMEOUT_SECS: {}", e))?, circuit_breaker_threshold: env::var("RAZORPAY_CB_THRESHOLD") .unwrap_or_else(|_| "5".to_string()) .parse() .map_err(|e| format!("Invalid RAZORPAY_CB_THRESHOLD: {}", e))?, circuit_breaker_recovery_secs: env::var("RAZORPAY_CB_RECOVERY_SECS") .unwrap_or_else(|_| "60".to_string()) .parse() .map_err(|e| format!("Invalid RAZORPAY_CB_RECOVERY_SECS: {}", e))?, }, email_service: EmailServiceConfig { endpoint: env::var("EMAIL_ENDPOINT") .unwrap_or_else(|_| "https://api.sendgrid.com/v3/mail/send".to_string()), timeout_secs: env::var("EMAIL_TIMEOUT_SECS") .unwrap_or_else(|_| "15".to_string()) .parse() .map_err(|e| format!("Invalid EMAIL_TIMEOUT_SECS: {}", e))?, retries: env::var("EMAIL_RETRIES") .unwrap_or_else(|_| "3".to_string()) .parse() .map_err(|e| format!("Invalid EMAIL_RETRIES: {}", e))?, }, }, security: SecurityConfig { jwt_secret: env::var("JWT_SECRET").map_err(|_| "JWT_SECRET not set".to_string())?, jwt_expiry_hours: env::var("JWT_EXPIRY_HOURS") .unwrap_or_else(|_| "24".to_string()) .parse() .map_err(|e| format!("Invalid JWT_EXPIRY_HOURS: {}", e))?, password_min_length: 8, require_uppercase: true, require_digits: true, max_login_attempts: 5, lockout_duration_secs: 900, // 15 minutes }, observability: ObservabilityConfig { log_level: env::var("RUST_LOG").unwrap_or_else(|_| "info".to_string()), enable_metrics: env::var("ENABLE_METRICS") .unwrap_or_else(|_| "true".to_string()) .parse() .unwrap_or(true), metrics_port: env::var("METRICS_PORT") .unwrap_or_else(|_| "9090".to_string()) .parse() .unwrap_or(9090), enable_request_logging: true, slow_query_threshold_ms: 1000, slow_request_threshold_ms: 1000, }, }) } /// Generate an example .env string with default safe values. /// Used for CLI initializations or local development setup. pub fn example_env() -> String { r#" # ===== SERVER CONFIGURATION ===== SERVER_HOST=0.0.0.0 SERVER_PORT=8000 SERVER_WORKERS=4 REQUEST_TIMEOUT_SECS=30 MAX_CONCURRENT_REQUESTS=500 # ===== DATABASE CONFIGURATION ===== DATABASE_URL=postgres://user:password@localhost:5432/rtix DB_MAX_CONNECTIONS=20 DB_MIN_CONNECTIONS=5 DB_CONNECTION_TIMEOUT_SECS=10 DB_IDLE_TIMEOUT_SECS=600 # ===== EXTERNAL SERVICES ===== RAZORPAY_BASE_URL=https://api.razorpay.com/v1 RAZORPAY_TIMEOUT_SECS=10 RAZORPAY_CB_THRESHOLD=5 RAZORPAY_CB_RECOVERY_SECS=60 EMAIL_ENDPOINT=https://api.sendgrid.com/v3/mail/send EMAIL_TIMEOUT_SECS=15 EMAIL_RETRIES=3 # ===== SECURITY ===== JWT_SECRET=your-super-secret-jwt-key-change-in-production JWT_EXPIRY_HOURS=24 # ===== OBSERVABILITY ===== RUST_LOG=rtix=info,tower_http=debug,sqlx=warn ENABLE_METRICS=true METRICS_PORT=9090 ENVIRONMENT=development "# .trim() .to_string() } } #[cfg(test)] mod tests { #[test] fn test_config_defaults() { let port_default = "8000"; assert_eq!(port_default.parse::().unwrap(), 8000); } }