Skip to content
Clone
2308dd0
159 lines 3.7 KB Rust
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<String>, value: impl Into<String>) -> Self {
self.pairs.push((key.into(), value.into()));
self
}
pub fn set(&mut self, key: impl Into<String>, value: impl Into<String>) {
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<String> = 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<HashMap<String, String>> for Env {
fn from(map: HashMap<String, String>) -> Self {
Self {
pairs: map.into_iter().collect(),
}
}
}
impl From<Vec<(String, String)>> for Env {
fn from(pairs: Vec<(String, String)>) -> Self {
Self { pairs }
}
}
impl FromIterator<(String, String)> for Env {
fn from_iter<T: IntoIterator<Item = (String, String)>>(iter: T) -> Self {
Self {
pairs: iter.into_iter().collect(),
}
}
}