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///     JWT_SECRET: String = "secret",
14///     WORKER_COUNT: i32 = 32,
15/// }
16///
17/// assert_eq!(config::JWT_SECRET.as_str(), "secret");
18/// assert_eq!(config::WORKER_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 super::*;
36
37            #[inline]
38            fn get_variable_type<T>(_: &T) -> &'static str {
39                std::any::type_name::<T>().split("::").last().unwrap()
40            }
41
42            #[inline]
43            fn variable_exists(variable_name: &str) -> bool {
44                std::env::var(variable_name).is_ok() || !std::env::var(format!("_{variable_name}_WAS_MISSING"))
45                        .unwrap_or("false".to_string())
46                        .eq("true")
47            }
48
49            #[inline]
50            fn warn_if_env_var_is_missing(variable_name: &str, default_value: impl std::fmt::Display) -> bool {
51                let variable_exists = variable_exists(variable_name);
52                if !variable_exists {
53                    $crate::log::warn!(
54                        "Variable `{variable_name}` is not set! Using default value `{default_value}`",
55                    );
56                }
57                variable_exists
58            }
59
60            #[inline]
61            fn get_variable_value<T: std::str::FromStr>(variable_name: &str, default_value: impl Into<T> + Clone + std::fmt::Display) -> T {
62                let value = if let Ok(value) = std::env::var(variable_name) {
63                    let value = value.parse::<T>().unwrap_or_else(|_| panic!(
64                        "Invalid value type for the variable `{variable_name}`! Expected type `{}`, got `{}`.",
65                        stringify!(T),
66                        get_variable_type(&value)
67                    ));
68                    value
69                } else {
70                    unsafe {
71                        std::env::set_var(format!("_{variable_name}_WAS_MISSING"), "true");
72                    }
73                    default_value.clone().into()
74                };
75
76                warn_if_env_var_is_missing(variable_name, default_value);
77
78                value
79            }
80
81            $(
82                /// Our environment variable. Lazy-evaluated by default
83                pub static $name: std::sync::LazyLock<$type> = std::sync::LazyLock::new(|| {
84                    get_variable_value(stringify!($name), $default_value)
85                });
86            )*
87
88            /// Inits all environment variables
89            pub fn init() {
90                $(
91                    std::sync::LazyLock::force(&$name);
92                )*
93            }
94
95            /// Checks whether all variables are set or not
96            pub fn check_values() -> bool {
97                vec![$(
98                    warn_if_env_var_is_missing(stringify!($name), $default_value)
99                ),*]
100                    .into_iter()
101                    .all(|elem| elem)
102            }
103
104            /// Updates the environment with variable values
105            ///
106            /// Uses `set_env_only` under the hood
107            pub fn set_env() {
108                unsafe {
109                    $(
110                        $crate::set_env_only!($name);
111                    )*
112                }
113            }
114        }
115    };
116}
117
118/// Inits config value and sets it in the runtime environment
119/// Note: uses `std::env::set_var` under the hood.
120/// # Examples
121/// ```
122/// use env_vars_config::{env_vars_config, set_env_only};
123///
124/// env_vars_config! {
125///     OTEL_SERVICE_NAME: String = "test-service",
126/// }
127///
128/// assert_eq!(config::OTEL_SERVICE_NAME.as_str(), "test-service");
129///
130/// unsafe {
131///     use config::OTEL_SERVICE_NAME;
132///     set_env_only!(OTEL_SERVICE_NAME);
133/// }
134///
135/// assert_eq!(std::env::var("OTEL_SERVICE_NAME").unwrap().as_str(), "test-service");
136/// ```
137#[macro_export]
138macro_rules! set_env_only {
139    ($($name:ident),*) => {
140        $(
141            std::env::set_var(stringify!($name), $name.to_string());
142        )*
143    };
144}