Skip to main content

cordis_include/
node.rs

1//! Order-preserving, format-neutral value tree used for entry config.
2
3use indexmap::IndexMap;
4use serde::de::{self, MapAccess, SeqAccess};
5use serde::ser::SerializeMap;
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8/// An ordered string-keyed map of [`Node`]s.
9pub type NodeMap = IndexMap<String, Node>;
10
11/// A dynamically typed value that round-trips through YAML and JSON while
12/// preserving object key order.
13///
14/// Entry config read from a file is stored as a `Node`; plugins receive it
15/// wrapped in a [`cordis::Value`] and recover it with
16/// `downcast::<Node>()`. Unlike `serde_json::Value` or
17/// `serde_yaml_ng::Value`, object keys keep their file order, which keeps
18/// serialized output stable and friendly to diffs and file watchers.
19#[derive(Debug, Clone, PartialEq, Default)]
20pub enum Node {
21    /// Absent value (`null` / `~`).
22    #[default]
23    Null,
24    /// Boolean literal.
25    Bool(bool),
26    /// Signed integer.
27    Int(i64),
28    /// Unsigned integer outside `i64` range.
29    UInt(u64),
30    /// Floating-point number.
31    Float(f64),
32    /// String literal (subject to `${{ ... }}` interpolation).
33    String(String),
34    /// Array of nodes.
35    Array(Vec<Node>),
36    /// Ordered object.
37    Object(NodeMap),
38}
39
40/// Unsigned integers within `i64` range become [`Node::Int`]; only values
41/// above it stay [`Node::UInt`]. Parse results and explicit conversions
42/// agree, so `Node::Int(1) == 1u64.into()` holds.
43fn small_uint(value: u64) -> Node {
44    if value <= i64::MAX as u64 {
45        Node::Int(value as i64)
46    } else {
47        Node::UInt(value)
48    }
49}
50
51impl Serialize for Node {
52    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
53        match self {
54            Self::Null => serializer.serialize_none(),
55            Self::Bool(value) => serializer.serialize_bool(*value),
56            Self::Int(value) => serializer.serialize_i64(*value),
57            Self::UInt(value) => serializer.serialize_u64(*value),
58            Self::Float(value) => serializer.serialize_f64(*value),
59            Self::String(value) => serializer.serialize_str(value),
60            Self::Array(items) => items.serialize(serializer),
61            Self::Object(map) => {
62                let mut map_serializer = serializer.serialize_map(Some(map.len()))?;
63                for (key, value) in map {
64                    map_serializer.serialize_entry(key, value)?;
65                }
66                map_serializer.end()
67            }
68        }
69    }
70}
71
72impl<'de> Deserialize<'de> for Node {
73    fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
74        struct NodeVisitor;
75
76        impl<'de> de::Visitor<'de> for NodeVisitor {
77            type Value = Node;
78
79            fn expecting(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
80                f.write_str("any YAML or JSON value")
81            }
82
83            fn visit_bool<E: de::Error>(self, value: bool) -> Result<Node, E> {
84                Ok(Node::Bool(value))
85            }
86
87            fn visit_i64<E: de::Error>(self, value: i64) -> Result<Node, E> {
88                Ok(Node::Int(value))
89            }
90
91            fn visit_i128<E: de::Error>(self, value: i128) -> Result<Node, E> {
92                if value >= i64::MIN as i128 && value <= i64::MAX as i128 {
93                    Ok(Node::Int(value as i64))
94                } else if value >= 0 && value <= u64::MAX as i128 {
95                    Ok(Node::UInt(value as u64))
96                } else {
97                    Err(E::custom("integer out of range"))
98                }
99            }
100
101            fn visit_u64<E: de::Error>(self, value: u64) -> Result<Node, E> {
102                Ok(small_uint(value))
103            }
104
105            fn visit_u128<E: de::Error>(self, value: u128) -> Result<Node, E> {
106                if value <= u64::MAX as u128 {
107                    Ok(small_uint(value as u64))
108                } else {
109                    Err(E::custom("integer out of range"))
110                }
111            }
112
113            fn visit_f64<E: de::Error>(self, value: f64) -> Result<Node, E> {
114                Ok(Node::Float(value))
115            }
116
117            fn visit_str<E: de::Error>(self, value: &str) -> Result<Node, E> {
118                Ok(Node::String(value.to_owned()))
119            }
120
121            fn visit_string<E: de::Error>(self, value: String) -> Result<Node, E> {
122                Ok(Node::String(value))
123            }
124
125            fn visit_none<E: de::Error>(self) -> Result<Node, E> {
126                Ok(Node::Null)
127            }
128
129            fn visit_unit<E: de::Error>(self) -> Result<Node, E> {
130                Ok(Node::Null)
131            }
132
133            fn visit_some<D: Deserializer<'de>>(self, deserializer: D) -> Result<Node, D::Error> {
134                deserializer.deserialize_any(self)
135            }
136
137            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Node, A::Error> {
138                let mut items = Vec::new();
139                while let Some(item) = seq.next_element::<Node>()? {
140                    items.push(item);
141                }
142                Ok(Node::Array(items))
143            }
144
145            fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Node, A::Error> {
146                let mut entries = NodeMap::new();
147                while let Some((key, value)) = map.next_entry::<String, Node>()? {
148                    entries.insert(key, value);
149                }
150                Ok(Node::Object(entries))
151            }
152        }
153
154        deserializer.deserialize_any(NodeVisitor)
155    }
156}
157
158impl Node {
159    /// Return the string contents, or `None` for any other node kind.
160    pub fn as_str(&self) -> Option<&str> {
161        match self {
162            Self::String(value) => Some(value),
163            _ => None,
164        }
165    }
166
167    /// Return the integer value, or `None` for any other node kind.
168    pub fn as_i64(&self) -> Option<i64> {
169        match self {
170            Self::Int(value) => Some(*value),
171            Self::UInt(value) => i64::try_from(*value).ok(),
172            _ => None,
173        }
174    }
175
176    /// Return the boolean value, or `None` for any other node kind.
177    pub fn as_bool(&self) -> Option<bool> {
178        match self {
179            Self::Bool(value) => Some(*value),
180            _ => None,
181        }
182    }
183
184    /// Return the object entries, or `None` for any other node kind.
185    pub fn as_object(&self) -> Option<&NodeMap> {
186        match self {
187            Self::Object(map) => Some(map),
188            _ => None,
189        }
190    }
191
192    /// Return the array items, or `None` for any other node kind.
193    pub fn as_array(&self) -> Option<&[Node]> {
194        match self {
195            Self::Array(items) => Some(items),
196            _ => None,
197        }
198    }
199
200    /// Whether this node is [`Node::Null`].
201    pub fn is_null(&self) -> bool {
202        matches!(self, Self::Null)
203    }
204}
205
206impl From<bool> for Node {
207    fn from(value: bool) -> Self {
208        Self::Bool(value)
209    }
210}
211
212impl From<i8> for Node {
213    fn from(value: i8) -> Self {
214        Self::Int(value as i64)
215    }
216}
217
218impl From<i16> for Node {
219    fn from(value: i16) -> Self {
220        Self::Int(value as i64)
221    }
222}
223
224impl From<i32> for Node {
225    fn from(value: i32) -> Self {
226        Self::Int(value as i64)
227    }
228}
229
230impl From<isize> for Node {
231    fn from(value: isize) -> Self {
232        Self::Int(value as i64)
233    }
234}
235
236impl From<u8> for Node {
237    fn from(value: u8) -> Self {
238        Self::Int(value as i64)
239    }
240}
241
242impl From<u16> for Node {
243    fn from(value: u16) -> Self {
244        Self::Int(value as i64)
245    }
246}
247
248impl From<u32> for Node {
249    fn from(value: u32) -> Self {
250        Self::Int(value as i64)
251    }
252}
253
254impl From<i64> for Node {
255    fn from(value: i64) -> Self {
256        Self::Int(value)
257    }
258}
259
260impl From<u64> for Node {
261    fn from(value: u64) -> Self {
262        small_uint(value)
263    }
264}
265
266impl From<f64> for Node {
267    fn from(value: f64) -> Self {
268        Self::Float(value)
269    }
270}
271
272impl From<usize> for Node {
273    fn from(value: usize) -> Self {
274        small_uint(value as u64)
275    }
276}
277
278/// Indexes object keys, panicking only when the node is not an object.
279///
280/// Missing keys yield [`Node::Null`], which keeps assertions on parsed
281/// config concise (`assert_eq!(node["missing"], Node::Null)`).
282impl std::ops::Index<&str> for Node {
283    type Output = Node;
284
285    fn index(&self, key: &str) -> &Node {
286        static NULL: Node = Node::Null;
287        self.as_object()
288            .and_then(|map| map.get(key))
289            .unwrap_or(&NULL)
290    }
291}
292
293impl From<&str> for Node {
294    fn from(value: &str) -> Self {
295        Self::String(value.to_owned())
296    }
297}
298
299impl From<String> for Node {
300    fn from(value: String) -> Self {
301        Self::String(value)
302    }
303}
304
305impl From<Vec<Node>> for Node {
306    fn from(value: Vec<Node>) -> Self {
307        Self::Array(value)
308    }
309}
310
311impl From<NodeMap> for Node {
312    fn from(value: NodeMap) -> Self {
313        Self::Object(value)
314    }
315}
316
317impl FromIterator<(String, Node)> for Node {
318    fn from_iter<I: IntoIterator<Item = (String, Node)>>(iter: I) -> Self {
319        Self::Object(NodeMap::from_iter(iter))
320    }
321}
322
323#[cfg(test)]
324mod tests {
325    use super::*;
326
327    #[test]
328    fn yaml_round_trip_preserves_key_order() {
329        let node: Node = serde_yaml_ng::from_str("b: 1\na: 2\nc: [x, y]").unwrap();
330        let text = serde_yaml_ng::to_string(&node).unwrap();
331        assert!(text.contains("b: 1"), "{text}");
332        assert!(text.contains("a: 2"), "{text}");
333        let back: Node = serde_yaml_ng::from_str(&text).unwrap();
334        assert_eq!(node, back);
335    }
336
337    #[test]
338    fn json_round_trip_preserves_key_order() {
339        let node: Node = serde_json::from_str(r#"{"b":1,"a":2,"c":[1,2.5,true,null]}"#).unwrap();
340        let text = serde_json::to_string(&node).unwrap();
341        assert_eq!(text, r#"{"b":1,"a":2,"c":[1,2.5,true,null]}"#);
342        let back: Node = serde_json::from_str(&text).unwrap();
343        assert_eq!(node, back);
344    }
345
346    #[test]
347    fn wide_integers_survive() {
348        let node: Node = serde_yaml_ng::from_str("big: 18446744073709551615").unwrap();
349        let Node::Object(map) = &node else {
350            panic!("expected object");
351        };
352        assert_eq!(map["big"], Node::UInt(u64::MAX));
353        let text = serde_yaml_ng::to_string(&node).unwrap();
354        assert!(text.contains("18446744073709551615"), "{text}");
355    }
356}