use crate::error::{ConfigError, ConfigErrorKind};
pub(super) fn interpolate_document(document: &mut toml::Table) -> Result<(), ConfigError> {
if let Some(server) = document
.get_mut("server")
.and_then(toml::Value::as_table_mut)
&& let Some(toml::Value::String(token)) = server.get("token")
&& interpolate(token).is_err_and(|e| e.kind() == ConfigErrorKind::UnresolvedVar)
{
server.remove("token");
}
interpolate_table(document)
}
fn interpolate_table(table: &mut toml::Table) -> Result<(), ConfigError> {
for (_, value) in table.iter_mut() {
interpolate_value(value)?;
}
Ok(())
}
fn interpolate_value(value: &mut toml::Value) -> Result<(), ConfigError> {
match value {
toml::Value::String(text) => *text = interpolate(text)?,
toml::Value::Array(items) => {
for item in items {
interpolate_value(item)?;
}
}
toml::Value::Table(table) => interpolate_table(table)?,
_ => {}
}
Ok(())
}
fn interpolate(input: &str) -> Result<String, ConfigError> {
interpolate_with(input, &|name| std::env::var(name).ok())
}
pub(super) fn interpolate_with(
input: &str,
lookup: &dyn Fn(&str) -> Option<String>,
) -> Result<String, ConfigError> {
let mut out = String::with_capacity(input.len());
let mut chars = input.chars().peekable();
while let Some(c) = chars.next() {
if c != '$' {
out.push(c);
continue;
}
match chars.peek() {
Some('$') => {
chars.next();
out.push('$');
}
Some('{') => {
chars.next();
let mut name = String::new();
let mut closed = false;
for nc in chars.by_ref() {
if nc == '}' {
closed = true;
break;
}
name.push(nc);
}
if !closed {
return Err(ConfigError::interpolation("unclosed ${...} interpolation"));
}
let value = lookup(&name).ok_or_else(|| ConfigError::unresolved_var(name))?;
out.push_str(&value);
}
_ => out.push('$'),
}
}
Ok(out)
}