#[cfg(test)]
mod tests;
pub trait FromEnvStr: Sized {
fn from_env_str(s: &str) -> Result<Self, String>;
}
impl FromEnvStr for String {
fn from_env_str(s: &str) -> Result<Self, String> {
Ok(s.to_string())
}
}
impl FromEnvStr for bool {
fn from_env_str(s: &str) -> Result<Self, String> {
match s.trim() {
"true" | "1" | "yes" => Ok(true),
"false" | "0" | "no" => Ok(false),
other => Err(format!("expected true/false/1/0/yes/no, got {:?}", other)),
}
}
}
macro_rules! impl_from_env_str_via_parse {
($($t:ty),+) => {
$(
impl FromEnvStr for $t {
fn from_env_str(s: &str) -> Result<Self, String> {
s.trim().parse::<$t>().map_err(|e| e.to_string())
}
}
)+
};
}
impl_from_env_str_via_parse!(u8, u16, u32, u64, u128, usize, i8, i16, i32, i64, i128, isize, f32, f64);
pub fn load_required<T: FromEnvStr>(key: &str) -> Result<T, String> {
let val = std::env::var(key)
.map_err(|_| format!("required env var `{}` is not set", key))?;
T::from_env_str(&val).map_err(|e| format!("`{}`: {}", key, e))
}
pub fn load_with_default<T: FromEnvStr>(key: &str, default: &str) -> Result<T, String> {
let val = std::env::var(key).unwrap_or_else(|_| default.to_string());
T::from_env_str(&val).map_err(|e| format!("`{}`: {}", key, e))
}
pub fn load_optional<T: FromEnvStr>(key: &str) -> Result<Option<T>, String> {
match std::env::var(key) {
Ok(val) if !val.trim().is_empty() => T::from_env_str(&val)
.map(Some)
.map_err(|e| format!("`{}`: {}", key, e)),
_ => Ok(None),
}
}