Skip to main content

vtcode_config/
env_helpers.rs

1//! Shared environment variable and serde-default helpers.
2//!
3//! Consolidates the `read_env_var` + test-override pattern and the trivial
4//! `default_true` / `default_enabled` serde default functions that were
5//! previously duplicated across the crate.
6
7/// Read an environment variable, honoring the test-only override map.
8pub fn read_env_var(name: &str) -> Option<String> {
9    #[cfg(test)]
10    if let Some(override_value) = test_env_overrides::get(name) {
11        return override_value;
12    }
13    std::env::var(name).ok()
14}
15
16/// Parse a boolean environment variable, returning `default` when unset or
17/// unrecognized. Accepted truthy values: `1`, `true`, `yes`, `on`. Accepted
18/// falsy values: `0`, `false`, `no`, `off`. Matching is case-insensitive after
19/// trimming whitespace.
20pub fn parse_env_bool(name: &str, default: bool) -> bool {
21    read_env_var(name)
22        .and_then(|value| {
23            let normalized = value.trim().to_ascii_lowercase();
24            match normalized.as_str() {
25                "1" | "true" | "yes" | "on" => Some(true),
26                "0" | "false" | "no" | "off" => Some(false),
27                _ => None,
28            }
29        })
30        .unwrap_or(default)
31}
32
33/// Serde default returning `true`. Use with `#[serde(default = "default_true")]`.
34pub const fn default_true() -> bool {
35    true
36}
37
38/// Serde default returning `true`. Alias of [`default_true`] for use with
39/// `#[serde(default = "default_enabled")]`.
40pub const fn default_enabled() -> bool {
41    true
42}
43
44#[cfg(test)]
45pub(crate) mod test_env_overrides {
46    use std::collections::HashMap;
47    use std::sync::{Mutex, OnceLock};
48
49    static ENV_OVERRIDES: OnceLock<Mutex<HashMap<String, Option<String>>>> = OnceLock::new();
50
51    fn overrides() -> &'static Mutex<HashMap<String, Option<String>>> {
52        ENV_OVERRIDES.get_or_init(|| Mutex::new(HashMap::new()))
53    }
54
55    pub fn get(name: &str) -> Option<Option<String>> {
56        overrides()
57            .lock()
58            .expect("env overrides lock poisoned")
59            .get(name)
60            .cloned()
61    }
62
63    pub fn set(name: &str, value: Option<&str>) {
64        overrides()
65            .lock()
66            .expect("env overrides lock poisoned")
67            .insert(name.to_string(), value.map(ToOwned::to_owned));
68    }
69
70    pub fn restore(name: &str, previous: Option<Option<String>>) {
71        let mut guard = overrides().lock().expect("env overrides lock poisoned");
72        match previous {
73            Some(value) => {
74                guard.insert(name.to_string(), value);
75            }
76            None => {
77                guard.remove(name);
78            }
79        }
80    }
81}