use figment::providers::Env;
use figment::Figment;
#[cfg(feature = "dotenv")]
use std::path::Path;
use crate::error::Error;
#[cfg(not(feature = "dotenv"))]
use crate::error::ErrorKind;
use crate::source::LoadSpec;
pub(super) fn environment(prefix: &str, key: &str, nest: &str, allow_empty: bool) -> Env {
let mut env = Env::prefixed(prefix);
if !allow_empty {
let empty = empty_keys(prefix);
if !empty.is_empty() {
env = env.filter_map(move |name| {
let is_empty = empty
.iter()
.any(|candidate| candidate.eq_ignore_ascii_case(name.as_str()));
(!is_empty).then(|| name.into())
});
}
}
env.split(nest).profile(key)
}
fn empty_keys(prefix: &str) -> Vec<String> {
std::env::vars_os()
.filter_map(|(name, value)| {
let name = name.into_string().ok()?;
let value = value.into_string().unwrap_or_else(|_| "x".to_owned());
Some((name, value))
})
.filter(|(_, value)| value.trim().is_empty())
.filter_map(|(name, _)| {
name.get(..prefix.len())
.filter(|candidate| candidate.eq_ignore_ascii_case(prefix))
.map(|_| name[prefix.len()..].to_owned())
})
.collect()
}
#[cfg(feature = "dotenv")]
pub(super) fn merge_env_files(mut figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
let Some(prefix) = spec.full_env_prefix() else {
return Ok(figment);
};
for file in spec.env_files {
let path = Path::new(file);
let entries = crate::dotenv::read(path)?;
if entries.is_empty() {
continue;
}
figment = figment.merge(crate::dotenv::DotenvProvider::new(
entries,
path,
&prefix,
spec.key,
spec.nest,
spec.allow_empty_env,
));
}
Ok(figment)
}
#[cfg(not(feature = "dotenv"))]
pub(super) fn merge_env_files(figment: Figment, spec: &LoadSpec<'_>) -> Result<Figment, Error> {
if spec.env_files.is_empty() {
return Ok(figment);
}
Err(Error::new(
ErrorKind::Backend,
"`.env` files need the `dotenv` feature; add features = [\"dotenv\"] \
to your dynamic-config dependency",
))
}