use std::collections::HashMap; use std::collections::HashSet; use std::fmt; #[derive(Default, Clone)] pub struct Env { pairs: Vec<(String, String)>, } impl Env { pub fn new() -> Self { Self::default() } pub fn with(mut self, key: impl Into, value: impl Into) -> Self { self.pairs.push((key.into(), value.into())); self } pub fn set(&mut self, key: impl Into, value: impl Into) { let key = key.into(); self.pairs.retain(|(k, _)| k != &key); self.pairs.push((key, value.into())); } pub fn unset(&mut self, key: &str) { self.pairs.retain(|(k, _)| k != key); } pub fn get(&self, key: &str) -> Option<&str> { self.pairs .iter() .find(|(k, _)| k == key) .map(|(_, v)| v.as_str()) } pub fn as_pairs(&self) -> &[(String, String)] { &self.pairs } pub fn into_pairs(self) -> Vec<(String, String)> { self.pairs } pub fn iter(&self) -> std::slice::Iter<'_, (String, String)> { self.pairs.iter() } pub fn len(&self) -> usize { self.pairs.len() } pub fn is_empty(&self) -> bool { self.pairs.is_empty() } pub fn append(&mut self, other: &Env) { for (k, v) in other.iter() { self.set(k.clone(), v.clone()); } } pub fn from_current_env() -> Self { Self { pairs: std::env::vars().collect(), } } pub fn with_current_env(mut self) -> Self { for (k, v) in std::env::vars() { self.set(k, v); } self } pub fn overlay_current_env(mut self) -> Self { let existing: HashSet = self.pairs.iter().map(|(k, _)| k.clone()).collect(); for (k, v) in std::env::vars() { if !existing.contains(&k) { self.pairs.push((k, v)); } } self } } impl fmt::Debug for Env { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let entries: Vec<(&str, String)> = self .pairs .iter() .map(|(k, v)| (k.as_str(), masked_value(k, v))) .collect(); f.debug_map() .entries(entries.iter().map(|(k, v)| (*k, v))) .finish() } } fn is_sensitive_key(key: &str) -> bool { let lower = key.to_ascii_lowercase(); let markers = [ "token", "secret", "password", "passwd", "apikey", "api_key", "credential", "authorization", "private_key", "session", "access_key", "secret_key", ]; markers.iter().any(|m| lower.contains(m)) } fn masked_value(key: &str, value: &str) -> String { if is_sensitive_key(key) && !value.is_empty() { "***REDACTED***".to_string() } else { value.to_string() } } impl fmt::Display for Env { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { for (i, (k, v)) in self.pairs.iter().enumerate() { if i > 0 { f.write_str(" ")?; } write!(f, "{}={}", k, masked_value(k, v))?; } Ok(()) } } impl From> for Env { fn from(map: HashMap) -> Self { Self { pairs: map.into_iter().collect(), } } } impl From> for Env { fn from(pairs: Vec<(String, String)>) -> Self { Self { pairs } } } impl FromIterator<(String, String)> for Env { fn from_iter>(iter: T) -> Self { Self { pairs: iter.into_iter().collect(), } } }