ruma_serde/
strings.rs

1use std::{collections::BTreeMap, convert::TryInto, fmt, marker::PhantomData};
2
3use js_int::{Int, UInt};
4use serde::{
5    de::{self, Deserializer, IntoDeserializer as _, MapAccess, Visitor},
6    ser::Serializer,
7    Deserialize, Serialize,
8};
9
10/// Serde deserialization decorator to map empty Strings to None,
11/// and forward non-empty Strings to the Deserialize implementation for T.
12/// Useful for the typical
13/// "A room with an X event with an absent, null, or empty Y field
14/// should be treated the same as a room with no such event."
15/// formulation in the spec.
16///
17/// To be used like this:
18/// `#[serde(default, deserialize_with = "empty_string_as_none")]`
19/// Relevant serde issue: <https://github.com/serde-rs/serde/issues/1425>
20pub fn empty_string_as_none<'de, D, T>(de: D) -> Result<Option<T>, D::Error>
21where
22    D: Deserializer<'de>,
23    T: Deserialize<'de>,
24{
25    let opt = Option::<String>::deserialize(de)?;
26    match opt.as_deref() {
27        None | Some("") => Ok(None),
28        // If T = String, like in m.room.name, the second deserialize is actually superfluous.
29        // TODO: optimize that somehow?
30        Some(s) => T::deserialize(s.into_deserializer()).map(Some),
31    }
32}
33
34/// Serde serializiation decorator to map None to an empty String,
35/// and forward Somes to the Serialize implementation for T.
36///
37/// To be used like this:
38/// `#[serde(serialize_with = "empty_string_as_none")]`
39pub fn none_as_empty_string<T: Serialize, S>(
40    value: &Option<T>,
41    serializer: S,
42) -> Result<S::Ok, S::Error>
43where
44    S: Serializer,
45{
46    match value {
47        Some(x) => x.serialize(serializer),
48        None => serializer.serialize_str(""),
49    }
50}
51
52/// Take either an integer number or a string and deserialize to an integer number.
53///
54/// To be used like this:
55/// `#[serde(deserialize_with = "deserialize_v1_powerlevel")]`
56pub fn deserialize_v1_powerlevel<'de, D>(de: D) -> Result<Int, D::Error>
57where
58    D: Deserializer<'de>,
59{
60    struct IntOrStringVisitor;
61
62    impl<'de> Visitor<'de> for IntOrStringVisitor {
63        type Value = Int;
64
65        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
66            formatter.write_str("an integer or a string")
67        }
68
69        fn visit_i8<E: de::Error>(self, v: i8) -> Result<Self::Value, E> {
70            Ok(v.into())
71        }
72
73        fn visit_i16<E: de::Error>(self, v: i16) -> Result<Self::Value, E> {
74            Ok(v.into())
75        }
76
77        fn visit_i32<E: de::Error>(self, v: i32) -> Result<Self::Value, E> {
78            Ok(v.into())
79        }
80
81        fn visit_i64<E: de::Error>(self, v: i64) -> Result<Self::Value, E> {
82            v.try_into().map_err(E::custom)
83        }
84
85        fn visit_i128<E: de::Error>(self, v: i128) -> Result<Self::Value, E> {
86            v.try_into().map_err(E::custom)
87        }
88
89        fn visit_u8<E: de::Error>(self, v: u8) -> Result<Self::Value, E> {
90            Ok(v.into())
91        }
92
93        fn visit_u16<E: de::Error>(self, v: u16) -> Result<Self::Value, E> {
94            Ok(v.into())
95        }
96
97        fn visit_u32<E: de::Error>(self, v: u32) -> Result<Self::Value, E> {
98            Ok(v.into())
99        }
100
101        fn visit_u64<E: de::Error>(self, v: u64) -> Result<Self::Value, E> {
102            v.try_into().map_err(E::custom)
103        }
104
105        fn visit_u128<E: de::Error>(self, v: u128) -> Result<Self::Value, E> {
106            v.try_into().map_err(E::custom)
107        }
108
109        fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
110            let trimmed = v.trim();
111
112            match trimmed.strip_prefix('+') {
113                Some(without) => without.parse::<UInt>().map(|u| u.into()).map_err(E::custom),
114                None => trimmed.parse().map_err(E::custom),
115            }
116        }
117    }
118
119    de.deserialize_any(IntOrStringVisitor)
120}
121
122/// Take a BTreeMap with values of either an integer number or a string and deserialize
123/// those to integer numbers.
124///
125/// To be used like this:
126/// `#[serde(deserialize_with = "btreemap_deserialize_v1_powerlevel_values")]`
127pub fn btreemap_deserialize_v1_powerlevel_values<'de, D, T>(
128    de: D,
129) -> Result<BTreeMap<T, Int>, D::Error>
130where
131    D: Deserializer<'de>,
132    T: Deserialize<'de> + Ord,
133{
134    #[repr(transparent)]
135    struct IntWrap(Int);
136
137    impl<'de> Deserialize<'de> for IntWrap {
138        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
139        where
140            D: Deserializer<'de>,
141        {
142            deserialize_v1_powerlevel(deserializer).map(IntWrap)
143        }
144    }
145
146    struct IntMapVisitor<T> {
147        _phantom: PhantomData<T>,
148    }
149
150    impl<T> IntMapVisitor<T> {
151        fn new() -> Self {
152            Self { _phantom: PhantomData }
153        }
154    }
155
156    impl<'de, T> Visitor<'de> for IntMapVisitor<T>
157    where
158        T: Deserialize<'de> + Ord,
159    {
160        type Value = BTreeMap<T, Int>;
161
162        fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
163            formatter.write_str("a map with integers or stings as values")
164        }
165
166        fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
167            let mut res = BTreeMap::new();
168
169            while let Some((k, IntWrap(v))) = map.next_entry()? {
170                res.insert(k, v);
171            }
172
173            Ok(res)
174        }
175    }
176
177    de.deserialize_map(IntMapVisitor::new())
178}
179
180#[cfg(test)]
181mod tests {
182    use js_int::{int, Int};
183    use matches::assert_matches;
184    use serde::Deserialize;
185
186    use super::deserialize_v1_powerlevel;
187
188    #[derive(Debug, Deserialize)]
189    struct Test {
190        #[serde(deserialize_with = "deserialize_v1_powerlevel")]
191        num: Int,
192    }
193
194    #[test]
195    fn int_or_string() -> serde_json::Result<()> {
196        assert_matches!(
197            serde_json::from_value::<Test>(serde_json::json!({ "num": "0" }))?,
198            Test { num } if num == int!(0)
199        );
200
201        Ok(())
202    }
203
204    #[test]
205    fn weird_plus_string() -> serde_json::Result<()> {
206        assert_matches!(
207            serde_json::from_value::<Test>(serde_json::json!({ "num": "  +0000000001000   " }))?,
208            Test { num } if num == int!(1000)
209        );
210
211        Ok(())
212    }
213
214    #[test]
215    fn weird_minus_string() -> serde_json::Result<()> {
216        assert_matches!(
217            serde_json::from_value::<Test>(serde_json::json!({ "num": "  \n\n-0000000000000001000   " }))?,
218            Test { num } if num == int!(-1000)
219        );
220
221        Ok(())
222    }
223}