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#[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 pub fn value(value: T) -> Self {
31 Self::Set {
32 value,
33 origin: None,
34 }
35 }
36
37 pub fn value_from(value: T, origin: OriginId) -> Self {
39 Self::Set {
40 value,
41 origin: Some(origin),
42 }
43 }
44
45 pub fn is_unset(&self) -> bool {
47 matches!(self, Self::Unset)
48 }
49
50 pub fn get(&self) -> Option<&T> {
52 match self {
53 Self::Unset => None,
54 Self::Set { value, .. } => Some(value),
55 }
56 }
57
58 #[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 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 pub fn into_option(self) -> Option<T> {
79 match self {
80 Self::Unset => None,
81 Self::Set { value, .. } => Some(value),
82 }
83 }
84
85 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
95impl<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
111impl<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 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 fn apply_defaults(&mut self) {
142 if let Self::Set { value, .. } = self {
143 value.apply_defaults();
144 }
145 }
146
147 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 Self::Unset => serializer.serialize_none(),
178 }
179 }
180}
181
182impl<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 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 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 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}