use crate::error::{Error, ErrorKind};
use crate::source::LoadSpec;
#[cfg(feature = "dotenv")]
pub(super) fn collect_env_files(
into: &mut crate::resolve::Collected,
spec: &LoadSpec<'_>,
) -> Result<(), Error> {
let Some(prefix) = spec.full_env_prefix() else {
return Ok(());
};
for file in spec.env_files {
let path = std::path::Path::new(file);
let entries = crate::dotenv::read(path)?;
if entries.is_empty() {
continue;
}
if spec.strict_env {
if let Some((name, _)) = entries.iter().find(|(_, value)| is_ambiguous(value)) {
return Err(ambiguous(name).with_origin(crate::Origin::File(path.to_owned())));
}
}
let values = crate::dotenv::tree(&entries, &prefix, spec.nest, spec.allow_empty_env);
if !values.is_empty() {
into.layer(".env", crate::Origin::File(path.to_owned()), values);
}
}
Ok(())
}
#[cfg(not(feature = "dotenv"))]
pub(super) fn collect_env_files(
_into: &mut crate::resolve::Collected,
spec: &LoadSpec<'_>,
) -> Result<(), Error> {
if spec.env_files.is_empty() {
return Ok(());
}
Err(Error::new(
ErrorKind::Backend,
"`.env` files need the `dotenv` feature",
))
}
const AMBIGUOUS: &[&str] = &["yes", "no", "on", "off", "null", "nil", "none"];
fn is_ambiguous(value: &str) -> bool {
let trimmed = value.trim();
AMBIGUOUS
.iter()
.any(|candidate| trimmed.eq_ignore_ascii_case(candidate))
}
fn ambiguous(variable: &str) -> Error {
Error::new(
ErrorKind::Env,
format!(
"`{variable}` is set to one of the ambiguous yes/no/on/off \
spellings, which `strict_env` refuses: it would arrive as a \
string, not a boolean; write `true`, `false`, or the value you \
mean"
),
)
}
pub(super) fn reject_ambiguous(prefix: &str) -> Result<(), Error> {
for (segments, value) in crate::env_layer::variables(prefix, "__") {
if is_ambiguous(&value) {
return Err(ambiguous(&format!(
"{prefix}{}",
segments.join("__").to_ascii_uppercase()
)));
}
}
Ok(())
}