Skip to main content

configbuilder_pattern_substitution/
env_value_resolver.rs

1use crate::{SubstitutionError, ValueResolver};
2
3/// Default [`ValueResolver`] — resolves variables from the process
4/// environment via `std::env::var`.
5#[derive(Debug, Default, Clone, Copy)]
6pub struct EnvValueResolver;
7
8impl ValueResolver for EnvValueResolver {
9    fn resolve(&self, var_name: &str, location: &str) -> Result<String, SubstitutionError> {
10        std::env::var(var_name).map_err(|_| SubstitutionError::VariableNotFound {
11            var_name: var_name.to_string(),
12            location: location.to_string(),
13        })
14    }
15}
16
17#[cfg(test)]
18#[allow(unsafe_code)]
19mod tests {
20    use super::*;
21
22    fn must<T, E>(result: Result<T, E>) -> T {
23        result.unwrap_or_else(|_| std::process::abort())
24    }
25
26    /// @covers: resolve
27    #[test]
28    fn test_resolve_existing_env_var_returns_value() {
29        // SAFETY: test-only; no concurrent env access in this test binary at this point.
30        unsafe { std::env::set_var("CONFIGBUILDER_TEST_RESOLVE_VAR", "value123") };
31        let result = EnvValueResolver.resolve("CONFIGBUILDER_TEST_RESOLVE_VAR", "loc");
32        // SAFETY: cleanup
33        unsafe { std::env::remove_var("CONFIGBUILDER_TEST_RESOLVE_VAR") };
34        assert_eq!(must(result), "value123");
35    }
36
37    /// @covers: resolve
38    #[test]
39    fn test_resolve_missing_env_var_returns_variable_not_found_with_location() {
40        let result = EnvValueResolver.resolve("CONFIGBUILDER_TEST_ABSENT_XYZ", "app.toml:key");
41        assert_eq!(
42            result,
43            Err(SubstitutionError::VariableNotFound {
44                var_name: "CONFIGBUILDER_TEST_ABSENT_XYZ".to_string(),
45                location: "app.toml:key".to_string(),
46            })
47        );
48    }
49}