env_struct/
lib.rs

1//! Environment variable struct for better env management.
2//!
3//! Currently it's very opinionated and depends on having
4//! `std` as it's fields defaults to `String` type, and
5//! uses `std::env::var(<key>)`.
6//!
7//! You are also forced to specify a default-value for the
8//! `ENV_VARIABLE` because IMHO that's important.
9//!
10//! Also lastly we just capitalize the field name for
11//! env_variable name so make sure to set those up correctly.
12//!
13//! Key roadmap goal is,
14//! Support custom aliases for env_var key.
15//!
16//! Note we don't support boolean or enum based env
17//! variables yet, I hope to shortly but I don't really
18//! need that so haven't thought about it much yet!
19//!
20//! ### Usage
21//! ```rust
22//! use env_struct::env_struct;
23//! env_struct!{
24//!     #[derive(Debug)]
25//!     pub struct DummyEnv {
26//!         pub path_to_something = "/path_to_something".into(),
27//!         pub config_path = "/folder/config_path.toml".into(),
28//!     }
29//! };
30//! ```
31
32/// Macro for writing a `env_struct`
33#[macro_export]
34macro_rules! env_struct {
35    (
36        $(#[$outer:meta])*
37        $vis:vis struct $struct_name:ident {
38            $(
39                $(#[$outer_field:meta])*
40                $vis_ident:vis $field:ident = $fieldDef:expr,
41            )*
42        }
43    ) => {
44        $(#[$outer])*
45        $vis struct $struct_name {
46            $(
47                $(#[$outer_field])*
48                $vis_ident $field: String,
49            )*
50        }
51        impl $struct_name {
52            pub fn load_from_env() -> Self {
53                let mut env = Self::default();
54                $(
55                    let _field = stringify!($field)
56                            .chars()
57                            .map(|x| char::to_ascii_uppercase(&x))
58                            .collect::<String>();
59                    if let Ok(s) = std::env::var(&_field) {
60                        env.$field = s;
61                    } else {
62                        $crate::log()
63                    }
64                )*
65                env
66            }
67        }
68        impl Default for $struct_name {
69            fn default() -> Self {
70                Self {
71                    $(
72                        $field: $fieldDef,
73                    )*
74                }
75            }
76        }
77    };
78    (
79        $(#[$outer:meta])*
80        $vis:vis struct $struct_name:ident {
81            $(
82                $(#[$outer_field:meta])*
83                $vis_ident:vis $field:ident,
84            )*
85        }
86    ) => {
87        $(#[$outer])*
88        $vis struct $struct_name {
89            $(
90                $(#[$outer_field])*
91                $vis_ident $field: String,
92            )*
93        }
94        impl $struct_name {
95            pub fn try_load_from_env() -> Result<Self, String> {
96                Ok(Self {
97                    $(
98                        $field: std::env::var(
99                            stringify!($field)
100                                .chars()
101                                .map(|x| char::to_ascii_uppercase(&x))
102                                .collect::<String>(),
103                        ).map_err(|_| {
104                            format!(
105                                "Environment Variable `{}` Not Present!",
106                                stringify!($field)
107                                    .chars()
108                                    .map(|x| char::to_ascii_uppercase(&x))
109                                    .collect::<String>()
110                            )
111                        })?,
112                    )*
113                })
114            }
115        }
116    };
117}
118
119#[cfg(not(feature = "logging"))]
120pub fn log() {}
121
122#[cfg(feature = "logging")]
123pub fn log(def: String) {
124    use log;
125    log::warn!(
126        "Failed to find `{}` in env, defaulting to {:?}",
127        _field,
128        def
129    );
130}
131
132#[cfg(test)]
133mod tests {
134
135    struct EnvTemp {
136        flag: &'static str,
137        original_content: Option<String>,
138    }
139    impl EnvTemp {
140        fn set_var(flag: &'static str, val: &'static str) -> EnvTemp {
141            let env = EnvTemp {
142                flag,
143                original_content: std::env::var(flag).ok(),
144            };
145            std::env::set_var(flag, val);
146            env
147        }
148    }
149    impl Drop for EnvTemp {
150        fn drop(&mut self) {
151            // reset_var
152            if let Some(og) = &self.original_content {
153                std::env::set_var(self.flag, og);
154            } else {
155                std::env::remove_var(self.flag);
156            }
157        }
158    }
159
160    #[test]
161    fn test_with_default() {
162        let hello_world = "Hello, world!";
163        let temp_env = [EnvTemp::set_var("HELLO_NOT_MY_WORLD", hello_world)];
164        env_struct! {
165            /// Env Items
166            struct Env {
167                /// Hello World
168                hello_not_my_world = "hello".into(),
169                hello_some_world = "Hello, Some World!".into(),
170            }
171        }
172        let env = Env::load_from_env();
173        assert_eq!(env.hello_not_my_world, hello_world);
174        assert_eq!(env.hello_some_world, "Hello, Some World!");
175        drop(temp_env); // drop would be called without this as well
176    }
177
178    #[test]
179    fn test_no_defaults_succeed() {
180        let hello_sam = "Hello, Sam!";
181        let welp_sam = "Welp, Sam!";
182        let temp_env = [
183            EnvTemp::set_var("HELLO_WORLD", hello_sam),
184            EnvTemp::set_var("WELP_MY_WORLD", welp_sam),
185        ];
186        env_struct! {
187            /// Env Items
188            struct Env2 {
189                /// Hello World
190                hello_world,
191                welp_my_world,
192            }
193        }
194        let env = Env2::try_load_from_env().unwrap();
195        assert_eq!(env.hello_world, hello_sam);
196        assert_eq!(env.welp_my_world, welp_sam);
197        drop(temp_env); // drop would be called without this as well
198    }
199
200    #[test]
201    #[should_panic]
202    fn test_no_defaults_failed() {
203        let welp_sam = "Welp, Sam!";
204        let temp_env = [EnvTemp::set_var("HELL_TO_WORLD", welp_sam)];
205        env_struct! {
206            struct Env {
207                hell_to_world,
208                welp_world,
209            }
210        }
211        let env = Env::try_load_from_env().unwrap();
212        _ = env.hell_to_world;
213        _ = env.welp_world;
214        drop(temp_env); // drop would be called without this as well
215    }
216}