RTIX / src /core /config /env.rs
github-actions
deploy: clean backend production release
7856e60
Raw
History Blame Contribute Delete
6.82 kB
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()
}
}
#[cfg(test)]
mod tests {
#[test]
fn test_config_defaults() {
let port_default = "8000";
assert_eq!(port_default.parse::<u16>().unwrap(), 8000);
}
}