Skip to main content

value_lang/
serde_impl.rs

1//! `serde` support for [`Value`], behind the `serde` feature.
2//!
3//! A [`Value`] is a bit pattern, not a self-describing shape, so it serializes
4//! through its [`Unpacked`](crate::Unpacked) kinds instead. A symbol is written as
5//! its raw 32-bit id — the same form [`Symbol`] itself uses — because the id is all
6//! that survives without the issuing interner. Deserializing a symbol rejects `0`,
7//! which is never a valid id.
8
9use core::fmt;
10
11use intern_lang::Symbol;
12use serde::de::{self, Deserializer};
13use serde::ser::Serializer;
14use serde::{Deserialize, Serialize};
15
16use crate::{Unpacked, Value};
17
18/// The wire shape of a [`Value`]: one externally-tagged enum, symbols as raw ids.
19#[derive(Serialize, Deserialize)]
20enum Repr {
21    Nil,
22    Bool(bool),
23    Int(i32),
24    Float(f64),
25    Sym(u32),
26}
27
28impl Serialize for Value {
29    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
30    where
31        S: Serializer,
32    {
33        let repr = match self.unpack() {
34            Unpacked::Nil => Repr::Nil,
35            Unpacked::Bool(b) => Repr::Bool(b),
36            Unpacked::Int(n) => Repr::Int(n),
37            Unpacked::Float(f) => Repr::Float(f),
38            Unpacked::Sym(s) => Repr::Sym(s.as_u32()),
39        };
40        repr.serialize(serializer)
41    }
42}
43
44impl<'de> Deserialize<'de> for Value {
45    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
46    where
47        D: Deserializer<'de>,
48    {
49        let value = match Repr::deserialize(deserializer)? {
50            Repr::Nil => Value::nil(),
51            Repr::Bool(b) => Value::bool(b),
52            Repr::Int(n) => Value::int(n),
53            Repr::Float(f) => Value::float(f),
54            Repr::Sym(id) => {
55                let symbol = Symbol::from_u32(id).ok_or_else(|| de::Error::custom(ZeroSymbol))?;
56                Value::sym(symbol)
57            }
58        };
59        Ok(value)
60    }
61}
62
63/// A zero-sized error message, so the custom error needs no allocation.
64struct ZeroSymbol;
65
66impl fmt::Display for ZeroSymbol {
67    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
68        f.write_str("invalid symbol id 0")
69    }
70}
71
72#[cfg(test)]
73mod tests {
74    #![allow(clippy::unwrap_used)]
75
76    use crate::Value;
77    use intern_lang::Symbol;
78
79    #[test]
80    fn test_json_roundtrip_every_kind() {
81        let s = Symbol::from_u32(11).unwrap();
82        for v in [
83            Value::nil(),
84            Value::bool(true),
85            Value::int(-5),
86            Value::float(2.5),
87            Value::sym(s),
88        ] {
89            let json = serde_json::to_string(&v).unwrap();
90            let back: Value = serde_json::from_str(&json).unwrap();
91            assert_eq!(back, v);
92        }
93    }
94
95    #[test]
96    fn test_symbol_serializes_as_raw_id() {
97        let s = Symbol::from_u32(42).unwrap();
98        let json = serde_json::to_string(&Value::sym(s)).unwrap();
99        assert_eq!(json, r#"{"Sym":42}"#);
100    }
101
102    #[test]
103    fn test_zero_symbol_id_is_rejected() {
104        let err = serde_json::from_str::<Value>(r#"{"Sym":0}"#);
105        assert!(err.is_err());
106    }
107}