#![allow(clippy::missing_docs_in_private_items)]
use crate::ConfigFields;
use crate::error::{ConfigError, Result};
#[cfg(feature = "files")]
use cfgmatic_files::{FileFinder, load_first};
use cfgmatic_paths::{
ConfigCandidate, ConfigDiscovery, DiscoveryOptions, FilePattern, PathsBuilder,
};
#[derive(Debug)]
pub struct ConfigLoadResult<T> {
pub config: T,
pub discovery: ConfigDiscovery,
}
#[derive(Debug, Clone)]
pub struct ConfigManager {
app_name: String,
#[cfg(feature = "files")]
finder: FileFinder,
env_prefix: Option<String>,
use_env: bool,
}
impl ConfigManager {
pub fn new(app_name: impl Into<String>) -> Self {
let app_name = app_name.into();
Self {
#[cfg(feature = "files")]
finder: FileFinder::new(&app_name),
app_name,
env_prefix: None,
use_env: true,
}
}
#[must_use]
pub fn with_env_prefix(mut self, prefix: impl Into<String>) -> Self {
self.env_prefix = Some(prefix.into());
self
}
#[must_use]
pub const fn with_env(mut self, use_env: bool) -> Self {
self.use_env = use_env;
self
}
#[cfg(feature = "files")]
pub fn load<T>(&self) -> Result<T>
where
T: serde::de::DeserializeOwned + ConfigFields + Default,
{
let mut config: T = load_first::<T>(&self.app_name)
.map_err(ConfigError::File)?
.unwrap_or_default();
if self.use_env {
config.load_from_env(self.env_prefix.as_deref())?;
}
Ok(config)
}
#[cfg(feature = "files")]
pub fn load_with_discovery<T>(&self) -> Result<ConfigLoadResult<T>>
where
T: serde::de::DeserializeOwned + ConfigFields + Default,
{
let discovery = self.discover();
let mut config: T = load_first::<T>(&self.app_name)
.map_err(ConfigError::File)?
.unwrap_or_default();
if self.use_env {
config.load_from_env(self.env_prefix.as_deref())?;
}
Ok(ConfigLoadResult { config, discovery })
}
#[must_use]
pub fn discover(&self) -> ConfigDiscovery {
PathsBuilder::new(&self.app_name).build().discover_config()
}
#[must_use]
pub fn discover_with_options(&self, options: &DiscoveryOptions) -> ConfigDiscovery {
PathsBuilder::new(&self.app_name)
.build()
.discover_config_with_options(options)
}
#[must_use]
pub fn config_path(&self) -> std::path::PathBuf {
PathsBuilder::new(&self.app_name)
.build()
.preferred_config_path()
}
#[must_use]
pub fn config_file(&self, filename: impl AsRef<std::path::Path>) -> std::path::PathBuf {
PathsBuilder::new(&self.app_name)
.build()
.preferred_config_file(filename)
}
#[must_use]
pub fn find_config_files(&self, pattern: &FilePattern) -> Vec<ConfigCandidate> {
PathsBuilder::new(&self.app_name)
.build()
.find_config_files(pattern)
}
#[must_use]
pub fn find_fragments(
&self,
pattern: &FilePattern,
fragment_dir_name: &str,
) -> Vec<std::path::PathBuf> {
PathsBuilder::new(&self.app_name)
.build()
.find_fragments(pattern, fragment_dir_name)
}
#[cfg(feature = "files")]
pub fn load_from_files<T>(&self) -> Result<T>
where
T: serde::de::DeserializeOwned,
{
load_first::<T>(&self.app_name)
.map_err(ConfigError::File)?
.ok_or_else(|| ConfigError::NotFound(self.app_name.clone()))
}
pub fn load_from_env<T>(&self) -> Result<T>
where
T: ConfigFields + Default,
{
let mut config = T::default();
config.load_from_env(self.env_prefix.as_deref())?;
Ok(config)
}
#[cfg(feature = "files")]
#[must_use]
pub fn exists(&self) -> bool {
self.finder.clone().find_first().ok().flatten().is_some()
}
#[must_use]
pub fn app_name(&self) -> &str {
&self.app_name
}
}