pub trait EnvOverridable {
fn apply_env_overrides(&mut self);
}
pub fn read_env<T: std::str::FromStr>(var_name: &str) -> Option<T> {
std::env::var(var_name).ok()?.parse::<T>().ok()
}
#[cfg(test)]
mod tests {
use super::*;
use serial_test::serial;
use std::env;
#[test]
#[serial]
fn test_read_env_string_set() {
unsafe { env::set_var("_TEST_STR", "hello") };
let result: Option<String> = read_env("_TEST_STR");
assert_eq!(result, Some("hello".to_string()));
unsafe { env::remove_var("_TEST_STR") };
}
#[test]
#[serial]
fn test_read_env_string_unset() {
unsafe { env::remove_var("_TEST_STR_UNSET") };
let result: Option<String> = read_env("_TEST_STR_UNSET");
assert_eq!(result, None);
}
#[test]
#[serial]
fn test_read_env_u16_valid() {
unsafe { env::set_var("_TEST_U16", "8080") };
let result: Option<u16> = read_env("_TEST_U16");
assert_eq!(result, Some(8080u16));
unsafe { env::remove_var("_TEST_U16") };
}
#[test]
#[serial]
fn test_read_env_u16_invalid_value() {
unsafe { env::set_var("_TEST_U16_BAD", "not_a_number") };
let result: Option<u16> = read_env("_TEST_U16_BAD");
assert_eq!(result, None);
unsafe { env::remove_var("_TEST_U16_BAD") };
}
#[test]
#[serial]
fn test_read_env_u16_overflow() {
unsafe { env::set_var("_TEST_U16_OVF", "99999") }; let result: Option<u16> = read_env("_TEST_U16_OVF");
assert_eq!(result, None);
unsafe { env::remove_var("_TEST_U16_OVF") };
}
#[test]
#[serial]
fn test_read_env_u64_valid() {
unsafe { env::set_var("_TEST_U64", "9999999999") };
let result: Option<u64> = read_env("_TEST_U64");
assert_eq!(result, Some(9_999_999_999u64));
unsafe { env::remove_var("_TEST_U64") };
}
#[test]
#[serial]
fn test_read_env_bool_true() {
unsafe { env::set_var("_TEST_BOOL", "true") };
let result: Option<bool> = read_env("_TEST_BOOL");
assert_eq!(result, Some(true));
unsafe { env::remove_var("_TEST_BOOL") };
}
#[test]
#[serial]
fn test_read_env_bool_false() {
unsafe { env::set_var("_TEST_BOOL_F", "false") };
let result: Option<bool> = read_env("_TEST_BOOL_F");
assert_eq!(result, Some(false));
unsafe { env::remove_var("_TEST_BOOL_F") };
}
#[test]
#[serial]
fn test_read_env_bool_invalid() {
unsafe { env::set_var("_TEST_BOOL_BAD", "yes") }; let result: Option<bool> = read_env("_TEST_BOOL_BAD");
assert_eq!(result, None);
unsafe { env::remove_var("_TEST_BOOL_BAD") };
}
#[test]
#[serial]
fn test_read_env_unset_returns_none() {
unsafe { env::remove_var("_TEST_NEVER_SET_XYZ") };
let result: Option<String> = read_env("_TEST_NEVER_SET_XYZ");
assert_eq!(result, None);
}
#[test]
#[serial]
fn test_env_overridable_trait_applies_override() {
#[derive(Default)]
struct TestConfig {
host: String,
port: u16,
}
impl EnvOverridable for TestConfig {
fn apply_env_overrides(&mut self) {
if let Some(v) = read_env::<String>("_TENV_HOST") {
self.host = v;
}
if let Some(v) = read_env::<u16>("_TENV_PORT") {
self.port = v;
}
}
}
unsafe {
env::set_var("_TENV_HOST", "example.com");
env::set_var("_TENV_PORT", "9090");
}
let mut cfg = TestConfig {
host: "localhost".to_string(),
port: 8080,
};
cfg.apply_env_overrides();
assert_eq!(cfg.host, "example.com");
assert_eq!(cfg.port, 9090);
unsafe {
env::remove_var("_TENV_HOST");
env::remove_var("_TENV_PORT");
}
}
#[test]
#[serial]
fn test_env_overridable_trait_no_override_when_unset() {
#[derive(Default)]
struct TestConfig {
value: String,
}
impl EnvOverridable for TestConfig {
fn apply_env_overrides(&mut self) {
if let Some(v) = read_env::<String>("_TENV_ABSENT_VAR") {
self.value = v;
}
}
}
unsafe { env::remove_var("_TENV_ABSENT_VAR") };
let mut cfg = TestConfig {
value: "original".to_string(),
};
cfg.apply_env_overrides();
assert_eq!(cfg.value, "original");
}
}