pub type SecretTriple = (Option<String>, Option<String>, Option<String>);
pub trait EnvSource {
fn var(&self, key: &str) -> Option<String>;
fn var_any(&self, keys: &[&str]) -> Option<String> {
keys.iter().find_map(|k| self.var(k))
}
fn var_triple(&self, key: &str, file_key: &str, command_key: &str) -> SecretTriple {
(self.var(key), self.var(file_key), self.var(command_key))
}
}
#[derive(Debug, Clone, Copy, Default)]
pub struct SystemEnv;
impl EnvSource for SystemEnv {
fn var(&self, key: &str) -> Option<String> {
std::env::var(key).ok()
}
}
impl<T: EnvSource + ?Sized> EnvSource for &T {
fn var(&self, key: &str) -> Option<String> {
(**self).var(key)
}
fn var_triple(&self, key: &str, file_key: &str, command_key: &str) -> SecretTriple {
(**self).var_triple(key, file_key, command_key)
}
}
pub(crate) fn non_empty_var(env: &impl EnvSource, key: &str) -> Option<String> {
env.var(key).filter(|v| !v.is_empty())
}
pub(crate) fn truthy_var(env: &impl EnvSource, key: &str) -> bool {
let Some(raw) = non_empty_var(env, key) else {
return false;
};
match raw.trim().to_ascii_lowercase().as_str() {
"1" | "true" | "yes" => true,
"0" | "false" | "no" => false,
_ => {
tracing::warn!(
"{key}={raw:?} is not a recognized boolean (expected 1/true/yes or \
0/false/no); treating it as unset"
);
false
}
}
}
pub(crate) struct ScopedEnvVar {
key: &'static str,
previous: Option<String>,
}
impl ScopedEnvVar {
pub(crate) fn set(key: &'static str, value: &str) -> Self {
let previous = std::env::var(key).ok();
std::env::set_var(key, value);
Self { key, previous }
}
}
impl Drop for ScopedEnvVar {
fn drop(&mut self) {
match &self.previous {
Some(value) => std::env::set_var(self.key, value),
None => std::env::remove_var(self.key),
}
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use crate::test_support::env::MapEnv;
#[test]
fn map_env_returns_inserted_values_and_none_otherwise() {
let env = MapEnv::new().with("USE_OPENAI", "true");
assert_eq!(env.var("USE_OPENAI").as_deref(), Some("true"));
assert_eq!(env.var("MISSING"), None);
}
#[test]
fn var_any_returns_first_set_key() {
let env = MapEnv::new().with("ANTHROPIC_API_KEY", "k");
assert_eq!(
env.var_any(&["CLAUDE_API_KEY", "ANTHROPIC_API_KEY"])
.as_deref(),
Some("k")
);
assert_eq!(env.var_any(&["A", "B"]), None);
}
#[test]
fn reference_forwards_to_inner_source() {
let env = MapEnv::new().with("K", "v");
fn read(src: &impl EnvSource) -> Option<String> {
src.var("K")
}
assert_eq!(read(&&env).as_deref(), Some("v"));
}
#[test]
fn reference_forwards_var_triple_to_inner_source() {
let env = MapEnv::new()
.with("K", "v")
.with("K_FILE", "f")
.with("K_COMMAND", "c");
fn read_triple(src: &impl EnvSource) -> SecretTriple {
src.var_triple("K", "K_FILE", "K_COMMAND")
}
assert_eq!(
read_triple(&&env),
(
Some("v".to_string()),
Some("f".to_string()),
Some("c".to_string())
)
);
}
#[test]
fn truthy_var_parses_truthy_and_falsy_values_case_insensitively() {
for v in ["1", "true", "TRUE", "yes", "YES"] {
assert!(truthy_var(&MapEnv::new().with("FLAG", v), "FLAG"), "{v:?}");
}
for v in ["0", "false", "FALSE", "no", "NO"] {
assert!(!truthy_var(&MapEnv::new().with("FLAG", v), "FLAG"), "{v:?}");
}
}
#[test]
fn truthy_var_treats_unset_as_false() {
assert!(!truthy_var(&MapEnv::new(), "FLAG"));
}
#[test]
fn truthy_var_treats_unrecognized_value_as_false() {
assert!(!truthy_var(&MapEnv::new().with("FLAG", "treu"), "FLAG"));
}
}