use std::env; use std::path::PathBuf; #[derive(Debug, Clone)] pub struct Config { pub database_url: String, #[allow(dead_code)] pub openweathermap_api_key: Option, pub admin_token: Option, pub server_port: u16, pub server_host: String, pub log_level: String, } impl Config { pub fn from_env() -> Self { let database_url = Self::get_database_url(); let openweathermap_api_key = env::var("OPENWEATHERMAP_API_KEY").ok(); let admin_token = env::var("ADMIN_TOKEN").ok(); let server_port = env::var("PORT") .unwrap_or_else(|_| "4000".to_string()) .parse() .unwrap_or(4000); let server_host = env::var("HOST").unwrap_or_else(|_| "0.0.0.0".to_string()); let log_level = env::var("LOG_LEVEL").unwrap_or_else(|_| "info".to_string()); Self { database_url, openweathermap_api_key, admin_token, server_port, server_host, log_level, } } fn get_database_url() -> String { // First, check if DATABASE_URL is explicitly set if let Ok(url) = env::var("DATABASE_URL") { return url; } // If not set, construct the path using XDG_DATA_HOME or fallback let data_dir = if let Ok(xdg_data_home) = env::var("XDG_DATA_HOME") { PathBuf::from(xdg_data_home) } else { // Fallback to ~/.local/share let home = env::var("HOME") .unwrap_or_else(|_| env::var("USERPROFILE").unwrap_or_else(|_| "/tmp".to_string())); PathBuf::from(home).join(".local").join("share") }; let db_path = data_dir.join("silmataivas").join("silmataivas.db"); // Ensure the directory exists if let Some(parent) = db_path.parent() { let _ = std::fs::create_dir_all(parent); } format!("sqlite://{}", db_path.display()) } #[allow(dead_code)] pub fn validate(&self) -> Result<(), String> { if self.openweathermap_api_key.is_none() { return Err("OPENWEATHERMAP_API_KEY environment variable is required".to_string()); } Ok(()) } #[allow(dead_code)] pub fn validate_optional(&self) -> Result<(), String> { // For optional validation (e.g., during development) Ok(()) } } #[cfg(test)] mod tests { use super::*; use std::env; use std::sync::Once; static INIT: Once = Once::new(); fn setup() { INIT.call_once(|| { // Clear all environment variables that might interfere with tests unsafe { env::remove_var("DATABASE_URL"); env::remove_var("XDG_DATA_HOME"); env::remove_var("PORT"); env::remove_var("HOST"); env::remove_var("LOG_LEVEL"); } }); // Clear environment variables before each test unsafe { env::remove_var("DATABASE_URL"); env::remove_var("XDG_DATA_HOME"); env::remove_var("PORT"); env::remove_var("HOST"); env::remove_var("LOG_LEVEL"); } } #[test] fn test_database_url_fallback() { setup(); let home = env::var("HOME").unwrap_or_else(|_| "/tmp".to_string()); let expected_path = format!("sqlite://{home}/.local/share/silmataivas/silmataivas.db"); let config = Config::from_env(); assert_eq!(config.database_url, expected_path); } #[test] fn test_database_url_xdg_data_home() { setup(); unsafe { env::set_var("XDG_DATA_HOME", "/custom/data/path"); } let expected_path = "sqlite:///custom/data/path/silmataivas/silmataivas.db"; let config = Config::from_env(); assert_eq!(config.database_url, expected_path); } #[test] fn test_database_url_explicit() { setup(); unsafe { env::set_var("DATABASE_URL", "sqlite:///explicit/path.db"); } let config = Config::from_env(); assert_eq!(config.database_url, "sqlite:///explicit/path.db"); } #[test] fn test_server_config() { setup(); unsafe { env::set_var("PORT", "8080"); env::set_var("HOST", "127.0.0.1"); env::set_var("LOG_LEVEL", "debug"); } let config = Config::from_env(); assert_eq!(config.server_port, 8080); assert_eq!(config.server_host, "127.0.0.1"); assert_eq!(config.log_level, "debug"); } }