-
Eduardo Trujillo authoredEduardo Trujillo authored
config.rs 4.53 KiB
use std::{
env,
path::{Path, PathBuf},
};
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment, Profile,
};
use log::{debug, info};
use serde::{de::DeserializeOwned, Serialize};
use thiserror::Error;
use crate::str::to_train_case;
#[derive(Error, Debug)]
pub enum ConfigError {
/// 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,
#[error("Unable to extract config from providers: {source}")]
ExtractConfig {
#[from]
source: figment::Error,
},
}
/// 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());
Figment::new()
.merge(Toml::file(path))
.extract()
.map_err(|source| ConfigError::ExtractConfig { 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> {
match get_first_valid_path(paths) {
Some(path) => from_file(path),
None => 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 current_path = get_current_dir_config_path()?;
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 paths = as_paths(additional_paths);
paths.push(get_current_dir_config_path()?);
paths.push(get_name_config_path(application_name));
let figment = match get_first_valid_path(paths) {
Some(path) => {
info!("Reading config file from {}", path.display());
Ok(Figment::from(Toml::file(path)))
}
None => Err(ConfigError::NoValidPath),
}?;
figment
.extract()
.map_err(|source| ConfigError::ExtractConfig { source })
}
/// Similar to `from_paths`. Uses a default set of paths:
///
/// - CURRENT_WORKING_DIRECTORY/config.toml
/// - /etc/CRATE/config.toml
pub fn from_defaults<P: AsRef<Path>, C: DeserializeOwned + Default + Serialize>(
application_name: &str,
additional_paths: &[P],
) -> Result<C, ConfigError> {
let mut paths = as_paths(additional_paths);
paths.push(get_current_dir_config_path()?);
paths.push(get_name_config_path(application_name));
let default_config: C = Default::default();
let figment = Figment::from(Serialized::from(default_config, Profile::default()));
let figment = match get_first_valid_path(paths) {
Some(path) => {
info!("Reading config file from {}", path.display());
figment.merge(Toml::file(path))
}
None => figment,
};
let env_prefix = format!("{}_", to_train_case(application_name));
debug!("Using env prefix: {}", &env_prefix);
figment
.merge(Env::prefixed(&env_prefix))
.extract()
.map_err(|source| ConfigError::ExtractConfig { source })
}
pub fn get_first_valid_path<P: AsRef<Path>>(paths: Vec<P>) -> Option<P> {
for path in paths {
if path.as_ref().exists() {
return Some(path);
}
}
None
}
fn as_paths<P: AsRef<Path>>(path_refs: &[P]) -> Vec<PathBuf> {
let mut paths: Vec<PathBuf> = vec![];
for path in path_refs {
let mut pathbuf = PathBuf::new();
pathbuf.push(path);
paths.push(pathbuf);
}
paths
}
fn get_current_dir_config_path() -> Result<PathBuf, ConfigError> {
let mut current_path =
env::current_dir().map_err(|source| ConfigError::GetCurrentDir { source })?;
current_path.push("config.toml");
Ok(current_path)
}
fn get_name_config_path(application_name: &str) -> PathBuf {
let mut config_path = PathBuf::from("/etc/");
config_path.push(application_name);
config_path.push("config.toml");
config_path
}