Skip to main content

statime_config/tree/
enum_section.rs

1use serde::{Deserialize, Deserializer, Serialize, Serializer};
2
3use crate::{
4    ConfigError, ConfigPath, Merge, MergeContext, OriginId, PartialValue, tree::EffectivelyUnset,
5};
6
7/// EnumSections are merge-able partial values within a configuration tree that have no
8/// default partial value. This is the case for enums, which may not have a default variant.
9#[derive(Debug, Clone, PartialEq, Eq)]
10pub enum EnumSection<T> {
11    Unset,
12    Set(T),
13}
14
15// Ensure default is always implemented for EnumSection<T>, even if T does not implement default.
16#[allow(clippy::derivable_impls, reason = "Want this even when T not Default")]
17impl<T> Default for EnumSection<T> {
18    fn default() -> Self {
19        Self::Unset
20    }
21}
22
23impl<T> EnumSection<T> {
24    pub fn is_unset(&self) -> bool {
25        matches!(self, EnumSection::Unset)
26    }
27}
28
29impl<'de, T> Deserialize<'de> for EnumSection<T>
30where
31    T: Deserialize<'de>,
32{
33    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
34    where
35        D: Deserializer<'de>,
36    {
37        T::deserialize(deserializer).map(Self::Set)
38    }
39}
40
41impl<T> Serialize for EnumSection<T>
42where
43    T: Serialize,
44{
45    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
46    where
47        S: Serializer,
48    {
49        match self {
50            Self::Set(value) => value.serialize(serializer),
51            // note: sections should be marked to be skipped and should never serialize to none
52            Self::Unset => serializer.serialize_none(),
53        }
54    }
55}
56
57/// An EnumSection says nothing when it is absent, but the lack of default partialvalue
58/// means that the presence of any partialvalue also indicates it is set.
59impl<T> EffectivelyUnset for EnumSection<T> {
60    fn is_effectively_unset(&self) -> bool {
61        matches!(self, EnumSection::Unset)
62    }
63}
64
65impl<T> PartialValue for EnumSection<T>
66where
67    T: PartialValue,
68{
69    type Resolved = T::Resolved;
70
71    /// A section records no origin of its own, and visits its children.
72    fn attribute(&mut self, origin: OriginId) {
73        if let EnumSection::Set(value) = self {
74            value.attribute(origin);
75        }
76    }
77
78    /// An absent section still has to be visited: a document that never
79    /// mentions a section should still get the defaults of everything inside
80    /// it.
81    fn apply_defaults(&mut self) {
82        if let EnumSection::Set(value) = self {
83            value.apply_defaults();
84        }
85    }
86
87    /// A section resolves to whatever its contents resolve to. An unset one
88    /// only reaches this point when resolving without defaulting first, and
89    /// resolving the empty struct still reports any required value it misses.
90    fn resolve(self, path: &mut ConfigPath) -> Result<Self::Resolved, ConfigError> {
91        match self {
92            EnumSection::Set(value) => value.resolve(path),
93            EnumSection::Unset => Err(ConfigError::MissingRequiredValue {
94                position: path.clone(),
95            }),
96        }
97    }
98}
99
100/// A section is a recursive merge boundary: two documents mentioning the same
101/// table is never a conflict by itself, so the policy is only consulted by the
102/// settings underneath.
103impl<T> Merge for EnumSection<T>
104where
105    T: Merge,
106{
107    fn merge(&mut self, incoming: Self, context: &mut MergeContext<'_>) -> Result<(), ConfigError> {
108        let EnumSection::Set(incoming) = incoming else {
109            return Ok(());
110        };
111
112        match self {
113            EnumSection::Set(existing) => existing.merge(incoming, context),
114            EnumSection::Unset => {
115                *self = EnumSection::Set(incoming);
116                Ok(())
117            }
118        }
119    }
120}