use anyhow::Result; #[derive(Clone, Debug)] pub struct Config { pub database_url: String, pub jwt_secret: String, pub jwt_expiry_secs: u64, pub cache_max_capacity: usize, pub cache_idle_timeout_secs: u64, pub cache_sweep_interval_secs: u64, /// Comma-separated allowed CORS origins, or "*" for permissive. Empty = no CORS headers. pub cors_origins: Vec, /// Base URL of the MinIO/S3 endpoint (no trailing slash). pub cdn_endpoint: String, /// Bucket name used for CDN object storage. pub cdn_bucket: String, /// Dev-only: when true, all auth/permission checks are bypassed and every /// request is treated as a super-admin. NEVER enable in production. pub disable_auth: bool, } impl Config { pub fn from_env() -> Result { Ok(Self { database_url: std::env::var("DATABASE_URL")?, jwt_secret: std::env::var("JWT_SECRET")?, jwt_expiry_secs: std::env::var("JWT_EXPIRY_SECS") .unwrap_or_else(|_| "3600".into()) .parse()?, cache_max_capacity: std::env::var("CACHE_MAX_CAPACITY") .unwrap_or_else(|_| "10000".into()) .parse()?, cache_idle_timeout_secs: std::env::var("CACHE_IDLE_TIMEOUT_SECS") .unwrap_or_else(|_| "300".into()) .parse()?, cache_sweep_interval_secs: std::env::var("CACHE_SWEEP_INTERVAL_SECS") .unwrap_or_else(|_| "60".into()) .parse()?, cors_origins: std::env::var("CORS_ORIGINS") .unwrap_or_default() .split(',') .map(|s| s.trim().to_string()) .filter(|s| !s.is_empty()) .collect(), cdn_endpoint: std::env::var("CDN_ENDPOINT") .unwrap_or_else(|_| "http://localhost:9000".into()), cdn_bucket: std::env::var("CDN_BUCKET").unwrap_or_else(|_| "mercury".into()), disable_auth: std::env::var("DISABLE_AUTH") .map(|v| matches!(v.trim().to_lowercase().as_str(), "1" | "true")) .unwrap_or(false), }) } } #[cfg(test)] mod tests { use super::*; use std::sync::Mutex; // Serializes tests that mutate the process-global DISABLE_AUTH env var, // since cargo runs tests in parallel by default. static DISABLE_AUTH_ENV_LOCK: Mutex<()> = Mutex::new(()); #[test] fn test_config_defaults() { std::env::set_var("DATABASE_URL", "postgres://test"); std::env::set_var("JWT_SECRET", "secret"); let cfg = Config::from_env().unwrap(); assert_eq!(cfg.jwt_expiry_secs, 3600); assert_eq!(cfg.cache_max_capacity, 10_000); assert_eq!(cfg.cache_idle_timeout_secs, 300); assert_eq!(cfg.cache_sweep_interval_secs, 60); assert!(cfg.cors_origins.is_empty()); } #[test] fn test_config_disable_auth_default_false() { let _guard = DISABLE_AUTH_ENV_LOCK.lock().unwrap(); std::env::set_var("DATABASE_URL", "postgres://test"); std::env::set_var("JWT_SECRET", "secret"); std::env::remove_var("DISABLE_AUTH"); let cfg = Config::from_env().unwrap(); assert!(!cfg.disable_auth); } #[test] fn test_config_disable_auth_true() { let _guard = DISABLE_AUTH_ENV_LOCK.lock().unwrap(); std::env::set_var("DATABASE_URL", "postgres://test"); std::env::set_var("JWT_SECRET", "secret"); std::env::set_var("DISABLE_AUTH", "true"); let cfg = Config::from_env().unwrap(); assert!(cfg.disable_auth); std::env::remove_var("DISABLE_AUTH"); } #[test] fn test_config_disable_auth_numeric_true() { let _guard = DISABLE_AUTH_ENV_LOCK.lock().unwrap(); std::env::set_var("DATABASE_URL", "postgres://test"); std::env::set_var("JWT_SECRET", "secret"); std::env::set_var("DISABLE_AUTH", "1"); let cfg = Config::from_env().unwrap(); assert!(cfg.disable_auth); std::env::remove_var("DISABLE_AUTH"); } }