#[must_use]
pub fn read_var(variable: &str) -> Option<String> {
let raw = std::env::var(variable).ok()?;
if raw.trim().is_empty() {
None
} else {
Some(raw)
}
}
#[must_use]
pub fn read_secret(variable: &str) -> Option<String> {
std::env::var(variable).ok()
}
#[cfg(test)]
mod tests {
use super::{read_secret, read_var};
use once_cell::sync::Lazy;
use std::sync::Mutex;
static ENV_MUTEX: Lazy<Mutex<()>> = Lazy::new(|| Mutex::new(()));
const TEST_VAR: &str = "OCS_TEST_BLANK_RULE";
fn set_var(value: &str) {
#[allow(unused_unsafe)]
unsafe {
std::env::set_var(TEST_VAR, value);
}
}
fn remove_var() {
#[allow(unused_unsafe)]
unsafe {
std::env::remove_var(TEST_VAR);
}
}
#[test]
fn test_a_value_is_returned_verbatim() {
let _guard = match ENV_MUTEX.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
for raw in [" padded ", "\ttabbed", "plain", "two words"] {
set_var(raw);
assert_eq!(
read_var(TEST_VAR).as_deref(),
Some(raw),
"the value must survive unchanged"
);
}
remove_var();
}
#[test]
fn test_a_secret_may_be_empty() {
let _guard = match ENV_MUTEX.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
set_var("");
assert_eq!(read_secret(TEST_VAR).as_deref(), Some(""));
assert_eq!(
read_var(TEST_VAR),
None,
"the two readers must disagree here"
);
set_var(" ");
assert_eq!(
read_secret(TEST_VAR).as_deref(),
Some(" "),
"a secret is never trimmed either"
);
remove_var();
assert_eq!(read_secret(TEST_VAR), None, "unset is still unset");
}
#[test]
fn test_an_unset_variable_is_none() {
let _guard = match ENV_MUTEX.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
remove_var();
assert_eq!(read_var(TEST_VAR), None);
}
#[test]
fn test_a_blank_variable_is_none() {
let _guard = match ENV_MUTEX.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
for blank in ["", " ", "\t", " \t "] {
set_var(blank);
assert_eq!(read_var(TEST_VAR), None, "{blank:?} must read as unset");
}
remove_var();
}
#[test]
fn test_a_padded_value_keeps_its_padding() {
let _guard = match ENV_MUTEX.lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
};
set_var(" s3cret ");
assert_eq!(read_var(TEST_VAR), Some(" s3cret ".to_string()));
remove_var();
}
}