Skip to main content

tanzim_value/
serde.rs

1//! [`serde`] deserialization support (`serde` Cargo feature).
2//!
3//! Implements [`serde::Deserializer`] over the configuration tree so a [`Value`] or
4//! [`LocatedValue`] can be turned into any [`serde::Deserialize`] type. [`LocatedValue`] drives the
5//! same [`Value`] deserializer but, on failure, stamps the offending node's [`crate::Location`]
6//! onto the error (see [`crate::Error::Deserialize`]). This is the mirror image of
7//! `tanzim-validate`'s `SchemaValue` visitor, which goes serde → [`Value`].
8
9use crate::{Error, LocatedValue, Value};
10use serde::de::{
11    Deserialize, DeserializeSeed, Deserializer, EnumAccess, MapAccess, SeqAccess, VariantAccess,
12    Visitor,
13};
14use serde::forward_to_deserialize_any;
15use std::fmt::Display;
16
17/// Build an unlocated deserialize error; the nearest [`LocatedValue`] fills in the location.
18fn custom(message: impl Display) -> Error {
19    Error::Deserialize {
20        text: String::new(),
21        message: message.to_string(),
22        location: None,
23    }
24}
25
26impl serde::de::Error for Error {
27    fn custom<T: Display>(message: T) -> Self {
28        custom(message)
29    }
30}
31
32// --- convenience API ---
33
34impl Value {
35    /// Deserialize this value into `T`. Errors carry no source location; prefer
36    /// [`LocatedValue::try_deserialize`] when a location is available.
37    pub fn try_deserialize<'de, T: Deserialize<'de>>(&'de self) -> Result<T, Error> {
38        T::deserialize(self)
39    }
40}
41
42impl LocatedValue {
43    /// Deserialize this value into `T`, attaching the nearest node's [`crate::Location`] to any
44    /// error that does not already carry one.
45    pub fn try_deserialize<'de, T: Deserialize<'de>>(&'de self) -> Result<T, Error> {
46        T::deserialize(self)
47    }
48}
49
50// --- Deserializer for &Value (the workhorse) ---
51
52impl<'de> Deserializer<'de> for &'de Value {
53    type Error = Error;
54
55    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
56        match self {
57            Value::Bool(value) => visitor.visit_bool(*value),
58            Value::Int(value) => visitor.visit_i64(*value as i64),
59            Value::Float(value) => visitor.visit_f64(*value),
60            Value::String(value) => visitor.visit_borrowed_str(value),
61            Value::List(items) => visitor.visit_seq(SeqDeserializer::new(items)),
62            Value::Map(map) => visitor.visit_map(MapDeserializer::new(map.entries())),
63            Value::Null => visitor.visit_unit(),
64        }
65    }
66
67    fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
68        match self {
69            Value::Null => visitor.visit_none(),
70            _ => visitor.visit_some(self),
71        }
72    }
73
74    fn deserialize_newtype_struct<V: Visitor<'de>>(
75        self,
76        _name: &'static str,
77        visitor: V,
78    ) -> Result<V::Value, Error> {
79        visitor.visit_newtype_struct(self)
80    }
81
82    fn deserialize_enum<V: Visitor<'de>>(
83        self,
84        _name: &'static str,
85        _variants: &'static [&'static str],
86        visitor: V,
87    ) -> Result<V::Value, Error> {
88        visitor.visit_enum(EnumDeserializer::new(self)?)
89    }
90
91    forward_to_deserialize_any! {
92        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
93        bytes byte_buf unit unit_struct seq tuple tuple_struct map struct
94        identifier ignored_any
95    }
96}
97
98// --- Deserializer for &LocatedValue (delegates to &Value, stamps location on error) ---
99
100impl<'de> Deserializer<'de> for &'de LocatedValue {
101    type Error = Error;
102
103    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
104        self.value()
105            .deserialize_any(visitor)
106            .map_err(|error| error.or_location(self.location()))
107    }
108
109    fn deserialize_option<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
110        self.value()
111            .deserialize_option(visitor)
112            .map_err(|error| error.or_location(self.location()))
113    }
114
115    fn deserialize_newtype_struct<V: Visitor<'de>>(
116        self,
117        name: &'static str,
118        visitor: V,
119    ) -> Result<V::Value, Error> {
120        self.value()
121            .deserialize_newtype_struct(name, visitor)
122            .map_err(|error| error.or_location(self.location()))
123    }
124
125    fn deserialize_enum<V: Visitor<'de>>(
126        self,
127        name: &'static str,
128        variants: &'static [&'static str],
129        visitor: V,
130    ) -> Result<V::Value, Error> {
131        self.value()
132            .deserialize_enum(name, variants, visitor)
133            .map_err(|error| error.or_location(self.location()))
134    }
135
136    forward_to_deserialize_any! {
137        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
138        bytes byte_buf unit unit_struct seq tuple tuple_struct map struct
139        identifier ignored_any
140    }
141}
142
143// --- access helpers ---
144
145struct SeqDeserializer<'de> {
146    iter: std::slice::Iter<'de, LocatedValue>,
147}
148
149impl<'de> SeqDeserializer<'de> {
150    fn new(items: &'de [LocatedValue]) -> Self {
151        Self { iter: items.iter() }
152    }
153}
154
155impl<'de> SeqAccess<'de> for SeqDeserializer<'de> {
156    type Error = Error;
157
158    fn next_element_seed<T: DeserializeSeed<'de>>(
159        &mut self,
160        seed: T,
161    ) -> Result<Option<T::Value>, Error> {
162        match self.iter.next() {
163            Some(element) => seed.deserialize(element).map(Some),
164            None => Ok(None),
165        }
166    }
167
168    fn size_hint(&self) -> Option<usize> {
169        Some(self.iter.len())
170    }
171}
172
173struct MapDeserializer<'de> {
174    iter: std::slice::Iter<'de, (String, LocatedValue)>,
175    value: Option<&'de LocatedValue>,
176}
177
178impl<'de> MapDeserializer<'de> {
179    fn new(entries: &'de [(String, LocatedValue)]) -> Self {
180        Self {
181            iter: entries.iter(),
182            value: None,
183        }
184    }
185}
186
187impl<'de> MapAccess<'de> for MapDeserializer<'de> {
188    type Error = Error;
189
190    fn next_key_seed<K: DeserializeSeed<'de>>(
191        &mut self,
192        seed: K,
193    ) -> Result<Option<K::Value>, Error> {
194        match self.iter.next() {
195            Some((key, value)) => {
196                self.value = Some(value);
197                seed.deserialize(MapKeyDeserializer { key }).map(Some)
198            }
199            None => Ok(None),
200        }
201    }
202
203    fn next_value_seed<V: DeserializeSeed<'de>>(&mut self, seed: V) -> Result<V::Value, Error> {
204        let value = self
205            .value
206            .take()
207            .expect("next_value_seed called before next_key_seed");
208        seed.deserialize(value)
209    }
210
211    fn size_hint(&self) -> Option<usize> {
212        Some(self.iter.len())
213    }
214}
215
216/// Deserializes a map key (always a string) into an identifier or `String`.
217struct MapKeyDeserializer<'de> {
218    key: &'de str,
219}
220
221impl<'de> Deserializer<'de> for MapKeyDeserializer<'de> {
222    type Error = Error;
223
224    fn deserialize_any<V: Visitor<'de>>(self, visitor: V) -> Result<V::Value, Error> {
225        visitor.visit_borrowed_str(self.key)
226    }
227
228    forward_to_deserialize_any! {
229        bool i8 i16 i32 i64 i128 u8 u16 u32 u64 u128 f32 f64 char str string
230        bytes byte_buf option unit unit_struct newtype_struct seq tuple
231        tuple_struct map struct enum identifier ignored_any
232    }
233}
234
235/// Externally-tagged enum access: a bare string is a unit variant; a single-entry map is a variant
236/// carrying a payload.
237struct EnumDeserializer<'de> {
238    variant: &'de str,
239    value: Option<&'de LocatedValue>,
240}
241
242impl<'de> EnumDeserializer<'de> {
243    fn new(value: &'de Value) -> Result<Self, Error> {
244        match value {
245            Value::String(variant) => Ok(Self {
246                variant,
247                value: None,
248            }),
249            Value::Map(map) if map.len() == 1 => {
250                let (variant, value) = &map.entries()[0];
251                Ok(Self {
252                    variant,
253                    value: Some(value),
254                })
255            }
256            other => Err(custom(format!(
257                "cannot deserialize enum from {}",
258                other.type_name()
259            ))),
260        }
261    }
262}
263
264impl<'de> EnumAccess<'de> for EnumDeserializer<'de> {
265    type Error = Error;
266    type Variant = VariantDeserializer<'de>;
267
268    fn variant_seed<V: DeserializeSeed<'de>>(
269        self,
270        seed: V,
271    ) -> Result<(V::Value, Self::Variant), Error> {
272        let variant = seed.deserialize(MapKeyDeserializer { key: self.variant })?;
273        Ok((variant, VariantDeserializer { value: self.value }))
274    }
275}
276
277struct VariantDeserializer<'de> {
278    value: Option<&'de LocatedValue>,
279}
280
281impl<'de> VariantAccess<'de> for VariantDeserializer<'de> {
282    type Error = Error;
283
284    fn unit_variant(self) -> Result<(), Error> {
285        match self.value {
286            None => Ok(()),
287            Some(_) => Err(custom("expected a unit variant, found a payload")),
288        }
289    }
290
291    fn newtype_variant_seed<T: DeserializeSeed<'de>>(self, seed: T) -> Result<T::Value, Error> {
292        match self.value {
293            Some(value) => seed.deserialize(value),
294            None => Err(custom("expected a newtype variant payload")),
295        }
296    }
297
298    fn tuple_variant<V: Visitor<'de>>(self, _len: usize, visitor: V) -> Result<V::Value, Error> {
299        match self.value {
300            Some(value) => value.deserialize_any(visitor),
301            None => Err(custom("expected a tuple variant payload")),
302        }
303    }
304
305    fn struct_variant<V: Visitor<'de>>(
306        self,
307        _fields: &'static [&'static str],
308        visitor: V,
309    ) -> Result<V::Value, Error> {
310        match self.value {
311            Some(value) => value.deserialize_any(visitor),
312            None => Err(custom("expected a struct variant payload")),
313        }
314    }
315}
316
317#[cfg(test)]
318mod tests {
319    use crate::{LocatedValue, Location, Map, Value};
320    use serde::Deserialize;
321
322    fn location() -> Location {
323        Location::at("test", "cfg", Some(1), Some(1), None)
324    }
325
326    fn located(value: Value) -> LocatedValue {
327        LocatedValue::new(value, location())
328    }
329
330    #[derive(Deserialize, Debug, PartialEq)]
331    struct Config {
332        name: String,
333        port: u16,
334        ratio: f64,
335        enabled: bool,
336        tags: Vec<String>,
337        nickname: Option<String>,
338        level: Level,
339    }
340
341    #[derive(Deserialize, Debug, PartialEq)]
342    #[serde(rename_all = "lowercase")]
343    enum Level {
344        Debug,
345        Info,
346        Warn,
347    }
348
349    fn sample_tree() -> LocatedValue {
350        let mut map = Map::new();
351        map.insert("name".into(), located(Value::String("app".into())));
352        map.insert("port".into(), located(Value::Int(8080)));
353        map.insert("ratio".into(), located(Value::Float(0.5)));
354        map.insert("enabled".into(), located(Value::Bool(true)));
355        map.insert(
356            "tags".into(),
357            located(Value::List(vec![
358                located(Value::String("a".into())),
359                located(Value::String("b".into())),
360            ])),
361        );
362        map.insert("nickname".into(), located(Value::Null));
363        map.insert("level".into(), located(Value::String("info".into())));
364        located(Value::Map(map))
365    }
366
367    #[test]
368    fn deserializes_nested_struct() {
369        let tree = sample_tree();
370        let config: Config = tree.try_deserialize().unwrap();
371        assert_eq!(
372            config,
373            Config {
374                name: "app".into(),
375                port: 8080,
376                ratio: 0.5,
377                enabled: true,
378                tags: vec!["a".into(), "b".into()],
379                nickname: None,
380                level: Level::Info,
381            }
382        );
383    }
384
385    #[test]
386    fn type_mismatch_error_carries_location() {
387        #[derive(Deserialize, Debug)]
388        struct Port {
389            #[allow(dead_code)]
390            port: u16,
391        }
392
393        let mut map = Map::new();
394        map.insert(
395            "port".into(),
396            LocatedValue::new(
397                Value::String("nope".into()),
398                Location::at("file", "config.toml", Some(4), Some(9), None),
399            ),
400        );
401        let tree = located(Value::Map(map));
402
403        let error = tree.try_deserialize::<Port>().unwrap_err();
404        assert!(
405            matches!(
406                error,
407                crate::Error::Deserialize {
408                    location: Some(_),
409                    ..
410                }
411            ),
412            "expected a located deserialize error, got {error:?}"
413        );
414        let message = error.to_string();
415        assert!(
416            message.contains("file:config.toml:4:9"),
417            "message should point at the offending node: {message}"
418        );
419    }
420
421    #[test]
422    fn deserializes_borrowed_str_zero_copy() {
423        #[derive(Deserialize)]
424        struct Borrowed<'a> {
425            name: &'a str,
426        }
427
428        let mut map = Map::new();
429        map.insert("name".into(), located(Value::String("app".into())));
430        let tree = located(Value::Map(map));
431
432        let borrowed: Borrowed = tree.try_deserialize().unwrap();
433        assert_eq!(borrowed.name, "app");
434    }
435
436    #[test]
437    fn deserializes_from_json_produced_tree_shape() {
438        // A tree whose leaves came from an ordinary parser still deserializes.
439        let mut inner = Map::new();
440        inner.insert("port".into(), located(Value::Int(1)));
441        let mut map = Map::new();
442        map.insert("name".into(), located(Value::String("x".into())));
443        map.insert("port".into(), located(Value::Int(2)));
444        map.insert("ratio".into(), located(Value::Float(1.0)));
445        map.insert("enabled".into(), located(Value::Bool(false)));
446        map.insert("tags".into(), located(Value::List(vec![])));
447        map.insert("nickname".into(), located(Value::String("n".into())));
448        map.insert("level".into(), located(Value::String("warn".into())));
449        let tree = located(Value::Map(map));
450        let config: Config = tree.try_deserialize().unwrap();
451        assert_eq!(config.nickname, Some("n".into()));
452        assert_eq!(config.level, Level::Warn);
453        let _ = inner;
454    }
455}