use crate::models::{ConfigError, EnvConfig, LoadingParam};
use config::{Config, File, FileFormat};
use serde::Deserialize;
use std::env;
use std::path::Path;
pub fn load_config_with_param<T>(param: &LoadingParam) -> Result<T, ConfigError>
where
T: for<'de> Deserialize<'de> + serde::Serialize,
{
log_loading_params(param);
validate_loading_params(param)?;
let mut config_builder = Config::builder();
if let Some(file_path) = param.file {
log::debug!("trying to load config file: {:?}", file_path);
config_builder = add_file_source(config_builder, file_path)?;
}
if let Some(env_config) = ¶m.env_prefix {
config_builder = add_env_source(config_builder, env_config)?;
}
let config = config_builder.build()?;
let result: T = config.try_deserialize()?;
if should_show_settings(param) {
log_loaded_config(&result);
}
Ok(result)
}
pub fn validate_loading_params(param: &LoadingParam) -> Result<(), ConfigError> {
if param.file.is_none() && param.env_prefix.is_none() {
return Err(ConfigError::InvalidLoadingParam);
}
if let Some(env_config) = ¶m.env_prefix {
validate_env_config(env_config)?;
}
Ok(())
}
fn validate_env_config(env_config: &EnvConfig) -> Result<(), ConfigError> {
let separator = env_config.get_separator();
if env_config.name.contains(separator) {
return Err(ConfigError::InvalidEnvConfig {
prefix: env_config.name.clone(),
separator: separator.to_string(),
});
}
Ok(())
}
fn add_file_source(
config_builder: config::ConfigBuilder<config::builder::DefaultState>,
file_path: &Path,
) -> Result<config::ConfigBuilder<config::builder::DefaultState>, ConfigError> {
if !file_path.exists() {
return Err(ConfigError::FileNotFound(file_path.to_path_buf()));
}
let format = get_file_format(file_path);
Ok(config_builder.add_source(File::from(file_path).format(format)))
}
fn get_file_format(file_path: &Path) -> FileFormat {
match file_path.extension().and_then(|ext| ext.to_str()) {
Some("json") => FileFormat::Json,
Some("yaml") | Some("yml") => FileFormat::Yaml,
Some("toml") => FileFormat::Toml,
Some("ini") => FileFormat::Ini,
_ => FileFormat::Yaml, }
}
fn add_env_source(
config_builder: config::ConfigBuilder<config::builder::DefaultState>,
env_config: &EnvConfig,
) -> Result<config::ConfigBuilder<config::builder::DefaultState>, ConfigError> {
let prefix = &env_config.name;
let separator = env_config.get_separator();
let env_vars_with_prefix: Vec<String> = env::vars()
.filter(|(key, _)| key.starts_with(prefix))
.map(|(key, _)| key)
.collect();
if env_vars_with_prefix.is_empty() {
log::warn!("No environment variables found with prefix: '{}'. Skipping environment variable loading.", prefix);
return Ok(config_builder);
}
Ok(config_builder.add_source(
config::Environment::with_prefix(prefix)
.separator(separator)
.try_parsing(true),
))
}
fn should_show_settings(param: &LoadingParam) -> bool {
if let Some(env_config) = ¶m.env_prefix {
let env_full_name = format!(
"{}{}SHOW_SETTINGS",
&env_config.name,
&env_config.get_separator()
);
match env::var(&env_full_name) {
Ok(value) => {
let lower_value = value.to_lowercase();
let result = lower_value == "true"
|| lower_value == "1"
|| lower_value == "yes"
|| lower_value == "on";
log::info!("{} is set, return {}", &env_full_name, result);
result
}
Err(_) => {
log::warn!("{} not set, return false", &env_full_name);
false
}
}
} else {
false
}
}
fn log_loading_params(param: &LoadingParam) {
if let Some(file_path) = param.file {
log::info!("Loading configuration from file: {:?}", file_path);
}
if let Some(env_config) = ¶m.env_prefix {
log::info!(
"Loading configuration from environment variables with prefix: '{}' and separator: '{}'",
env_config.name,
env_config.get_separator()
);
}
}
fn log_loaded_config<T>(config: &T)
where
T: serde::Serialize,
{
match serde_json::to_string_pretty(config) {
Ok(serialized) => {
log::info!(
"Configuration loaded successfully (SHOW_SETTINGS enabled):\n{}",
serialized
);
}
Err(e) => {
log::warn!("Failed to serialize configuration for logging: {}", e);
log::info!("Configuration loaded successfully (SHOW_SETTINGS enabled)");
}
}
}
#[cfg(test)]
pub fn test_should_show_settings(param: &LoadingParam) -> bool {
should_show_settings(param)
}