use std::sync::OnceLock;
use dotenvy::dotenv;
use serde::Deserialize;
static GLOBAL_CONFIG: OnceLock<Config> = OnceLock::new();
#[derive(Deserialize, Debug)]
pub struct Config {
pub database_url: String,
#[serde(default = "default_max_concurrent_jobs")]
pub max_concurrent_jobs: usize,
#[serde(default = "default_tick_rate_ms")]
pub tick_rate_ms: u64,
#[serde(default = "default_max_retry_attempts")]
pub max_retry_attempts: i32,
#[serde(default = "default_retry_interval_ms")]
pub retry_interval_ms: i64,
}
fn default_max_concurrent_jobs() -> usize {
4
}
fn default_tick_rate_ms() -> u64 {
1000
}
fn default_max_retry_attempts() -> i32 {
3
}
fn default_retry_interval_ms() -> i64 {
300_000
}
impl Config {
pub fn init() -> Result<(), Box<dyn std::error::Error>> {
dotenv().ok();
let config = envy::from_env::<Config>()?;
GLOBAL_CONFIG
.set(config)
.map_err(|_| "Failed to set global config")?;
Ok(())
}
pub fn from_env() -> Self {
dotenv().ok();
envy::from_env::<Config>().expect("Failed to read configuration from environment")
}
pub fn get_config() -> &'static Config {
GLOBAL_CONFIG.get_or_init(Config::from_env)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
fn clear_env_vars() {
env::remove_var("DATABASE_URL");
env::remove_var("MAX_CONCURRENT_JOBS");
env::remove_var("TICK_RATE_MS");
}
#[test]
fn test_defaults_when_env_not_set() {
clear_env_vars();
env::set_var("DATABASE_URL", "mysql://root:password@localhost/db");
let config = Config::from_env();
assert_eq!(config.database_url, "mysql://root:password@localhost/db");
assert_eq!(config.max_concurrent_jobs, 4);
assert_eq!(config.tick_rate_ms, 1000);
}
#[test]
fn test_custom_env_values() {
clear_env_vars();
env::set_var("DATABASE_URL", "postgres://user:pass@localhost/custom_db");
env::set_var("MAX_CONCURRENT_JOBS", "8");
env::set_var("TICK_RATE_MS", "500");
let config = Config::from_env();
assert_eq!(config.database_url, "postgres://user:pass@localhost/custom_db");
assert_eq!(config.max_concurrent_jobs, 8);
assert_eq!(config.tick_rate_ms, 500);
}
#[test]
#[should_panic(expected = "Failed to read configuration from environment")]
fn test_missing_db_url_panics() {
clear_env_vars();
let _config = Config::from_env();
}
}