Skip to main content

doido_controller/
env_override.rs

1//! Environment-variable config overrides (spec 05 `SECTION__KEY`).
2//!
3//! An env var named `SECTION__KEY` (double underscore) overrides
4//! `config[section][key]`, so `SERVER__PORT=4000` sets `server.port`. Values are
5//! coerced to bool/number when they parse, else kept as strings.
6
7use serde_json::Value;
8
9/// Apply `SECTION__KEY=value` overrides (as `(name, value)` pairs) onto `config`.
10pub fn apply_env_overrides(config: &mut Value, vars: &[(String, String)]) {
11    let Value::Object(root) = config else { return };
12    for (name, raw) in vars {
13        let Some((section, key)) = name.split_once("__") else {
14            continue;
15        };
16        let section = section.to_lowercase();
17        let key = key.to_lowercase();
18        let entry = root
19            .entry(section)
20            .or_insert_with(|| Value::Object(Default::default()));
21        if let Value::Object(map) = entry {
22            map.insert(key, coerce(raw));
23        }
24    }
25}
26
27/// Read `SECTION__KEY` overrides from the process environment.
28pub fn from_process_env(config: &mut Value) {
29    let vars: Vec<(String, String)> = std::env::vars().filter(|(k, _)| k.contains("__")).collect();
30    apply_env_overrides(config, &vars);
31}
32
33fn coerce(raw: &str) -> Value {
34    if let Ok(b) = raw.parse::<bool>() {
35        return Value::Bool(b);
36    }
37    if let Ok(i) = raw.parse::<i64>() {
38        return Value::from(i);
39    }
40    if let Ok(f) = raw.parse::<f64>() {
41        return Value::from(f);
42    }
43    Value::String(raw.to_string())
44}