Skip to main content

sim_config/
probe.rs

1//! Probe contracts for configuration defaults.
2
3use sim_kernel::Symbol;
4
5use crate::{ConfigLayer, ProbeMode};
6
7/// Capability grants available to a configuration probe.
8#[derive(Clone, Debug, Default, PartialEq, Eq)]
9pub struct ConfigProbeCaps {
10    /// Permission to inspect environment-variable presence.
11    pub env: bool,
12    /// Permission to inspect operating-system facts.
13    pub os: bool,
14    /// Permission to inspect hardware inventory.
15    pub hardware_inventory: bool,
16    /// Permission to inspect network inventory.
17    pub network_inventory: bool,
18}
19
20/// Request passed to a configuration probe.
21#[derive(Clone, Debug, PartialEq, Eq)]
22pub struct ConfigProbeRequest {
23    /// Library whose defaults are being requested.
24    pub lib: Symbol,
25    /// Probe mode selected by the host.
26    pub mode: ProbeMode,
27    /// Capabilities granted to this probe call.
28    pub caps: ConfigProbeCaps,
29}
30
31/// Outcome reported by a configuration probe.
32#[derive(Clone, Debug, PartialEq, Eq)]
33pub enum ConfigProbeStatus {
34    /// A config layer was emitted and applied.
35    Applied,
36    /// The probe intentionally did not emit a layer.
37    Skipped {
38        /// Human-readable skip reason.
39        reason: String,
40    },
41    /// The probe was denied a required capability.
42    Denied {
43        /// Capability name that was required.
44        capability: String,
45    },
46    /// The probe failed without making configuration discovery fatal.
47    Failed {
48        /// Human-readable failure message.
49        message: String,
50    },
51}
52
53/// Report emitted by one configuration probe call.
54#[derive(Clone, Debug, PartialEq, Eq)]
55pub struct ConfigProbeReport {
56    /// Probe implementation id.
57    pub probe: Symbol,
58    /// Library whose defaults were requested.
59    pub lib: Symbol,
60    /// Probe mode used for this call.
61    pub mode: ProbeMode,
62    /// Probe outcome.
63    pub status: ConfigProbeStatus,
64    /// Top-level config keys emitted by the probe layer.
65    pub emitted_keys: Vec<String>,
66}
67
68/// Source of modeled or real configuration defaults.
69pub trait ConfigProbe {
70    /// Returns the stable probe implementation id.
71    fn symbol(&self) -> Symbol;
72
73    /// Runs the probe and returns an optional config layer plus a report.
74    fn probe(&self, request: &ConfigProbeRequest) -> (Option<ConfigLayer>, ConfigProbeReport);
75}
76
77#[cfg(test)]
78mod tests {
79    use sim_kernel::Expr;
80
81    use super::*;
82    use crate::{ConfigDir, ConfigSource};
83
84    struct FakeProbe {
85        symbol: Symbol,
86    }
87
88    impl FakeProbe {
89        fn new() -> Self {
90            Self {
91                symbol: Symbol::qualified("config", "fake"),
92            }
93        }
94    }
95
96    impl ConfigProbe for FakeProbe {
97        fn symbol(&self) -> Symbol {
98            self.symbol.clone()
99        }
100
101        fn probe(&self, request: &ConfigProbeRequest) -> (Option<ConfigLayer>, ConfigProbeReport) {
102            let emitted_keys = vec!["backend".to_owned()];
103            let layer = ConfigLayer::new(
104                ConfigSource::Probe {
105                    probe: self.symbol(),
106                    mode: request.mode,
107                },
108                ConfigDir::one(
109                    request.lib.clone(),
110                    Expr::Map(vec![(
111                        Expr::Symbol(Symbol::new("backend")),
112                        Expr::String("modeled".to_owned()),
113                    )]),
114                )
115                .unwrap(),
116            );
117            let report = ConfigProbeReport {
118                probe: self.symbol(),
119                lib: request.lib.clone(),
120                mode: request.mode,
121                status: ConfigProbeStatus::Applied,
122                emitted_keys,
123            };
124            (Some(layer), report)
125        }
126    }
127
128    #[test]
129    fn probe_mode_defaults_to_modeled() {
130        assert_eq!(ProbeMode::default(), ProbeMode::Modeled);
131    }
132
133    #[test]
134    fn probe_returns_optional_layer_and_typed_report() {
135        let probe = FakeProbe::new();
136        let request = ConfigProbeRequest {
137            lib: Symbol::qualified("stream", "host"),
138            mode: ProbeMode::default(),
139            caps: ConfigProbeCaps::default(),
140        };
141
142        let (layer, report) = probe.probe(&request);
143
144        assert_eq!(report.probe, Symbol::qualified("config", "fake"));
145        assert_eq!(report.lib, Symbol::qualified("stream", "host"));
146        assert_eq!(report.mode, ProbeMode::Modeled);
147        assert_eq!(report.status, ConfigProbeStatus::Applied);
148        assert_eq!(report.emitted_keys, ["backend"]);
149        assert!(matches!(
150            layer.unwrap().source,
151            ConfigSource::Probe {
152                probe,
153                mode: ProbeMode::Modeled
154            } if probe == Symbol::qualified("config", "fake")
155        ));
156    }
157}