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