Skip to main content

remem/runtime_config/
promotion.rs

1use anyhow::{bail, Result};
2use toml_edit::{DocumentMut, Item};
3
4const DEFAULT_SUMMARY_GATE_MODE: &str = "enforce";
5
6#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum SummaryGateMode {
8    Off,
9    Shadow,
10    Enforce,
11}
12
13pub fn summary_gate_mode() -> Result<SummaryGateMode> {
14    let mut doc = super::read_config_doc_or_default()?;
15    ensure_defaults(&mut doc)?;
16    let mode = doc
17        .get("promotion")
18        .and_then(Item::as_table)
19        .and_then(|table| super::optional_str(table, "summary_gate_mode"))
20        .unwrap_or_else(|| DEFAULT_SUMMARY_GATE_MODE.to_string());
21    parse_summary_gate_mode(&mode)
22}
23
24pub(super) fn ensure_defaults(doc: &mut DocumentMut) -> Result<()> {
25    let promotion = super::top_table_mut(doc, "promotion")?;
26    super::set_str_if_missing(promotion, "summary_gate_mode", DEFAULT_SUMMARY_GATE_MODE);
27    Ok(())
28}
29
30fn parse_summary_gate_mode(raw: &str) -> Result<SummaryGateMode> {
31    match raw.trim().to_ascii_lowercase().as_str() {
32        "off" => Ok(SummaryGateMode::Off),
33        "shadow" => Ok(SummaryGateMode::Shadow),
34        "enforce" => Ok(SummaryGateMode::Enforce),
35        other => {
36            bail!("unknown promotion.summary_gate_mode: {other}; expected off, shadow, or enforce")
37        }
38    }
39}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    fn with_promotion_config_path<T>(path: &std::path::Path, f: impl FnOnce() -> T) -> T {
46        let _guard = super::super::TEST_ENV_LOCK
47            .lock()
48            .expect("env lock should acquire");
49        let old = std::env::var("REMEM_CONFIG").ok();
50        unsafe { std::env::set_var("REMEM_CONFIG", path) };
51        let result = f();
52        match old {
53            Some(value) => unsafe { std::env::set_var("REMEM_CONFIG", value) },
54            None => unsafe { std::env::remove_var("REMEM_CONFIG") },
55        }
56        result
57    }
58
59    fn promotion_config_path(label: &str) -> std::path::PathBuf {
60        std::env::temp_dir().join(format!(
61            "remem-{label}-{}-{}.toml",
62            std::process::id(),
63            chrono::Utc::now().timestamp_nanos_opt().unwrap_or_default()
64        ))
65    }
66
67    #[test]
68    fn default_config_enables_summary_gate_enforce_mode() {
69        let text = super::super::default_config_text();
70        assert!(text.contains("summary_gate_mode = \"enforce\""), "{text}");
71    }
72
73    #[test]
74    fn summary_gate_mode_reads_config_value() -> Result<()> {
75        let path = promotion_config_path("summary-gate-mode");
76        with_promotion_config_path(&path, || -> Result<()> {
77            super::super::init_config()?;
78            super::super::set_config_value("promotion.summary_gate_mode", "shadow")?;
79            assert_eq!(summary_gate_mode()?, SummaryGateMode::Shadow);
80
81            super::super::set_config_value("promotion.summary_gate_mode", "off")?;
82            assert_eq!(summary_gate_mode()?, SummaryGateMode::Off);
83            Ok(())
84        })?;
85        std::fs::remove_file(path)?;
86        Ok(())
87    }
88
89    #[test]
90    fn summary_gate_mode_rejects_unknown_value() -> Result<()> {
91        let path = promotion_config_path("summary-gate-mode-invalid");
92        with_promotion_config_path(&path, || -> Result<()> {
93            std::fs::write(&path, "[promotion]\nsummary_gate_mode = \"maybe\"\n")?;
94            let err = summary_gate_mode().expect_err("invalid mode must fail closed");
95            assert!(
96                err.to_string()
97                    .contains("unknown promotion.summary_gate_mode"),
98                "{err}"
99            );
100            Ok(())
101        })?;
102        std::fs::remove_file(path)?;
103        Ok(())
104    }
105}