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