Newer
Older
#[cfg(feature = "config-json")]
use figment::providers::Json;
#[cfg(feature = "config-yaml")]
use figment::providers::Yaml;
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment, Profile,
};
use log::{debug, info};
use serde::{de::DeserializeOwned, Serialize};
#[cfg(feature = "xdg")]
use xdg;
use crate::str::to_train_case;
#[allow(clippy::large_enum_variant)]
#[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,
/// A config file format was not speicfied and we were unable to infer one
/// from the path.
#[error("Unable to derive config file format from extension.")]
UnknownExtension,
#[error("Unable to extract config from providers: {source}")]
ExtractConfig {
#[from]
source: figment::Error,
},
#[derive(Clone)]
pub enum ConfigFileFormat {
#[cfg(feature = "config-json")]
Json,
Toml,
#[cfg(feature = "config-yaml")]
Yaml,
}
/// Attempts to read a Config object from the specified path.
///
/// The expected file format can be optionally specified. If a format is not
/// specified, the library will attempt to infer it from the file extension.
pub fn figment_from_file<P: AsRef<Path>>(
path: P,
format: Option<ConfigFileFormat>,
) -> Result<Figment, ConfigError> {
let format = match format {
Some(format) => format,
None => infer_format_from_path(path)?,
};
info!("Reading config file from {}", path.display());
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
let figment = Figment::new();
let figment = match format {
#[cfg(feature = "config-json")]
ConfigFileFormat::Json => figment.merge(Json::file(path)),
ConfigFileFormat::Toml => figment.merge(Toml::file(path)),
#[cfg(feature = "config-yaml")]
ConfigFileFormat::Yaml => figment.merge(Yaml::file(path)),
};
Ok(figment)
}
/// Attempts to read a Config object from the specified path.
///
/// The expected file format can be optionally specified. If a format is not
/// specified, the library will attempt to infer it from the file extension.
pub fn from_file<P: AsRef<Path>, C: DeserializeOwned>(
path: P,
format: Option<ConfigFileFormat>,
) -> Result<C, ConfigError> {
extract(figment_from_file(path, format)?)
}
/// Attempts to read a Config object from the specified paths.
pub fn figment_from_paths<P: AsRef<Path>>(
paths: Vec<(P, Option<ConfigFileFormat>)>,
) -> Result<Option<Figment>, ConfigError> {
for (path, format) in paths {
if path.as_ref().exists() {
return Ok(Some(figment_from_file(path, format)?));
}
}
Ok(None)
}
/// Attempts to read a Config object from the specified paths.
pub fn from_paths<P: AsRef<Path>, C: DeserializeOwned>(
paths: Vec<(P, Option<ConfigFileFormat>)>,
) -> Result<C, ConfigError> {
match figment_from_paths(paths)? {
Some(figment) => extract(figment),
None => Err(ConfigError::NoValidPath),
}
}
/// Attempts to read a Config object from the current directory.
///
/// The configuration file is expected to be a file named `config.ext` where
/// `ext` is one of `json`, `toml`, or `yaml`.
pub fn from_current_dir<C: DeserializeOwned>(format: ConfigFileFormat) -> Result<C, ConfigError> {
let current_path = get_current_dir_config_path(&format)?;
from_file(current_path, Some(format))
}
/// 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, Option<ConfigFileFormat>)],
format: ConfigFileFormat,
let mut paths = as_paths(additional_paths);
paths.push((get_current_dir_config_path(&format)?, Some(format.clone())));
paths.push((
get_name_config_path(application_name, &format),
Some(format),
));
from_paths(paths)
}
/// 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, Option<ConfigFileFormat>)],
format: ConfigFileFormat,
) -> Result<C, ConfigError> {
let mut paths = as_paths(additional_paths);
paths.push((get_current_dir_config_path(&format)?, Some(format.clone())));
paths.push((
get_name_config_path(application_name, &format),
Some(format),
));
let default_config: C = Default::default();
let figment = Figment::from(Serialized::from(default_config, Profile::default()));
let figment = match figment_from_paths(paths)? {
Some(file_figment) => figment.merge(file_figment),
None => figment,
};
let env_prefix = format!("{}_", to_train_case(application_name));
debug!("Using env prefix: {}", &env_prefix);
extract(figment.merge(Env::prefixed(&env_prefix)))
fn as_paths<P: AsRef<Path>>(
path_refs: &[(P, Option<ConfigFileFormat>)],
) -> Vec<(PathBuf, Option<ConfigFileFormat>)> {
let mut paths: Vec<(PathBuf, Option<ConfigFileFormat>)> = vec![];
for (path, format) in path_refs {
let mut pathbuf = PathBuf::new();
pathbuf.push(path);
paths.push((pathbuf, format.clone()));
paths
}
fn get_current_dir_config_path(format: &ConfigFileFormat) -> Result<PathBuf, ConfigError> {
let mut current_path =
env::current_dir().map_err(|source| ConfigError::GetCurrentDir { source })?;
current_path.push(get_default_filename_from_format(format));
Ok(current_path)
}
fn get_name_config_path(application_name: &str, format: &ConfigFileFormat) -> PathBuf {
let mut config_path = PathBuf::from("/etc/");
config_path.push(application_name);
config_path.push(get_default_filename_from_format(format));
#[cfg(feature = "xdg")]
pub fn get_name_xdg_config_path(
application_name: &str,
format: &ConfigFileFormat,
) -> Option<PathBuf> {
let xdg_dirs = xdg::BaseDirectories::with_prefix(application_name).ok();
if let Some(xdg_dirs) = xdg_dirs {
xdg_dirs.find_config_file(get_default_filename_from_format(format))
} else {
None
}
}
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
fn infer_format_from_path<P: AsRef<Path>>(path: P) -> Result<ConfigFileFormat, ConfigError> {
match path.as_ref().extension() {
Some(extension) => match extension.to_str() {
Some("yaml") => Ok(ConfigFileFormat::Yaml),
Some("yml") => Ok(ConfigFileFormat::Yaml),
Some("json") => Ok(ConfigFileFormat::Json),
Some("toml") => Ok(ConfigFileFormat::Toml),
_ => Err(ConfigError::UnknownExtension),
},
_ => Err(ConfigError::UnknownExtension),
}
}
fn get_default_filename_from_format(format: &ConfigFileFormat) -> &'static str {
match format {
#[cfg(feature = "config-json")]
ConfigFileFormat::Json => "config.json",
ConfigFileFormat::Toml => "config.toml",
#[cfg(feature = "config-yaml")]
ConfigFileFormat::Yaml => "config.yaml",
}
}
fn extract<C: DeserializeOwned>(figment: Figment) -> Result<C, ConfigError> {
figment
.extract()
.map_err(|source| ConfigError::ExtractConfig { source })