use anyhow::Context; use serde::de::DeserializeOwned; use std::fs; use std::path::Path; pub fn load_toml_config(path: &Path) -> anyhow::Result where T: DeserializeOwned + Default, { if !path.exists() { return Ok(T::default()); } let contents = fs::read_to_string(path) .with_context(|| format!("failed to read config file {}", path.display()))?; toml::from_str(&contents) .with_context(|| format!("failed to parse config file {}", path.display())) } #[cfg(test)] mod tests { use super::load_toml_config; use serde::Deserialize; use std::fs; use std::path::PathBuf; use std::time::{SystemTime, UNIX_EPOCH}; #[derive(Debug, Default, Deserialize, PartialEq)] struct TestConfig { value: String, } fn temp_path(name: &str) -> PathBuf { let nanos = SystemTime::now() .duration_since(UNIX_EPOCH) .unwrap() .as_nanos(); std::env::temp_dir().join(format!("{}-{}-{}.toml", name, std::process::id(), nanos)) } #[test] fn returns_default_when_file_is_missing() { let path = temp_path("photon-config-missing"); let config: TestConfig = load_toml_config(&path).unwrap(); assert_eq!(config, TestConfig::default()); } #[test] fn loads_existing_toml_file() { let path = temp_path("photon-config-load"); fs::write(&path, "value = \"hello\"\n").unwrap(); let config: TestConfig = load_toml_config(&path).unwrap(); assert_eq!(config.value, "hello"); let _ = fs::remove_file(path); } }