Skip to main content

knf/
value.rs

1//! Native value adapters for the shared merge and interpolation algorithms.
2
3/// An ordered object whose children have the same native value type.
4pub trait ConfigObject<V>: Sized + IntoIterator<Item = (String, V)> {
5    /// Construct an empty object.
6    fn new() -> Self;
7    /// Iterate in input/insertion order.
8    fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, &'a V)>
9    where
10        V: 'a;
11    /// Look up a child by key.
12    fn get(&self, key: &str) -> Option<&V>;
13    /// Look up a child for replacement or recursion.
14    fn get_mut(&mut self, key: &str) -> Option<&mut V>;
15    /// Insert a child, preserving an existing key's position.
16    fn insert(&mut self, key: String, value: V);
17}
18
19/// Structural operations on a native configuration value.
20///
21/// Scalars stay opaque to merge and traversal. Implementations classify
22/// integers and floats as `number`, and render scalars without quoting strings.
23pub trait ConfigValue: Clone {
24    /// The native object type.
25    type Object: ConfigObject<Self>;
26    /// A diagnostic kind: object, array, string, number, bool, null or datetime.
27    fn kind(&self) -> &'static str;
28    /// Borrow an object, if this is one.
29    fn as_object(&self) -> Option<&Self::Object>;
30    /// Mutably borrow an object, if this is one.
31    fn as_object_mut(&mut self) -> Option<&mut Self::Object>;
32    /// Consume an object; return the original value for any other kind.
33    fn into_object(self) -> Result<Self::Object, Self>;
34    /// Borrow an array, if this is one.
35    fn as_array(&self) -> Option<&[Self]>;
36    /// Borrow a string, if this is one.
37    fn as_str(&self) -> Option<&str>;
38    /// Wrap a native object.
39    fn object(object: Self::Object) -> Self;
40    /// Construct an array.
41    fn array(items: Vec<Self>) -> Self;
42    /// Construct a string.
43    fn string(text: String) -> Self;
44    /// Render a scalar for an embedded reference. Containers and null reject.
45    /// Finite floats keep Rust float spelling; serializers may use another spelling.
46    fn stringify(&self) -> Option<String>;
47}
48
49impl ConfigObject<serde_json::Value> for serde_json::Map<String, serde_json::Value> {
50    fn new() -> Self {
51        Self::new()
52    }
53    fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, &'a serde_json::Value)>
54    where
55        serde_json::Value: 'a,
56    {
57        self.iter()
58    }
59    fn get(&self, key: &str) -> Option<&serde_json::Value> {
60        self.get(key)
61    }
62    fn get_mut(&mut self, key: &str) -> Option<&mut serde_json::Value> {
63        self.get_mut(key)
64    }
65    fn insert(&mut self, key: String, value: serde_json::Value) {
66        self.insert(key, value);
67    }
68}
69
70impl ConfigObject<toml::Value> for toml::Table {
71    fn new() -> Self {
72        Self::new()
73    }
74    fn iter<'a>(&'a self) -> impl Iterator<Item = (&'a String, &'a toml::Value)>
75    where
76        toml::Value: 'a,
77    {
78        self.iter()
79    }
80    fn get(&self, key: &str) -> Option<&toml::Value> {
81        self.get(key)
82    }
83    fn get_mut(&mut self, key: &str) -> Option<&mut toml::Value> {
84        self.get_mut(key)
85    }
86    fn insert(&mut self, key: String, value: toml::Value) {
87        self.insert(key, value);
88    }
89}
90
91impl ConfigValue for serde_json::Value {
92    type Object = serde_json::Map<String, Self>;
93    fn kind(&self) -> &'static str {
94        match self {
95            Self::Null => "null",
96            Self::Bool(_) => "bool",
97            Self::Number(_) => "number",
98            Self::String(_) => "string",
99            Self::Array(_) => "array",
100            Self::Object(_) => "object",
101        }
102    }
103    fn as_object(&self) -> Option<&<Self as ConfigValue>::Object> {
104        self.as_object()
105    }
106    fn as_object_mut(&mut self) -> Option<&mut <Self as ConfigValue>::Object> {
107        self.as_object_mut()
108    }
109    fn into_object(self) -> Result<<Self as ConfigValue>::Object, Self> {
110        match self {
111            Self::Object(map) => Ok(map),
112            other => Err(other),
113        }
114    }
115    fn as_array(&self) -> Option<&[Self]> {
116        self.as_array().map(Vec::as_slice)
117    }
118    fn as_str(&self) -> Option<&str> {
119        self.as_str()
120    }
121    fn object(object: <Self as ConfigValue>::Object) -> Self {
122        Self::Object(object)
123    }
124    fn array(items: Vec<Self>) -> Self {
125        Self::Array(items)
126    }
127    fn string(text: String) -> Self {
128        Self::String(text)
129    }
130    fn stringify(&self) -> Option<String> {
131        match self {
132            Self::Bool(b) => Some(b.to_string()),
133            Self::Number(n) => Some(if n.is_i64() || n.is_u64() {
134                n.to_string()
135            } else {
136                n.as_f64().map(float).unwrap_or_else(|| n.to_string())
137            }),
138            Self::String(s) => Some(s.clone()),
139            Self::Null | Self::Array(_) | Self::Object(_) => None,
140        }
141    }
142}
143
144impl ConfigValue for toml::Value {
145    type Object = toml::Table;
146    fn kind(&self) -> &'static str {
147        match self {
148            Self::String(_) => "string",
149            Self::Integer(_) | Self::Float(_) => "number",
150            Self::Boolean(_) => "bool",
151            Self::Datetime(_) => "datetime",
152            Self::Array(_) => "array",
153            Self::Table(_) => "object",
154        }
155    }
156    fn as_object(&self) -> Option<&Self::Object> {
157        self.as_table()
158    }
159    fn as_object_mut(&mut self) -> Option<&mut Self::Object> {
160        self.as_table_mut()
161    }
162    fn into_object(self) -> Result<Self::Object, Self> {
163        match self {
164            Self::Table(map) => Ok(map),
165            other => Err(other),
166        }
167    }
168    fn as_array(&self) -> Option<&[Self]> {
169        self.as_array().map(Vec::as_slice)
170    }
171    fn as_str(&self) -> Option<&str> {
172        self.as_str()
173    }
174    fn object(object: Self::Object) -> Self {
175        Self::Table(object)
176    }
177    fn array(items: Vec<Self>) -> Self {
178        Self::Array(items)
179    }
180    fn string(text: String) -> Self {
181        Self::String(text)
182    }
183    fn stringify(&self) -> Option<String> {
184        match self {
185            Self::String(s) => Some(s.clone()),
186            Self::Integer(i) => Some(i.to_string()),
187            Self::Float(f) => Some(float(*f)),
188            Self::Boolean(b) => Some(b.to_string()),
189            Self::Datetime(dt) => Some(dt.to_string()),
190            Self::Array(_) | Self::Table(_) => None,
191        }
192    }
193}
194
195fn float(f: f64) -> String {
196    if f.is_finite() {
197        format!("{f:?}")
198    } else if f.is_nan() {
199        "nan".into()
200    } else if f.is_sign_negative() {
201        "-inf".into()
202    } else {
203        "inf".into()
204    }
205}
206
207#[cfg(test)]
208mod tests {
209    use super::*;
210    use crate::ConfigFormat;
211
212    fn rendering<V: ConfigFormat>() {
213        for (source, expected) in [
214            ("true", "true"),
215            ("false", "false"),
216            ("1", "1"),
217            ("1.0", "1.0"),
218            ("-0.0", "-0.0"),
219            ("1e300", "1e300"),
220            ("1e-7", "1e-7"),
221        ] {
222            assert_eq!(
223                V::parse_inline(source.into()).stringify().as_deref(),
224                Some(expected)
225            );
226        }
227        assert_eq!(
228            V::string("plain".into()).stringify().as_deref(),
229            Some("plain")
230        );
231        assert_eq!(V::array(Vec::new()).stringify(), None);
232        assert_eq!(V::object(V::Object::new()).stringify(), None);
233    }
234
235    #[test]
236    fn embedded_float_spelling_is_independent_of_serialization() {
237        let json = serde_json::json!({"v": 1e20});
238        let toml = toml::Value::parse_document("v = 1e20").unwrap();
239        assert_eq!(json["v"].stringify().as_deref(), Some("1e20"));
240        assert_eq!(toml["v"].stringify().as_deref(), Some("1e20"));
241        assert_eq!(crate::format::emit(json, false).unwrap(), "{\"v\":1e+20}\n");
242        assert_eq!(
243            crate::format::emit(toml, false).unwrap(),
244            "v = 100000000000000000000.0\n"
245        );
246    }
247
248    #[test]
249    fn native_scalar_rendering_preserves_existing_spellings() {
250        rendering::<serde_json::Value>();
251        rendering::<toml::Value>();
252        assert_eq!(serde_json::Value::Null.stringify(), None);
253        assert_eq!(
254            serde_json::Value::from(u64::MAX).stringify().as_deref(),
255            Some("18446744073709551615")
256        );
257        for spelling in [
258            "inf",
259            "-inf",
260            "nan",
261            "1979-05-27T07:32:00Z",
262            "1979-05-27",
263            "07:32:00",
264        ] {
265            assert_eq!(
266                toml::Value::parse_inline(spelling.into())
267                    .stringify()
268                    .as_deref(),
269                Some(spelling)
270            );
271        }
272    }
273}