use std::collections::{HashMap, HashSet};
use std::sync::{OnceLock, RwLock};
static RUNTIME_ENV: OnceLock<RwLock<HashMap<String, String>>> = OnceLock::new();
static MASKED_KEYS: OnceLock<RwLock<HashSet<String>>> = OnceLock::new();
fn env_map() -> &'static RwLock<HashMap<String, String>> {
RUNTIME_ENV.get_or_init(|| RwLock::new(HashMap::new()))
}
fn masked() -> &'static RwLock<HashSet<String>> {
MASKED_KEYS.get_or_init(|| RwLock::new(HashSet::new()))
}
pub fn set(key: impl Into<String>, value: impl Into<String>) {
let key = key.into();
masked()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.remove(&key);
env_map()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.insert(key, value.into());
}
pub fn get(key: &str) -> Option<String> {
if let Some(val) = env_map()
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.get(key)
{
return Some(val.clone());
}
if masked()
.read()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.contains(key)
{
return None;
}
std::env::var(key).ok()
}
pub fn is_set(key: &str) -> bool {
get(key).is_some()
}
pub fn remove(key: &str) -> Option<String> {
env_map()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.remove(key)
}
pub fn mask(key: impl Into<String>) {
masked()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.insert(key.into());
}
pub fn unmask(key: &str) {
masked()
.write()
.unwrap_or_else(|poisoned| poisoned.into_inner())
.remove(key);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_set_and_get() {
set("TEST_RUNTIME_KEY", "hello");
assert_eq!(get("TEST_RUNTIME_KEY"), Some("hello".to_string()));
}
#[test]
fn test_remove() {
set("TEST_REMOVE_KEY", "value");
assert!(is_set("TEST_REMOVE_KEY"));
env_map().write().unwrap().remove("TEST_REMOVE_KEY");
}
#[test]
fn test_falls_back_to_env() {
assert!(get("PATH").is_some());
}
#[test]
fn test_runtime_takes_precedence() {
set("PATH", "overridden");
assert_eq!(get("PATH"), Some("overridden".to_string()));
env_map().write().unwrap().remove("PATH");
}
#[test]
fn test_remove_returns_previous_value() {
set("TEST_REMOVE_RETURN", "orig");
assert_eq!(remove("TEST_REMOVE_RETURN"), Some("orig".to_string()));
assert_eq!(remove("TEST_REMOVE_RETURN"), None);
}
#[test]
fn test_remove_does_not_touch_process_env() {
let _ = remove("HOME"); assert!(
get("HOME").is_some(),
"process env HOME must survive a runtime-map remove"
);
}
#[test]
fn test_mask_hides_process_env_from_get() {
const KEY: &str = "KODA_TEST_MASK_HIDES_PROC";
assert_eq!(get(KEY), None, "precondition: key not set");
mask(KEY);
assert_eq!(get(KEY), None, "masked key returns None");
set(KEY, "shadow");
assert_eq!(
get(KEY),
Some("shadow".to_string()),
"runtime override wins over mask"
);
remove(KEY);
unmask(KEY);
}
#[test]
fn test_unmask_restores_process_env_visibility() {
const KEY: &str = "KODA_TEST_UNMASK_RESTORES";
set(KEY, "runtime_value");
assert_eq!(get(KEY), Some("runtime_value".to_string()));
remove(KEY);
mask(KEY);
assert_eq!(get(KEY), None, "masked key returns None");
unmask(KEY);
assert_eq!(
get(KEY),
std::env::var(KEY).ok(),
"after unmask, get() falls back to process env"
);
}
#[test]
fn test_set_implicitly_unmasks() {
const KEY: &str = "KODA_TEST_SET_UNMASKS";
mask(KEY);
assert_eq!(get(KEY), None);
set(KEY, "value");
assert_eq!(get(KEY), Some("value".to_string()));
remove(KEY);
assert_eq!(
get(KEY),
std::env::var(KEY).ok(),
"after set+remove, get falls back to process env (mask gone)"
);
}
}