#[macro_export]
macro_rules! env_struct {
(
$(#[$outer:meta])*
$vis:vis struct $struct_name:ident {
$(
$(#[$outer_field:meta])*
$vis_ident:vis $field:ident = $fieldDef:expr,
)*
}
) => {
$(#[$outer])*
$vis struct $struct_name {
$(
$(#[$outer_field])*
$vis_ident $field: String,
)*
}
impl $struct_name {
pub fn load_from_env() -> Self {
let mut env = Self::default();
$(
let _field = stringify!($field)
.chars()
.map(|x| char::to_ascii_uppercase(&x))
.collect::<String>();
if let Ok(s) = std::env::var(&_field) {
env.$field = s;
} else {
$crate::log()
}
)*
env
}
}
impl Default for $struct_name {
fn default() -> Self {
Self {
$(
$field: $fieldDef,
)*
}
}
}
};
(
$(#[$outer:meta])*
$vis:vis struct $struct_name:ident {
$(
$(#[$outer_field:meta])*
$vis_ident:vis $field:ident,
)*
}
) => {
$(#[$outer])*
$vis struct $struct_name {
$(
$(#[$outer_field])*
$vis_ident $field: String,
)*
}
impl $struct_name {
pub fn try_load_from_env() -> Result<Self, String> {
Ok(Self {
$(
$field: std::env::var(
stringify!($field)
.chars()
.map(|x| char::to_ascii_uppercase(&x))
.collect::<String>(),
).map_err(|_| {
format!(
"Environment Variable `{}` Not Present!",
stringify!($field)
.chars()
.map(|x| char::to_ascii_uppercase(&x))
.collect::<String>()
)
})?,
)*
})
}
}
};
}
#[cfg(not(feature = "logging"))]
pub fn log() {}
#[cfg(feature = "logging")]
pub fn log(def: String) {
use log;
log::warn!(
"Failed to find `{}` in env, defaulting to {:?}",
_field,
def
);
}
#[cfg(test)]
mod tests {
struct EnvTemp {
flag: &'static str,
original_content: Option<String>,
}
impl EnvTemp {
fn set_var(flag: &'static str, val: &'static str) -> EnvTemp {
let env = EnvTemp {
flag,
original_content: std::env::var(flag).ok(),
};
std::env::set_var(flag, val);
env
}
}
impl Drop for EnvTemp {
fn drop(&mut self) {
if let Some(og) = &self.original_content {
std::env::set_var(self.flag, og);
} else {
std::env::remove_var(self.flag);
}
}
}
#[test]
fn test_with_default() {
let hello_world = "Hello, world!";
let temp_env = [EnvTemp::set_var("HELLO_NOT_MY_WORLD", hello_world)];
env_struct! {
struct Env {
hello_not_my_world = "hello".into(),
hello_some_world = "Hello, Some World!".into(),
}
}
let env = Env::load_from_env();
assert_eq!(env.hello_not_my_world, hello_world);
assert_eq!(env.hello_some_world, "Hello, Some World!");
drop(temp_env); }
#[test]
fn test_no_defaults_succeed() {
let hello_sam = "Hello, Sam!";
let welp_sam = "Welp, Sam!";
let temp_env = [
EnvTemp::set_var("HELLO_WORLD", hello_sam),
EnvTemp::set_var("WELP_MY_WORLD", welp_sam),
];
env_struct! {
struct Env2 {
hello_world,
welp_my_world,
}
}
let env = Env2::try_load_from_env().unwrap();
assert_eq!(env.hello_world, hello_sam);
assert_eq!(env.welp_my_world, welp_sam);
drop(temp_env); }
#[test]
#[should_panic]
fn test_no_defaults_failed() {
let welp_sam = "Welp, Sam!";
let temp_env = [EnvTemp::set_var("HELL_TO_WORLD", welp_sam)];
env_struct! {
struct Env {
hell_to_world,
welp_world,
}
}
let env = Env::try_load_from_env().unwrap();
_ = env.hell_to_world;
_ = env.welp_world;
drop(temp_env); }
}