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}