env_vars_config/
lib.rs

1//! A simple lib for configuring your applications via environment variables
2
3#[doc(hidden)]
4pub extern crate log;
5
6/// Creates new mod with specified environment variables
7/// # Examples
8///
9/// ```
10/// use env_vars_config::env_vars_config;
11///
12/// env_vars_config! {
13///     SERVER_ADDRESS: String = "0.0.0.0:8080",
14///     WORKERS_COUNT: i32 = 32,
15/// }
16///
17/// assert_eq!(config::SERVER_ADDRESS.as_str(), "0.0.0.0:8080");
18/// assert_eq!(config::WORKERS_COUNT.clone(), 32);
19/// ```
20///
21/// # Panics
22///
23/// ```no_compile
24/// use env_vars_config::env_vars_config;
25///
26/// env_vars_config! {
27///     // incompatible type
28///     ENABLE_REGISTRATION: bool = "false",
29/// }
30/// ```
31#[macro_export]
32macro_rules! env_vars_config {
33    ( $( $name:ident: $type:tt = $default_value:expr ),* $(,)? ) => {
34        pub mod config {
35            use std::{
36                any::type_name,
37                env,
38                fmt::Display,
39                str::FromStr,
40                sync::LazyLock,
41            };
42
43            #[inline]
44            fn get_variable_type<T>(_: &T) -> &'static str {
45                type_name::<T>().split("::").last().unwrap()
46            }
47
48            #[inline]
49            fn missing_in_env_warn(variable_name: &str, default_value: impl Display) {
50                $crate::log::warn!(
51                    "Variable `{variable_name}` is missing in the env! Using default value `{default_value}`",
52                );
53            }
54
55            #[inline]
56            fn get_variable_value<T: FromStr>(variable_name: &str, default_value: impl Into<T> + Clone + Display) -> T {
57                let (value, is_missing) = if let Ok(value) = env::var(variable_name) {
58                    let value = value.parse::<T>().unwrap_or_else(|_| panic!(
59                        "Invalid value type for the variable `{variable_name}`! Expected type `{}`, got `{}`.",
60                        stringify!(T),
61                        get_variable_type(&value)
62                    ));
63                    let is_missing = env::var(format!("_{variable_name}_WAS_SET"))
64                        .unwrap_or("false".to_string())
65                        .eq("true");
66                    (value, is_missing)
67                } else {
68                    (default_value.clone().into(), true)
69                };
70
71                if is_missing {
72                    missing_in_env_warn(&variable_name, &default_value);
73                }
74
75                value
76            }
77
78            $(
79                /// Our environment variable. Lazy-evaluated by default
80                pub static $name: LazyLock<$type> = LazyLock::new(|| {
81                    get_variable_value(stringify!($name), $default_value)
82                });
83            )*
84
85            /// Inits all environment variables
86            pub fn init() {
87                $(
88                    LazyLock::force(&$name);
89                )*
90            }
91
92            /// Tries to get every variable value. Usable when you need
93            pub fn test_values() {
94                $(
95                    get_variable_value::<$type>(stringify!($name), $default_value);
96                )*
97            }
98
99            /// Updates the environment with variable values
100            ///
101            /// Does `set_env_only` under the hood
102            pub fn set_env() {
103                $(
104                    unsafe {
105                        $crate::set_env_only!($name);
106                    }
107                )*
108            }
109        }
110    };
111}
112
113/// Inits config value and sets it in the runtime environment
114/// Note: uses `std::env::set_var` under the hood.
115/// # Examples
116/// ```
117/// use env_vars_config::{env_vars_config, set_env_only};
118/// use std::env;
119///
120/// env_vars_config! {
121///     OTEL_SERVICE_NAME: String = "test-service",
122/// }
123///
124/// assert_eq!(config::OTEL_SERVICE_NAME.as_str(), "test-service");
125///
126/// unsafe {
127///     use config::OTEL_SERVICE_NAME;
128///     set_env_only!(OTEL_SERVICE_NAME);
129/// }
130///
131/// assert_eq!(env::var("OTEL_SERVICE_NAME").unwrap().as_str(), "test-service");
132/// ```
133#[macro_export]
134macro_rules! set_env_only {
135    ($name:ident) => {
136        env::set_var(stringify!($name), $name.to_string());
137        env::set_var(format!("_{}_WAS_SET", stringify!($name)), "true");
138    };
139}