typed_num/
serde.rs

1use crate::Num;
2use serde::{
3    de::{self, Deserializer, Visitor},
4    ser::Serializer,
5    Deserialize, Serialize,
6};
7use std::fmt;
8
9impl<const N: i64> Serialize for Num<N> {
10    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
11    where
12        S: Serializer,
13    {
14        serializer.serialize_i64(N)
15    }
16}
17
18impl<'de, const N: i64> Deserialize<'de> for Num<N> {
19    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
20    where
21        D: Deserializer<'de>,
22    {
23        deserializer.deserialize_i64(NumVisitor::<N>)
24    }
25}
26
27struct NumVisitor<const N: i64>;
28
29impl<'de, const N: i64> Visitor<'de> for NumVisitor<N> {
30    type Value = Num<N>;
31
32    fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
33        formatter.write_str(&format!("{N}"))
34    }
35
36    fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
37    where
38        E: de::Error,
39    {
40        if N == value {
41            Ok(Default::default())
42        } else {
43            Err(E::custom(format!("not {N}")))
44        }
45    }
46
47    fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
48    where
49        E: de::Error,
50    {
51        match i64::try_from(value) {
52            Ok(v) if v == N => Ok(Default::default()),
53            _ => Err(E::custom(format!("not {N}"))),
54        }
55    }
56}
57
58#[cfg(test)]
59mod tests {
60    use crate::Num;
61    use serde_derive::{Deserialize, Serialize};
62
63    #[test]
64    fn test_typed_num() {
65        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
66        struct Config {
67            version: Num<3>,
68            hash: String,
69        }
70
71        const OLD_CONFIG: &str = r#"
72version = 2
73hash = "9g8OSL4Ty5nQ62yYBOOmujNAfFrkVZVUMS1iQJ85QlU="
74"#;
75
76        const NEW_CONFIG: &str = r#"
77version = 3
78hash = "OoXQqX+ZRNE7VLmkbhGlj2R1B3n3gAJAaGh9kS0mAv8="
79"#;
80
81        let new_config = Config {
82            version: Num,
83            hash: "OoXQqX+ZRNE7VLmkbhGlj2R1B3n3gAJAaGh9kS0mAv8=".to_string(),
84        };
85
86        assert!(toml::from_str::<Config>(OLD_CONFIG).is_err());
87        assert_eq!(toml::from_str::<Config>(NEW_CONFIG).unwrap(), new_config);
88        assert_eq!(
89            toml::to_string_pretty(&new_config).unwrap().trim(),
90            NEW_CONFIG.trim()
91        );
92    }
93
94    #[test]
95    fn test_typed_num_negative() {
96        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
97        struct ConfigNegative {
98            version: Num<-1>,
99            description: String,
100        }
101
102        const CONFIG_WITH_NEGATIVE_VAL: &str = r#"
103version = -1
104description = "negative one"
105"#;
106        const CONFIG_WITH_WRONG_NEGATIVE_VAL: &str = r#"
107version = -2
108description = "wrong negative one"
109"#;
110
111        let config_negative = ConfigNegative {
112            version: Num,
113            description: "negative one".to_string(),
114        };
115
116        assert!(toml::from_str::<ConfigNegative>(CONFIG_WITH_WRONG_NEGATIVE_VAL).is_err());
117        assert_eq!(
118            toml::from_str::<ConfigNegative>(CONFIG_WITH_NEGATIVE_VAL).unwrap(),
119            config_negative
120        );
121        assert_eq!(
122            toml::to_string_pretty(&config_negative).unwrap().trim(),
123            CONFIG_WITH_NEGATIVE_VAL.trim()
124        );
125    }
126
127    #[test]
128    fn test_serde_typed_num_i64_min() {
129        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
130        struct SerdeConfigMin {
131            version: Num<{ i64::MIN }>,
132            data: String,
133        }
134
135        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
136        struct SerdeConfigMinMinusOne {
137            version: Num<{ i64::MIN + 1 }>,
138            data: String,
139        }
140
141        let config_min = SerdeConfigMin {
142            version: Num,
143            data: "test_data_min".to_string(),
144        };
145
146        let config_min_toml = toml::to_string_pretty(&config_min).unwrap();
147        let decoded_min: SerdeConfigMin = toml::from_str(&config_min_toml).unwrap();
148        assert_eq!(decoded_min, config_min);
149
150        let config_min_minus_one = SerdeConfigMinMinusOne {
151            version: Num,
152            data: "test_data_min_minus_one".to_string(),
153        };
154
155        let config_min_minus_one_toml = toml::to_string_pretty(&config_min_minus_one).unwrap();
156        let decode_result_min_minus_one: Result<SerdeConfigMin, _> =
157            toml::from_str(&config_min_minus_one_toml);
158
159        assert!(decode_result_min_minus_one.is_err());
160    }
161
162    #[test]
163    fn test_serde_typed_num_i64_max() {
164        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
165        struct SerdeConfigMax {
166            version: Num<{ i64::MAX }>,
167            data: String,
168        }
169
170        #[derive(Debug, PartialEq, Eq, Serialize, Deserialize)]
171        struct SerdeConfigMaxMinusOne {
172            version: Num<{ i64::MAX - 1 }>,
173            data: String,
174        }
175
176        let config_max = SerdeConfigMax {
177            version: Num,
178            data: "test_data_max".to_string(),
179        };
180
181        let config_max_toml = toml::to_string_pretty(&config_max).unwrap();
182        let decoded_max: SerdeConfigMax = toml::from_str(&config_max_toml).unwrap();
183        assert_eq!(decoded_max, config_max);
184
185        let config_max_minus_one = SerdeConfigMaxMinusOne {
186            version: Num,
187            data: "test_data_max_minus_one".to_string(),
188        };
189
190        let config_max_minus_one_toml = toml::to_string_pretty(&config_max_minus_one).unwrap();
191        let decode_result_max_minus_one: Result<SerdeConfigMax, _> =
192            toml::from_str(&config_max_minus_one_toml);
193
194        assert!(decode_result_max_minus_one.is_err());
195    }
196}