datasynth_config/
env_interpolation.rs1use regex::Regex;
8use std::env;
9use thiserror::Error;
10
11#[derive(Debug, Error)]
12pub enum EnvInterpolationError {
13 #[error("Environment variable '{0}' is not set and no default provided")]
14 MissingVariable(String),
15}
16
17pub fn interpolate_env(input: &str) -> Result<String, EnvInterpolationError> {
37 let re = Regex::new(r"\$\{([^}]+)\}").expect("valid env interpolation regex");
38 let mut result = input.to_string();
39 let mut errors = Vec::new();
40
41 let matches: Vec<(String, String)> = re
43 .captures_iter(input)
44 .map(|cap| {
45 let full_match = cap
46 .get(0)
47 .expect("capture group 0 always exists")
48 .as_str()
49 .to_string();
50 let inner = cap
51 .get(1)
52 .expect("capture group 1 defined in regex")
53 .as_str()
54 .to_string();
55 (full_match, inner)
56 })
57 .collect();
58
59 for (full_match, inner) in matches {
60 let replacement = if let Some((var_name, default_value)) = inner.split_once(":-") {
61 match env::var(var_name) {
63 Ok(val) => val,
64 Err(_) => default_value.to_string(),
65 }
66 } else {
67 match env::var(&inner) {
69 Ok(val) => val,
70 Err(_) => {
71 errors.push(inner.clone());
72 continue;
73 }
74 }
75 };
76
77 result = result.replace(&full_match, &replacement);
78 }
79
80 if let Some(first_error) = errors.into_iter().next() {
81 return Err(EnvInterpolationError::MissingVariable(first_error));
82 }
83
84 Ok(result)
85}
86
87#[cfg(test)]
88mod tests {
89 use super::*;
90
91 #[test]
92 fn test_basic_substitution() {
93 env::set_var("TEST_INTERP_VAR", "hello");
94 let result = interpolate_env("value: ${TEST_INTERP_VAR}").unwrap();
95 assert_eq!(result, "value: hello");
96 env::remove_var("TEST_INTERP_VAR");
97 }
98
99 #[test]
100 fn test_default_value() {
101 env::remove_var("TEST_INTERP_MISSING");
102 let result = interpolate_env("value: ${TEST_INTERP_MISSING:-fallback}").unwrap();
103 assert_eq!(result, "value: fallback");
104 }
105
106 #[test]
107 fn test_default_with_existing_var() {
108 env::set_var("TEST_INTERP_EXISTS", "real_value");
109 let result = interpolate_env("value: ${TEST_INTERP_EXISTS:-fallback}").unwrap();
110 assert_eq!(result, "value: real_value");
111 env::remove_var("TEST_INTERP_EXISTS");
112 }
113
114 #[test]
115 fn test_missing_required_variable() {
116 env::remove_var("TEST_INTERP_REQUIRED");
117 let result = interpolate_env("value: ${TEST_INTERP_REQUIRED}");
118 assert!(result.is_err());
119 }
120
121 #[test]
122 fn test_no_interpolation_needed() {
123 let result = interpolate_env("plain text without variables").unwrap();
124 assert_eq!(result, "plain text without variables");
125 }
126
127 #[test]
128 fn test_multiple_variables() {
129 env::set_var("TEST_INTERP_A", "alpha");
130 env::set_var("TEST_INTERP_B", "beta");
131 let result = interpolate_env("${TEST_INTERP_A} and ${TEST_INTERP_B}").unwrap();
132 assert_eq!(result, "alpha and beta");
133 env::remove_var("TEST_INTERP_A");
134 env::remove_var("TEST_INTERP_B");
135 }
136
137 #[test]
138 fn test_empty_default() {
139 env::remove_var("TEST_INTERP_EMPTY_DEFAULT");
140 let result = interpolate_env("value: ${TEST_INTERP_EMPTY_DEFAULT:-}").unwrap();
141 assert_eq!(result, "value: ");
142 }
143}