Spaces:
Sleeping
Sleeping
| 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<Self, String> { | |
| 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() | |
| } | |
| } | |
| mod tests { | |
| fn test_config_defaults() { | |
| let port_default = "8000"; | |
| assert_eq!(port_default.parse::<u16>().unwrap(), 8000); | |
| } | |
| } | |