use anyhow::anyhow; #[derive(Clone)] pub struct EnvConfig { pub http_port: u16, pub webhook_secret: String, pub open_router_api_key: String, pub open_router_model: String, pub open_router_timeout: u64, pub bot_max_concurrent: usize, pub gitea_url: String, pub gitea_token: String, pub gitea_timeout: u64, pub metrics_bind_addr: Option, } pub fn load_config() -> anyhow::Result { let http_port = try_get_env("HTTP_PORT")?.parse()?; let webhook_secret = try_get_env("WEBHOOK_SIG_HEADER_SECRET")?; let open_router_api_key = try_get_env("OPEN_ROUTER_API_KEY")?; let open_router_model = try_get_env("OPEN_ROUTER_MODEL")?; let open_router_timeout = try_get_env("OPEN_ROUTER_TIMEOUT")?.parse()?; let bot_max_concurrent = try_get_env("BOT_MAX_CONCURRENT")?.parse()?; let gitea_url = try_get_env("GITEA_URL")?; let gitea_token = try_get_env("GITEA_TOKEN")?; let gitea_timeout = try_get_env("GITEA_TIMEOUT")?.parse()?; let metrics_bind_addr = std::env::var("METRICS_BIND_ADDR").ok(); Ok(EnvConfig { http_port, webhook_secret, open_router_api_key, open_router_model, open_router_timeout, bot_max_concurrent, gitea_url, gitea_token, gitea_timeout, metrics_bind_addr, }) } pub fn try_get_env(key: &str) -> anyhow::Result { let env_value = std::env::var(key).map_err(|e| anyhow::anyhow!("{}: {}", key, e))?; if env_value.trim().is_empty() { return Err(anyhow!(format!("env var {} is empty", key))); } Ok(env_value) } #[cfg(test)] mod tests { use super::*; #[test] fn test_try_get_env_returns_value() { unsafe { std::env::set_var("TEST_ENV_PRESENT", "hello") }; assert_eq!(try_get_env("TEST_ENV_PRESENT").unwrap(), "hello"); } #[test] fn test_try_get_env_missing_var_returns_error() { unsafe { std::env::remove_var("TEST_ENV_MISSING") }; assert!(try_get_env("TEST_ENV_MISSING").is_err()); } #[test] fn test_try_get_env_empty_value_returns_error() { unsafe { std::env::set_var("TEST_ENV_EMPTY", "") }; let err = try_get_env("TEST_ENV_EMPTY").unwrap_err(); assert!(err.to_string().contains("TEST_ENV_EMPTY")); } #[test] fn test_try_get_env_whitespace_only_returns_error() { unsafe { std::env::set_var("TEST_ENV_WHITESPACE", " ") }; let err = try_get_env("TEST_ENV_WHITESPACE").unwrap_err(); assert!(err.to_string().contains("TEST_ENV_WHITESPACE")); } }