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}