Skip to main content

zerv/
config.rs

1use std::env;
2
3/// Centralized environment variable names used throughout Zerv.
4/// Following uv's pattern for maintainability and documentation.
5pub struct EnvVars;
6
7impl EnvVars {
8    /// Control logging verbosity (standard Rust ecosystem convention).
9    ///
10    /// Examples:
11    /// * `RUST_LOG=zerv=debug` - Debug logs for Zerv
12    /// * `RUST_LOG=trace` - Trace-level logging
13    /// * `RUST_LOG=zerv::vcs=debug` - Module-specific logging
14    ///
15    /// See [tracing documentation](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html)
16    pub const RUST_LOG: &'static str = "RUST_LOG";
17
18    /// Force logging off for CI environments (Ubuntu CI).
19    ///
20    /// When set to any value, all logging is disabled regardless of RUST_LOG setting.
21    /// Used specifically to prevent debug log spam in Ubuntu CI.
22    pub const ZERV_FORCE_RUST_LOG_OFF: &'static str = "ZERV_FORCE_RUST_LOG_OFF";
23
24    /// Use native Git instead of Docker Git for tests (default: false).
25    ///
26    /// Set to `true` or `1` to enable native Git in test environments.
27    pub const ZERV_TEST_NATIVE_GIT: &'static str = "ZERV_TEST_NATIVE_GIT";
28
29    /// Enable Docker-dependent tests (default: true).
30    ///
31    /// Set to `false` or `0` to skip Docker tests on systems without Docker.
32    pub const ZERV_TEST_DOCKER: &'static str = "ZERV_TEST_DOCKER";
33
34    /// Preferred pager program for displaying manual pages.
35    ///
36    /// Examples:
37    /// * `PAGER=less` - Use less pager
38    /// * `PAGER=more` - Use more pager
39    /// * `PAGER=most` - Use most pager
40    ///
41    /// If not set, Zerv will fall back to searching for common pagers (less, more, most).
42    pub const PAGER: &'static str = "PAGER";
43}
44
45#[derive(Debug, Clone, Default)]
46pub struct ZervConfig {
47    pub test_native_git: bool,
48    pub test_docker: bool,
49    pub force_rust_log_off: bool,
50}
51
52impl ZervConfig {
53    pub fn load() -> Result<Self, Box<dyn std::error::Error>> {
54        let test_native_git = Self::parse_bool_env(EnvVars::ZERV_TEST_NATIVE_GIT, false)?;
55        let test_docker = Self::parse_bool_env(EnvVars::ZERV_TEST_DOCKER, true)?;
56        let force_rust_log_off = Self::parse_bool_env(EnvVars::ZERV_FORCE_RUST_LOG_OFF, false)?;
57
58        Ok(ZervConfig {
59            test_native_git,
60            test_docker,
61            force_rust_log_off,
62        })
63    }
64
65    pub fn parse_bool_env(
66        var_name: &str,
67        default: bool,
68    ) -> Result<bool, Box<dyn std::error::Error>> {
69        match env::var(var_name) {
70            Ok(val) => Ok(val == "true" || val == "1"),
71            Err(_) => Ok(default),
72        }
73    }
74
75    pub fn should_use_native_git(&self) -> bool {
76        self.test_native_git
77    }
78
79    pub fn should_run_docker_tests(&self) -> bool {
80        self.test_docker
81    }
82
83    pub fn should_force_rust_log_off(&self) -> bool {
84        self.force_rust_log_off
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use std::env;
91
92    use serial_test::serial;
93
94    use super::*;
95
96    struct EnvGuard {
97        vars: Vec<(String, Option<String>)>,
98    }
99
100    impl EnvGuard {
101        fn new(var_names: &[&str]) -> Self {
102            let vars = var_names
103                .iter()
104                .map(|&name| (name.to_string(), env::var(name).ok()))
105                .collect();
106            Self { vars }
107        }
108    }
109
110    impl Drop for EnvGuard {
111        fn drop(&mut self) {
112            for (name, value) in &self.vars {
113                unsafe {
114                    match value {
115                        Some(val) => env::set_var(name, val),
116                        None => env::remove_var(name),
117                    }
118                }
119            }
120        }
121    }
122
123    #[test]
124    #[serial]
125    fn test_default_config() {
126        let _guard = EnvGuard::new(&[
127            EnvVars::ZERV_TEST_NATIVE_GIT,
128            EnvVars::ZERV_TEST_DOCKER,
129            EnvVars::ZERV_FORCE_RUST_LOG_OFF,
130        ]);
131        unsafe {
132            env::remove_var(EnvVars::ZERV_TEST_NATIVE_GIT);
133            env::remove_var(EnvVars::ZERV_TEST_DOCKER);
134            env::remove_var(EnvVars::ZERV_FORCE_RUST_LOG_OFF);
135        }
136
137        let config = ZervConfig::load().expect("Failed to load default config");
138        assert!(!config.should_use_native_git());
139        assert!(config.should_run_docker_tests());
140        assert!(!config.should_force_rust_log_off());
141    }
142
143    #[test]
144    #[serial]
145    fn test_native_git_env_var() {
146        let _guard = EnvGuard::new(&[
147            EnvVars::ZERV_TEST_NATIVE_GIT,
148            EnvVars::ZERV_TEST_DOCKER,
149            EnvVars::ZERV_FORCE_RUST_LOG_OFF,
150        ]);
151        unsafe {
152            env::remove_var(EnvVars::ZERV_TEST_DOCKER);
153            env::remove_var(EnvVars::ZERV_FORCE_RUST_LOG_OFF);
154            env::set_var(EnvVars::ZERV_TEST_NATIVE_GIT, "true");
155        }
156
157        let config = ZervConfig::load().expect("Failed to load config with native git enabled");
158        assert!(config.should_use_native_git());
159        assert!(config.should_run_docker_tests());
160        assert!(!config.should_force_rust_log_off());
161    }
162
163    #[test]
164    #[serial]
165    fn test_docker_tests_env_var() {
166        let _guard = EnvGuard::new(&[
167            EnvVars::ZERV_TEST_NATIVE_GIT,
168            EnvVars::ZERV_TEST_DOCKER,
169            EnvVars::ZERV_FORCE_RUST_LOG_OFF,
170        ]);
171        unsafe {
172            env::remove_var(EnvVars::ZERV_TEST_NATIVE_GIT);
173            env::remove_var(EnvVars::ZERV_FORCE_RUST_LOG_OFF);
174            env::set_var(EnvVars::ZERV_TEST_DOCKER, "true");
175        }
176
177        let config = ZervConfig::load().expect("Failed to load config with docker tests enabled");
178        assert!(!config.should_use_native_git());
179        assert!(config.should_run_docker_tests());
180        assert!(!config.should_force_rust_log_off());
181    }
182
183    #[test]
184    #[serial]
185    fn test_both_env_vars() {
186        let _guard = EnvGuard::new(&[
187            EnvVars::ZERV_TEST_NATIVE_GIT,
188            EnvVars::ZERV_TEST_DOCKER,
189            EnvVars::ZERV_FORCE_RUST_LOG_OFF,
190        ]);
191        unsafe {
192            env::set_var(EnvVars::ZERV_TEST_NATIVE_GIT, "true");
193            env::set_var(EnvVars::ZERV_TEST_DOCKER, "true");
194            env::remove_var(EnvVars::ZERV_FORCE_RUST_LOG_OFF);
195        }
196
197        let config = ZervConfig::load().expect("Failed to load config with both env vars set");
198        assert!(config.should_use_native_git());
199        assert!(config.should_run_docker_tests());
200        assert!(!config.should_force_rust_log_off());
201    }
202
203    #[test]
204    #[serial]
205    fn test_false_values() {
206        let _guard = EnvGuard::new(&[
207            EnvVars::ZERV_TEST_NATIVE_GIT,
208            EnvVars::ZERV_TEST_DOCKER,
209            EnvVars::ZERV_FORCE_RUST_LOG_OFF,
210        ]);
211        unsafe {
212            env::set_var(EnvVars::ZERV_TEST_NATIVE_GIT, "false");
213            env::set_var(EnvVars::ZERV_TEST_DOCKER, "false");
214            env::set_var(EnvVars::ZERV_FORCE_RUST_LOG_OFF, "false");
215        }
216
217        let config = ZervConfig::load().expect("Failed to load config with false values");
218        assert!(!config.should_use_native_git());
219        assert!(!config.should_run_docker_tests());
220        assert!(!config.should_force_rust_log_off());
221    }
222
223    #[test]
224    #[serial]
225    fn test_force_rust_log_off_env_var() {
226        let _guard = EnvGuard::new(&[
227            EnvVars::ZERV_TEST_NATIVE_GIT,
228            EnvVars::ZERV_TEST_DOCKER,
229            EnvVars::ZERV_FORCE_RUST_LOG_OFF,
230        ]);
231        unsafe {
232            env::remove_var(EnvVars::ZERV_TEST_NATIVE_GIT);
233            env::remove_var(EnvVars::ZERV_TEST_DOCKER);
234            env::set_var(EnvVars::ZERV_FORCE_RUST_LOG_OFF, "true");
235        }
236
237        let config =
238            ZervConfig::load().expect("Failed to load config with force rust log off enabled");
239        assert!(!config.should_use_native_git());
240        assert!(config.should_run_docker_tests());
241        assert!(config.should_force_rust_log_off());
242    }
243}