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))
}
}
#[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)
}
}
#[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"));
}
}