zng_ext_config/
toml.rs

1use super::*;
2
3use ::toml as serde_toml;
4
5impl ConfigMap for indexmap::IndexMap<ConfigKey, serde_toml::Value> {
6    fn empty() -> Self {
7        Self::new()
8    }
9
10    fn read(mut file: WatchFile) -> io::Result<Self> {
11        file.toml()
12    }
13
14    fn write(self, file: &mut WriteFile) -> io::Result<()> {
15        if self.is_empty() {
16            // helps diagnosticate issues with empty config, JSON and RON empty are `{}`, TOML is
17            // zero-sized if we don't add this.
18            file.write_text("#")
19        } else {
20            file.write_toml(&self, true)
21        }
22    }
23
24    fn get_raw(&self, key: &ConfigKey) -> Result<Option<RawConfigValue>, Arc<dyn std::error::Error + Send + Sync>> {
25        match self.get(key) {
26            Some(e) => Ok(Some(RawConfigValue::try_from(e.clone())?)),
27            None => Ok(None),
28        }
29    }
30
31    fn set_raw(map: &mut VarModify<Self>, key: ConfigKey, value: RawConfigValue) -> Result<(), Arc<dyn std::error::Error + Send + Sync>> {
32        let value = value.try_into()?;
33        if map.get(&key) != Some(&value) {
34            map.to_mut().insert(key, value);
35        }
36        Ok(())
37    }
38
39    fn contains_key(&self, key: &ConfigKey) -> bool {
40        self.contains_key(key)
41    }
42
43    fn get<O: ConfigValue>(&self, key: &ConfigKey) -> Result<Option<O>, Arc<dyn std::error::Error + Send + Sync>> {
44        if let Some(value) = self.get(key) {
45            match serde_toml::to_string(&value) {
46                Ok(value) => match serde_toml::from_str(&value) {
47                    Ok(value) => Ok(Some(value)),
48                    Err(e) => Err(Arc::new(e)),
49                },
50                Err(e) => Err(Arc::new(e)),
51            }
52        } else {
53            Ok(None)
54        }
55    }
56
57    fn set<O: ConfigValue>(map: &mut VarModify<Self>, key: ConfigKey, value: O) -> Result<(), Arc<dyn std::error::Error + Send + Sync>> {
58        match serde_toml::to_string(&value) {
59            Ok(value) => match serde_toml::from_str(&value) {
60                Ok(value) => {
61                    if map.get(&key) != Some(&value) {
62                        map.to_mut().insert(key, value);
63                    }
64                    Ok(())
65                }
66                Err(e) => Err(Arc::new(e)),
67            },
68            Err(e) => Err(Arc::new(e)),
69        }
70    }
71
72    fn remove(map: &mut VarModify<Self>, key: &ConfigKey) {
73        if map.contains_key(key) {
74            map.to_mut().shift_remove(key);
75        }
76    }
77}
78
79/// Represents a config source that synchronizes with a TOML file.
80pub type TomlConfig = SyncConfig<indexmap::IndexMap<ConfigKey, serde_toml::Value>>;
81
82impl TryFrom<serde_toml::Value> for RawConfigValue {
83    type Error = TomlValueRawError;
84
85    fn try_from(value: serde_toml::Value) -> Result<Self, Self::Error> {
86        let ok = match value {
87            serde_toml::Value::String(s) => serde_json::Value::String(s),
88            serde_toml::Value::Integer(n) => serde_json::Value::Number(n.into()),
89            serde_toml::Value::Float(f) => match serde_json::Number::from_f64(f) {
90                Some(f) => serde_json::Value::Number(f),
91                None => return Err(TomlValueRawError::InvalidFloat(f)),
92            },
93            serde_toml::Value::Boolean(b) => serde_json::Value::Bool(b),
94            serde_toml::Value::Datetime(d) => serde_json::Value::String(d.to_string()),
95            serde_toml::Value::Array(a) => serde_json::Value::Array({
96                let mut r = Vec::with_capacity(a.len());
97                for v in a {
98                    r.push(RawConfigValue::try_from(v)?.0);
99                }
100                r
101            }),
102            serde_toml::Value::Table(m) => serde_json::Value::Object({
103                let mut r = serde_json::Map::with_capacity(m.len());
104                for (k, v) in m {
105                    r.insert(k, RawConfigValue::try_from(v)?.0);
106                }
107                r
108            }),
109        };
110        Ok(Self(ok))
111    }
112}
113impl TryFrom<RawConfigValue> for serde_toml::Value {
114    type Error = TomlValueRawError;
115
116    fn try_from(value: RawConfigValue) -> Result<Self, Self::Error> {
117        let ok = match value.0 {
118            serde_json::Value::Null => return Err(TomlValueRawError::Null),
119            serde_json::Value::Bool(b) => serde_toml::Value::Boolean(b),
120            serde_json::Value::Number(n) => {
121                // serde_json does not implicit converts float to integer, so we try integers first here.
122                if let Some(n) = n.as_i64() {
123                    serde_toml::Value::Integer(n)
124                } else if let Some(n) = n.as_u64() {
125                    if n > i64::MAX as u64 {
126                        return Err(TomlValueRawError::InvalidInt(n));
127                    }
128                    serde_toml::Value::Integer(n as i64)
129                } else if let Some(n) = n.as_f64() {
130                    serde_toml::Value::Float(n)
131                } else {
132                    unreachable!()
133                }
134            }
135            serde_json::Value::String(s) => serde_toml::Value::String(s),
136            serde_json::Value::Array(a) => serde_toml::Value::Array({
137                let mut r = Vec::with_capacity(a.len());
138                for v in a {
139                    match RawConfigValue(v).try_into() {
140                        Ok(v) => r.push(v),
141                        Err(TomlValueRawError::Null) => continue,
142                        e => return e,
143                    }
144                }
145                r
146            }),
147            serde_json::Value::Object(m) => serde_toml::Value::Table({
148                let mut r = serde_toml::Table::with_capacity(m.len());
149                for (k, v) in m {
150                    match RawConfigValue(v).try_into() {
151                        Ok(v) => {
152                            r.insert(k, v);
153                        }
154                        Err(TomlValueRawError::Null) => continue,
155                        e => return e,
156                    }
157                }
158                r
159            }),
160        };
161        Ok(ok)
162    }
163}
164
165/// Error converting toml::Value, RawConfigValue.
166#[derive(Debug, Clone, Copy)]
167pub enum TomlValueRawError {
168    /// JSON only supports finite floats.
169    InvalidFloat(f64),
170    /// TOML does not support `null`.
171    Null,
172    /// TOML only supports integers up to `i64::MAX`.
173    InvalidInt(u64),
174}
175impl fmt::Display for TomlValueRawError {
176    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
177        match self {
178            TomlValueRawError::InvalidFloat(fl) => write!(f, "json does not support float `{fl}`"),
179            TomlValueRawError::Null => write!(f, "toml does not support `null`"),
180            TomlValueRawError::InvalidInt(i) => write!(f, "toml does not support int > i64::MAX ({i})"),
181        }
182    }
183}
184impl std::error::Error for TomlValueRawError {}
185impl From<TomlValueRawError> for Arc<dyn std::error::Error + Send + Sync> {
186    fn from(value: TomlValueRawError) -> Self {
187        Arc::new(value)
188    }
189}