use std::{
ffi::OsStr,
sync::atomic::{AtomicU8, Ordering},
};
const DISABLED: u8 = 0;
const ENABLED: u8 = 1;
const UNRESOLVED: u8 = 2;
#[repr(C)]
pub struct EnvGuard {
state: AtomicU8,
pub name: &'static str,
}
impl EnvGuard {
pub const fn new(name: &'static str) -> EnvGuard {
Self {
state: AtomicU8::new(UNRESOLVED),
name,
}
}
#[inline(always)]
pub fn is_enabled(&self) -> bool {
match self.state.load(Ordering::Relaxed) {
DISABLED => false,
ENABLED => true,
_ => self.load_from_env(),
}
}
#[inline(never)]
#[cold]
pub fn load_from_env(&self) -> bool {
let enabled = if let Some(value) = std::env::var_os(self.name) {
if value.as_os_str() != OsStr::new("0") {
ENABLED
} else {
DISABLED
}
} else {
DISABLED
};
match self.state.compare_exchange(
UNRESOLVED,
enabled,
Ordering::Relaxed,
Ordering::Relaxed,
) {
Ok(_) => enabled == ENABLED,
Err(state) => state == ENABLED,
}
}
pub fn set(&self, value: bool) {
self.state.store(
if value { ENABLED } else { DISABLED },
Ordering::Relaxed,
);
}
}
#[cfg(test)]
mod tests {
use super::{EnvGuard, ENABLED};
use std::sync::atomic::Ordering;
#[test]
fn loads_and_caches_environment_values() {
const UNSET: &str = "KVLOG_TEST_ENV_GUARD_UNSET";
const ZERO: &str = "KVLOG_TEST_ENV_GUARD_ZERO";
const EMPTY: &str = "KVLOG_TEST_ENV_GUARD_EMPTY";
const TEXT: &str = "KVLOG_TEST_ENV_GUARD_TEXT";
std::env::remove_var(UNSET);
std::env::set_var(ZERO, "0");
std::env::set_var(EMPTY, "");
std::env::set_var(TEXT, "enabled");
assert!(!EnvGuard::new(UNSET).is_enabled());
assert!(!EnvGuard::new(ZERO).is_enabled());
assert!(EnvGuard::new(EMPTY).is_enabled());
let guard = EnvGuard::new(TEXT);
assert_eq!(guard.name, TEXT);
assert!(guard.is_enabled());
std::env::set_var(TEXT, "0");
assert!(guard.is_enabled());
std::env::remove_var(ZERO);
std::env::remove_var(EMPTY);
std::env::remove_var(TEXT);
}
#[test]
fn explicit_values_override_environment_loading() {
const FALSE_ENV: &str = "KVLOG_TEST_ENV_GUARD_EXPLICIT_TRUE";
const TRUE_ENV: &str = "KVLOG_TEST_ENV_GUARD_EXPLICIT_FALSE";
std::env::set_var(FALSE_ENV, "0");
std::env::set_var(TRUE_ENV, "1");
let enabled = EnvGuard::new(FALSE_ENV);
enabled.set(true);
assert!(enabled.load_from_env());
assert_eq!(enabled.state.load(Ordering::Relaxed), ENABLED);
let disabled = EnvGuard::new(TRUE_ENV);
disabled.set(false);
assert!(!disabled.load_from_env());
let loaded = EnvGuard::new(FALSE_ENV);
assert!(!loaded.is_enabled());
loaded.set(true);
assert!(loaded.is_enabled());
std::env::remove_var(FALSE_ENV);
std::env::remove_var(TRUE_ENV);
}
#[cfg(unix)]
#[test]
fn non_unicode_environment_values_enable_the_guard() {
use std::{ffi::OsString, os::unix::ffi::OsStringExt};
const NAME: &str = "KVLOG_TEST_ENV_GUARD_NON_UNICODE";
std::env::set_var(NAME, OsString::from_vec(vec![0xff]));
assert!(EnvGuard::new(NAME).is_enabled());
std::env::remove_var(NAME);
}
}