use std::{ env, fs, path::{Path, PathBuf}, }; use log::info; use serde::de::DeserializeOwned; use thiserror::Error; #[derive(Error, Debug)] pub enum ConfigError { /// The configuration file could not be found or read. #[error("Could not open config from {}: {source:?}", path.display())] OpenConfig { path: PathBuf, #[source] source: std::io::Error, }, /// The configuration file could not be parsed or deserialized. #[error("Could not deserialize config from {}: {source:?}", path.display())] DeserializeConfig { path: PathBuf, #[source] source: toml::de::Error, }, /// The current directory could not be determined. #[error("Unable to determine the current directory.")] GetCurrentDir { #[from] source: std::io::Error, }, /// None of the provided paths were valid configuration files. #[error("None of the provided paths were valid configuration files.")] NoValidPath, } /// Attempts to read a Config object from the specified path. /// /// The configuration file is expected to be a TOML file. pub fn from_file<P: AsRef<Path>, C: DeserializeOwned>(path: P) -> Result<C, ConfigError> { let path = path.as_ref(); info!("Reading config file from {}", path.display()); let contents = fs::read_to_string(path).map_err(|source| ConfigError::OpenConfig { path: path.into(), source, })?; let config = toml::from_str(&contents); config.map_err(|source| ConfigError::DeserializeConfig { path: path.into(), source, }) } /// Attempts to read a Config object from the specified paths. pub fn from_paths<P: AsRef<Path>, C: DeserializeOwned>(paths: Vec<P>) -> Result<C, ConfigError> { for path in paths { if path.as_ref().exists() { return from_file(path); } } Err(ConfigError::NoValidPath) } /// Attempts to read a Config object from the current directory. /// /// The configuration file is expected to be a TOML file named `config.toml`. pub fn from_current_dir<C: DeserializeOwned>() -> Result<C, ConfigError> { let mut current_path = env::current_dir().map_err(|source| ConfigError::GetCurrentDir { source })?; current_path.push("config.toml"); from_file(¤t_path) } /// Similar to `from_paths`. Uses a default set of paths: /// /// - CURRENT_WORKING_DIRECTORY/config.toml /// - /etc/CRATE/config.toml pub fn from_default_paths<P: AsRef<Path>, C: DeserializeOwned>( application_name: &str, additional_paths: &[P], ) -> Result<C, ConfigError> { let mut current_path = env::current_dir().map_err(|source| ConfigError::GetCurrentDir { source })?; current_path.push("config.toml"); let mut config_path = PathBuf::from("/etc/"); config_path.push(application_name); config_path.push("config.toml"); let mut paths: Vec<PathBuf> = vec![]; for path in additional_paths { let mut pathbuf = PathBuf::new(); pathbuf.push(path); paths.push(pathbuf); } paths.push(current_path); paths.push(config_path); from_paths(paths) }