pub fn expand_env_var(raw: &str) -> String {
expand_env_var_with(raw, |name| std::env::var(name).ok())
}
fn expand_env_var_with(raw: &str, lookup: impl Fn(&str) -> Option<String>) -> String {
if raw.starts_with("${") && raw.ends_with('}') && raw.len() > 3 {
let var = &raw[2..raw.len() - 1];
if !var.is_empty() {
return lookup(var).unwrap_or_default();
}
}
raw.to_string()
}
#[cfg(test)]
mod tests {
use super::*;
fn only(name: &'static str, value: &'static str) -> impl Fn(&str) -> Option<String> {
move |n| (n == name).then(|| value.to_string())
}
fn nothing_set(_: &str) -> Option<String> {
None
}
#[test]
fn expand_env_var_passthrough() {
let boom = |n: &str| panic!("lookup should not run for {n}");
assert_eq!(
expand_env_var_with("ghp_actualtoken", boom),
"ghp_actualtoken"
);
assert_eq!(expand_env_var_with("", boom), "");
assert_eq!(
expand_env_var_with("no-special-chars", boom),
"no-special-chars"
);
}
#[test]
fn expand_env_var_placeholder() {
assert_eq!(
expand_env_var_with("${GITHUB_TOKEN}", only("GITHUB_TOKEN", "resolved-value")),
"resolved-value"
);
}
#[test]
fn expand_env_var_unset_var() {
assert_eq!(
expand_env_var_with("${GITHUB_TOKEN}", nothing_set),
"",
"unset var should expand to empty string, not the literal placeholder"
);
}
#[test]
fn expand_env_var_partial_placeholder() {
let boom = |n: &str| panic!("lookup should not run for {n}");
assert_eq!(expand_env_var_with("${NOCLOSE", boom), "${NOCLOSE");
assert_eq!(expand_env_var_with("VAR}", boom), "VAR}");
assert_eq!(expand_env_var_with("$VAR", boom), "$VAR");
assert_eq!(expand_env_var_with("${}", boom), "${}");
}
}