dynamo_runtime/config/
env_config.rs1use std::{env::VarError, str::FromStr};
7
8#[doc(hidden)]
10pub fn parse_or_default<T>(name: &str, default: T) -> T
11where
12 T: FromStr,
13 T::Err: std::fmt::Display,
14{
15 match std::env::var(name) {
16 Ok(value) => match value.parse() {
17 Ok(parsed) => parsed,
18 Err(error) => {
19 tracing::warn!(
20 env_var = name,
21 value,
22 %error,
23 "invalid environment variable, using default"
24 );
25 default
26 }
27 },
28 Err(VarError::NotPresent) => default,
29 Err(VarError::NotUnicode(value)) => {
30 tracing::warn!(
31 env_var = name,
32 ?value,
33 "environment variable is not valid Unicode, using default"
34 );
35 default
36 }
37 }
38}
39
40#[macro_export]
42macro_rules! env_config {
43 (
44 $(#[$attr:meta])*
45 $visibility:vis fn $name:ident() -> $value_type:ty =
46 $env_var:path, default = $default:expr;
47 ) => {
48 $(#[$attr])*
49 $visibility fn $name() -> $value_type {
50 static VALUE: ::std::sync::OnceLock<$value_type> = ::std::sync::OnceLock::new();
51 VALUE.get_or_init(|| {
52 $crate::config::env_config::parse_or_default($env_var, $default)
53 }).clone()
54 }
55 };
56}
57
58#[cfg(test)]
59mod tests {
60 const TEST_ENV: &str = "DYN_TEST_ENV_CONFIG_PARSE_OR_DEFAULT";
61 const INVALID_TEST_ENV: &str = "DYN_TEST_ENV_CONFIG_INVALID";
62
63 crate::env_config! {
64 fn configured_value() -> usize = TEST_ENV, default = 17;
65 }
66
67 crate::env_config! {
68 fn configured_string() -> String = TEST_ENV, default = "fallback".to_string();
69 }
70
71 #[test]
72 fn uses_parsed_environment_value() {
73 temp_env::with_var(TEST_ENV, Some("42"), || {
74 assert_eq!(configured_value(), 42);
75 });
76 }
77
78 #[test]
79 fn invalid_value_uses_default() {
80 temp_env::with_var(INVALID_TEST_ENV, Some("invalid"), || {
81 assert_eq!(super::parse_or_default::<usize>(INVALID_TEST_ENV, 17), 17);
82 });
83 }
84
85 #[test]
86 fn supports_non_copy_values() {
87 temp_env::with_var(TEST_ENV, Some("configured"), || {
88 assert_eq!(configured_string(), "configured");
89 });
90 }
91}