use std::str::FromStr;
use std::time::Duration;
use super::ConfigError;
pub fn env_or<T: FromStr>(var: &str, default: T) -> T {
std::env::var(var)
.ok()
.and_then(|v| v.parse().ok())
.unwrap_or(default)
}
pub fn env_string(var: &str, default: &str) -> String {
std::env::var(var).unwrap_or_else(|_| default.to_string())
}
pub fn env_string_any(vars: &[&str], default: &str) -> String {
vars.iter()
.find_map(|var| env_opt_string(var))
.unwrap_or_else(|| default.to_string())
}
pub fn env_bool(var: &str, default: bool) -> bool {
std::env::var(var)
.ok()
.map(|v| v == "true" || v == "1")
.unwrap_or(default)
}
pub fn env_duration_secs(var: &str, default: Duration) -> Duration {
std::env::var(var)
.ok()
.and_then(|v| v.parse::<u64>().ok())
.map(Duration::from_secs)
.unwrap_or(default)
}
pub fn env_duration_ms(var: &str, default: Duration) -> Duration {
std::env::var(var)
.ok()
.and_then(|v| v.parse::<u64>().ok())
.map(Duration::from_millis)
.unwrap_or(default)
}
pub fn env_opt<T: FromStr>(var: &str) -> Option<T> {
std::env::var(var)
.ok()
.filter(|s| !s.is_empty())
.and_then(|v| v.parse().ok())
}
pub fn env_required<T: FromStr>(var: &str) -> Result<T, ConfigError> {
let value = std::env::var(var).map_err(|_| ConfigError::Missing {
var: var.to_string(),
})?;
value.parse().map_err(|_| ConfigError::Invalid {
var: var.to_string(),
value: value.clone(),
reason: format!("could not parse as {}", std::any::type_name::<T>()),
})
}
pub fn env_list<T: FromStr>(var: &str) -> Vec<T> {
std::env::var(var)
.ok()
.filter(|s| !s.is_empty())
.map(|s| {
s.split(',')
.filter_map(|item| item.trim().parse().ok())
.collect()
})
.unwrap_or_default()
}
pub fn env_opt_string(var: &str) -> Option<String> {
std::env::var(var).ok().filter(|s| !s.is_empty())
}
pub fn env_opt_string_any(vars: &[&str]) -> Option<String> {
vars.iter().find_map(|var| env_opt_string(var))
}
#[cfg(test)]
mod tests {
use super::*;
use std::env;
const PREFIX: &str = "EVERRUNS_CONFIG_TEST_";
fn set(suffix: &str, val: &str) -> String {
let key = format!("{PREFIX}{suffix}");
unsafe { env::set_var(&key, val) };
key
}
fn unset(suffix: &str) -> String {
let key = format!("{PREFIX}{suffix}");
unsafe { env::remove_var(&key) };
key
}
#[test]
fn env_or_present() {
let key = set("OR_PRESENT", "42");
assert_eq!(env_or::<u32>(&key, 0), 42);
}
#[test]
fn env_or_missing() {
let key = unset("OR_MISSING");
assert_eq!(env_or::<u32>(&key, 7), 7);
}
#[test]
fn env_or_unparseable() {
let key = set("OR_BAD", "not_a_number");
assert_eq!(env_or::<u32>(&key, 99), 99);
}
#[test]
fn env_string_present() {
let key = set("STR_P", "hello");
assert_eq!(env_string(&key, "default"), "hello");
}
#[test]
fn env_string_missing() {
let key = unset("STR_M");
assert_eq!(env_string(&key, "fallback"), "fallback");
}
#[test]
fn env_string_any_uses_first_non_empty_value() {
let first = unset("STR_ANY_FIRST");
let second = set("STR_ANY_SECOND", "");
let third = set("STR_ANY_THIRD", "value");
assert_eq!(
env_string_any(&[&first, &second, &third], "fallback"),
"value"
);
}
#[test]
fn env_string_any_falls_back_to_default() {
let first = unset("STR_ANY_MISSING_FIRST");
let second = set("STR_ANY_MISSING_SECOND", "");
assert_eq!(env_string_any(&[&first, &second], "fallback"), "fallback");
}
#[test]
fn env_bool_true_variants() {
let key = set("BOOL_T", "true");
assert!(env_bool(&key, false));
let key = set("BOOL_1", "1");
assert!(env_bool(&key, false));
}
#[test]
fn env_bool_false() {
let key = set("BOOL_F", "false");
assert!(!env_bool(&key, true));
}
#[test]
fn env_bool_missing() {
let key = unset("BOOL_M");
assert!(env_bool(&key, true));
}
#[test]
fn env_duration_secs_present() {
let key = set("DUR_S", "10");
assert_eq!(
env_duration_secs(&key, Duration::from_secs(1)),
Duration::from_secs(10)
);
}
#[test]
fn env_duration_secs_missing() {
let key = unset("DUR_S_M");
assert_eq!(
env_duration_secs(&key, Duration::from_secs(5)),
Duration::from_secs(5)
);
}
#[test]
fn env_duration_ms_present() {
let key = set("DUR_MS", "250");
assert_eq!(
env_duration_ms(&key, Duration::from_millis(100)),
Duration::from_millis(250)
);
}
#[test]
fn env_opt_present() {
let key = set("OPT_P", "42");
assert_eq!(env_opt::<u32>(&key), Some(42));
}
#[test]
fn env_opt_missing() {
let key = unset("OPT_M");
assert_eq!(env_opt::<u32>(&key), None::<u32>);
}
#[test]
fn env_opt_empty() {
let key = set("OPT_E", "");
assert_eq!(env_opt::<u32>(&key), None::<u32>);
}
#[test]
fn env_required_ok() {
let key = set("REQ_OK", "100");
assert_eq!(env_required::<u32>(&key).unwrap(), 100);
}
#[test]
fn env_required_missing() {
let key = unset("REQ_M");
let err = env_required::<u32>(&key).unwrap_err();
assert!(matches!(err, ConfigError::Missing { .. }));
}
#[test]
fn env_required_invalid() {
let key = set("REQ_BAD", "xyz");
let err = env_required::<u32>(&key).unwrap_err();
assert!(matches!(err, ConfigError::Invalid { .. }));
}
#[test]
fn env_list_present() {
let key = set("LIST_P", "1, 2, 3");
assert_eq!(env_list::<u32>(&key), vec![1, 2, 3]);
}
#[test]
fn env_list_missing() {
let key = unset("LIST_M");
assert_eq!(env_list::<u32>(&key), Vec::<u32>::new());
}
#[test]
fn env_list_with_bad_items() {
let key = set("LIST_B", "1,bad,3");
assert_eq!(env_list::<u32>(&key), vec![1, 3]);
}
#[test]
fn env_opt_string_present() {
let key = set("OSTR_P", "value");
assert_eq!(env_opt_string(&key), Some("value".to_string()));
}
#[test]
fn env_opt_string_empty() {
let key = set("OSTR_E", "");
assert_eq!(env_opt_string(&key), None);
}
#[test]
fn env_opt_string_missing() {
let key = unset("OSTR_M");
assert_eq!(env_opt_string(&key), None);
}
#[test]
fn env_opt_string_any_uses_first_non_empty_value() {
let first = unset("OSTR_ANY_FIRST");
let second = set("OSTR_ANY_SECOND", "");
let third = set("OSTR_ANY_THIRD", "value");
assert_eq!(
env_opt_string_any(&[&first, &second, &third]),
Some("value".to_string())
);
}
}