Skip to main content

statime_config/tree/
setting.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3use crate::{
4    ConfigError,
5    tree::{
6        empty::EffectivelyUnset,
7        merge::{Merge, MergeContext, MergePolicy, OriginId},
8        partial_value::PartialValue,
9        path::ConfigPath,
10    },
11};
12
13/// An atomic merge boundary in the configuration tree.
14///
15/// A set setting also records where its value came from if it has been set.
16/// Because parsing logic does not understand origins, they are attached
17/// separately.
18#[derive(Debug, Clone, Default)]
19pub enum Setting<T> {
20    #[default]
21    Unset,
22    Set {
23        value: T,
24        origin: Option<OriginId>,
25    },
26}
27
28impl<T> Setting<T> {
29    /// Create a set setting whose origin is not (yet) known.
30    pub fn value(value: T) -> Self {
31        Self::Set {
32            value,
33            origin: None,
34        }
35    }
36
37    /// Create a set setting originating from `origin`.
38    pub fn value_from(value: T, origin: OriginId) -> Self {
39        Self::Set {
40            value,
41            origin: Some(origin),
42        }
43    }
44
45    /// Return `true` if this setting is unset.
46    pub fn is_unset(&self) -> bool {
47        matches!(self, Self::Unset)
48    }
49
50    /// Return the value in this setting, if it is set.
51    pub fn get(&self) -> Option<&T> {
52        match self {
53            Self::Unset => None,
54            Self::Set { value, .. } => Some(value),
55        }
56    }
57
58    /// The origin of this setting, if it is set and has been attributed.
59    #[cfg(test)]
60    pub fn origin(&self) -> Option<OriginId> {
61        match self {
62            Self::Unset => None,
63            Self::Set { origin, .. } => *origin,
64        }
65    }
66
67    /// Set this setting to its built-in default if no document supplied a
68    /// value.
69    /// The default is only produced when it is needed, so a setting a document
70    /// supplied never pays for computing one it will not use.
71    pub fn default_to(&mut self, value: impl FnOnce() -> T) {
72        if self.is_unset() {
73            *self = Self::value_from(value(), OriginId::BUILT_IN_DEFAULT);
74        }
75    }
76
77    /// Consume this setting, returning the value in it, if it is set.
78    pub fn into_option(self) -> Option<T> {
79        match self {
80            Self::Unset => None,
81            Self::Set { value, .. } => Some(value),
82        }
83    }
84
85    /// Consume this setting, returning the value in it. If no value is set,
86    /// returns an error naming the setting.
87    pub fn require(self, path: &ConfigPath) -> Result<T, ConfigError> {
88        self.into_option()
89            .ok_or_else(|| ConfigError::MissingRequiredValue {
90                position: path.clone(),
91            })
92    }
93}
94
95/// Two settings are equal when they carry equal values.
96impl<T> PartialEq for Setting<T>
97where
98    T: PartialEq,
99{
100    fn eq(&self, other: &Self) -> bool {
101        match (self, other) {
102            (Self::Unset, Self::Unset) => true,
103            (Self::Set { value: this, .. }, Self::Set { value: other, .. }) => this == other,
104            _ => false,
105        }
106    }
107}
108
109impl<T> Eq for Setting<T> where T: Eq {}
110
111/// Settings are effectively unset if they are just unset or if they are set to
112/// their default value with a default origin.
113impl<T> EffectivelyUnset for Setting<T> {
114    fn is_effectively_unset(&self) -> bool {
115        self.is_unset()
116    }
117}
118
119impl<T> PartialValue for Setting<T>
120where
121    T: PartialValue,
122{
123    type Resolved = T::Resolved;
124
125    /// A setting records its own origin, and visits its nested values.
126    fn attribute(&mut self, origin: OriginId) {
127        if let Self::Set {
128            value,
129            origin: slot,
130        } = self
131        {
132            *slot = Some(origin);
133            value.attribute(origin);
134        }
135    }
136
137    /// A setting holds no default of its own, since the field it belongs to
138    /// supplies that; it only lets nested values be defaulted. Note that if
139    /// this setting itself should have been defaulted, that should have
140    /// happened at the parent level.
141    fn apply_defaults(&mut self) {
142        if let Self::Set { value, .. } = self {
143            value.apply_defaults();
144        }
145    }
146
147    /// A setting resolves to whatever its value is, and is missing if no
148    /// document and no default supplied one.
149    fn resolve(self, path: &mut ConfigPath) -> Result<Self::Resolved, ConfigError> {
150        self.require(path)?.resolve(path)
151    }
152}
153
154impl<'de, T> Deserialize<'de> for Setting<T>
155where
156    T: Deserialize<'de>,
157{
158    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
159    where
160        D: Deserializer<'de>,
161    {
162        T::deserialize(deserializer).map(Setting::value)
163    }
164}
165
166impl<T> Serialize for Setting<T>
167where
168    T: Serialize,
169{
170    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
171    where
172        S: Serializer,
173    {
174        match self {
175            Self::Set { value, .. } => value.serialize(serializer),
176            // note: settings should be marked to be skipped and should never serialize to none
177            Self::Unset => serializer.serialize_none(),
178        }
179    }
180}
181
182/// A setting is a leaf node, so we don't need to bother too much about merging.
183impl<T> Merge for Setting<T> {
184    fn merge(&mut self, incoming: Self, context: &mut MergeContext<'_>) -> Result<(), ConfigError> {
185        let Setting::Set { value, origin } = incoming else {
186            return Ok(());
187        };
188
189        if let Setting::Set {
190            origin: current, ..
191        } = self
192            && context.policy == MergePolicy::RejectOverlap
193        {
194            return Err(ConfigError::OverwriteNotAllowed {
195                position: context.path.clone(),
196                current_origin: context.origin(*current),
197                incoming_origin: context.origin(origin),
198            });
199        }
200
201        *self = Setting::Set { value, origin };
202
203        Ok(())
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use crate::tree::merge::{Origin, ProvenanceTracker};
211
212    /// Two tracked documents, along with the registry that can name them.
213    fn origins() -> (ProvenanceTracker, OriginId, OriginId) {
214        let mut tracker = ProvenanceTracker::new();
215        let first = tracker.track(Origin::SystemConfig("/etc/ntp.d/a.toml".into()));
216        let second = tracker.track(Origin::SystemConfig("/etc/ntp.d/b.toml".into()));
217        (tracker, first, second)
218    }
219
220    #[test]
221    fn equality_ignores_origin() {
222        let (_tracker, first, second) = origins();
223
224        assert_eq!(
225            Setting::value_from(1, first),
226            Setting::value_from(1, second)
227        );
228        assert_eq!(Setting::value_from(1, first), Setting::value(1));
229        assert_ne!(Setting::value_from(1, first), Setting::value_from(2, first));
230        assert_ne!(Setting::value_from(1, first), Setting::<i32>::Unset);
231    }
232
233    #[test]
234    fn only_set_settings_are_attributed() {
235        let (_tracker, first, _) = origins();
236
237        let mut setting = Setting::value(1);
238        assert_eq!(setting.origin(), None);
239        setting.attribute(first);
240        assert_eq!(setting.origin(), Some(first));
241
242        let mut unset = Setting::<i32>::Unset;
243        unset.attribute(first);
244        assert_eq!(unset.origin(), None);
245    }
246
247    #[test]
248    fn override_replaces_value_and_origin() {
249        let (tracker, first, second) = origins();
250        let mut context = MergeContext::new(MergePolicy::Override, &tracker);
251
252        let mut setting = Setting::value_from(1, first);
253        setting
254            .merge(Setting::value_from(2, second), &mut context)
255            .unwrap();
256
257        assert_eq!(setting.get(), Some(&2));
258        assert_eq!(setting.origin(), Some(second));
259    }
260
261    #[test]
262    fn reject_overlap_fills_an_unset_setting() {
263        let (tracker, first, _) = origins();
264        let mut context = MergeContext::new(MergePolicy::RejectOverlap, &tracker);
265
266        let mut setting = Setting::Unset;
267        setting
268            .merge(Setting::value_from(1, first), &mut context)
269            .unwrap();
270
271        assert_eq!(setting.get(), Some(&1));
272        assert_eq!(setting.origin(), Some(first));
273    }
274
275    #[test]
276    fn reject_overlap_reports_both_origins() {
277        let (tracker, first, second) = origins();
278        let mut context = MergeContext::new(MergePolicy::RejectOverlap, &tracker);
279
280        let mut setting = Setting::value_from(1, first);
281        let error = setting
282            .merge(Setting::value_from(2, second), &mut context)
283            .unwrap_err();
284
285        let ConfigError::OverwriteNotAllowed {
286            position,
287            current_origin,
288            incoming_origin,
289        } = error
290        else {
291            panic!("expected an overwrite conflict, got {error:?}");
292        };
293        assert_eq!(position, ConfigPath::root());
294        // the error names the documents, not the ids it was built from
295        assert_eq!(
296            current_origin,
297            Some(Origin::SystemConfig("/etc/ntp.d/a.toml".into()))
298        );
299        assert_eq!(
300            incoming_origin,
301            Some(Origin::SystemConfig("/etc/ntp.d/b.toml".into()))
302        );
303        // the existing value is left untouched by a rejected merge
304        assert_eq!(setting.get(), Some(&1));
305    }
306
307    #[test]
308    fn merging_an_unset_setting_changes_nothing() {
309        let (tracker, first, _) = origins();
310
311        for policy in [MergePolicy::Override, MergePolicy::RejectOverlap] {
312            let mut context = MergeContext::new(policy, &tracker);
313
314            let mut setting = Setting::value_from(1, first);
315            setting.merge(Setting::Unset, &mut context).unwrap();
316
317            assert_eq!(setting.get(), Some(&1));
318            assert_eq!(setting.origin(), Some(first));
319        }
320    }
321}