use regex::Regex;
use std::sync::OnceLock;
use super::{ConfigError, ConfigReader, ConfigResult};
static VARIABLE_PATTERN: OnceLock<Regex> = OnceLock::new();
fn get_variable_pattern() -> &'static Regex {
VARIABLE_PATTERN.get_or_init(|| {
Regex::new(r"\$\{([^}]+)\}").expect("Failed to compile variable pattern regex")
})
}
pub fn substitute_variables<R: ConfigReader + ?Sized>(
value: &str,
config: &R,
max_depth: usize,
) -> ConfigResult<String> {
if value.is_empty() {
return Ok(value.to_string());
}
let pattern = get_variable_pattern();
let mut result = value.to_string();
let mut depth = 0;
loop {
let replacements: Vec<(String, String)> = pattern
.captures_iter(&result)
.map(|cap| {
let full_match = cap.get(0).unwrap().as_str().to_string();
let var_name = cap.get(1).unwrap().as_str();
let var_value = find_variable_value(var_name, config)?;
Ok((full_match, var_value))
})
.collect::<ConfigResult<Vec<_>>>()?;
if replacements.is_empty() {
break;
}
if depth >= max_depth {
return Err(ConfigError::SubstitutionDepthExceeded(max_depth));
}
for (full_match, var_value) in replacements {
result = result.replace(&full_match, &var_value);
}
depth += 1;
}
Ok(result)
}
fn find_variable_value<R: ConfigReader + ?Sized>(
var_name: &str,
config: &R,
) -> ConfigResult<String> {
if let Ok(value) = config.get::<String>(var_name) {
return Ok(value);
}
if let Ok(value) = std::env::var(var_name) {
return Ok(value);
}
Err(ConfigError::SubstitutionError(format!(
"Cannot resolve variable: {}",
var_name
)))
}