Skip to main content

ruma_common/canonical_json/
value.rs

1use std::{collections::BTreeMap, fmt};
2
3use as_variant::as_variant;
4use js_int::{Int, UInt};
5use serde::{Deserialize, Serialize, de::Deserializer, ser::Serializer};
6use serde_json::{Value as JsonValue, to_string as to_json_string};
7
8use super::CanonicalJsonError;
9use crate::serde::{JsonCastable, JsonObject};
10
11/// The inner type of `CanonicalJsonValue::Object`.
12pub type CanonicalJsonObject = BTreeMap<String, CanonicalJsonValue>;
13
14impl<T> JsonCastable<CanonicalJsonObject> for T where T: JsonCastable<JsonObject> {}
15
16/// Represents a canonical JSON value as per the Matrix specification.
17#[derive(Clone, Default, Eq, PartialEq)]
18#[allow(clippy::exhaustive_enums)]
19pub enum CanonicalJsonValue {
20    /// Represents a JSON null value.
21    ///
22    /// ```
23    /// # use serde_json::json;
24    /// # use ruma_common::CanonicalJsonValue;
25    /// let v: CanonicalJsonValue = json!(null).try_into().unwrap();
26    /// ```
27    #[default]
28    Null,
29
30    /// Represents a JSON boolean.
31    ///
32    /// ```
33    /// # use serde_json::json;
34    /// # use ruma_common::CanonicalJsonValue;
35    /// let v: CanonicalJsonValue = json!(true).try_into().unwrap();
36    /// ```
37    Bool(bool),
38
39    /// Represents a JSON integer.
40    ///
41    /// ```
42    /// # use serde_json::json;
43    /// # use ruma_common::CanonicalJsonValue;
44    /// let v: CanonicalJsonValue = json!(12).try_into().unwrap();
45    /// ```
46    Integer(Int),
47
48    /// Represents a JSON string.
49    ///
50    /// ```
51    /// # use serde_json::json;
52    /// # use ruma_common::CanonicalJsonValue;
53    /// let v: CanonicalJsonValue = json!("a string").try_into().unwrap();
54    /// ```
55    String(String),
56
57    /// Represents a JSON array.
58    ///
59    /// ```
60    /// # use serde_json::json;
61    /// # use ruma_common::CanonicalJsonValue;
62    /// let v: CanonicalJsonValue = json!(["an", "array"]).try_into().unwrap();
63    /// ```
64    Array(Vec<CanonicalJsonValue>),
65
66    /// Represents a JSON object.
67    ///
68    /// The map is backed by a BTreeMap to guarantee the sorting of keys.
69    ///
70    /// ```
71    /// # use serde_json::json;
72    /// # use ruma_common::CanonicalJsonValue;
73    /// let v: CanonicalJsonValue = json!({ "an": "object" }).try_into().unwrap();
74    /// ```
75    Object(CanonicalJsonObject),
76}
77
78impl CanonicalJsonValue {
79    /// The type of this value.
80    pub fn json_type(&self) -> CanonicalJsonType {
81        match self {
82            Self::Null => CanonicalJsonType::Null,
83            Self::Bool(_) => CanonicalJsonType::Boolean,
84            Self::Integer(_) => CanonicalJsonType::Integer,
85            Self::String(_) => CanonicalJsonType::String,
86            Self::Array(_) => CanonicalJsonType::Array,
87            Self::Object(_) => CanonicalJsonType::Object,
88        }
89    }
90
91    /// If the `CanonicalJsonValue` is a `Bool`, return the inner value.
92    pub fn as_bool(&self) -> Option<bool> {
93        as_variant!(self, Self::Bool).copied()
94    }
95
96    /// If the `CanonicalJsonValue` is an `Integer`, return the inner value.
97    pub fn as_integer(&self) -> Option<Int> {
98        as_variant!(self, Self::Integer).copied()
99    }
100
101    /// If the `CanonicalJsonValue` is a `String`, return a reference to the inner value.
102    pub fn as_str(&self) -> Option<&str> {
103        as_variant!(self, Self::String)
104    }
105
106    /// If the `CanonicalJsonValue` is an `Array`, return a reference to the inner value.
107    pub fn as_array(&self) -> Option<&[CanonicalJsonValue]> {
108        as_variant!(self, Self::Array)
109    }
110
111    /// If the `CanonicalJsonValue` is an `Object`, return a reference to the inner value.
112    pub fn as_object(&self) -> Option<&CanonicalJsonObject> {
113        as_variant!(self, Self::Object)
114    }
115
116    /// If the `CanonicalJsonValue` is an `Array`, return a mutable reference to the inner value.
117    pub fn as_array_mut(&mut self) -> Option<&mut Vec<CanonicalJsonValue>> {
118        as_variant!(self, Self::Array)
119    }
120
121    /// If the `CanonicalJsonValue` is an `Object`, return a mutable reference to the inner value.
122    pub fn as_object_mut(&mut self) -> Option<&mut CanonicalJsonObject> {
123        as_variant!(self, Self::Object)
124    }
125
126    /// Returns `true` if the `CanonicalJsonValue` is a `Bool`.
127    pub fn is_bool(&self) -> bool {
128        matches!(self, Self::Bool(_))
129    }
130
131    /// Returns `true` if the `CanonicalJsonValue` is an `Integer`.
132    pub fn is_integer(&self) -> bool {
133        matches!(self, Self::Integer(_))
134    }
135
136    /// Returns `true` if the `CanonicalJsonValue` is a `String`.
137    pub fn is_string(&self) -> bool {
138        matches!(self, Self::String(_))
139    }
140
141    /// Returns `true` if the `CanonicalJsonValue` is an `Array`.
142    pub fn is_array(&self) -> bool {
143        matches!(self, Self::Array(_))
144    }
145
146    /// Returns `true` if the `CanonicalJsonValue` is an `Object`.
147    pub fn is_object(&self) -> bool {
148        matches!(self, Self::Object(_))
149    }
150}
151
152impl fmt::Debug for CanonicalJsonValue {
153    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
154        match *self {
155            Self::Null => formatter.debug_tuple("Null").finish(),
156            Self::Bool(v) => formatter.debug_tuple("Bool").field(&v).finish(),
157            Self::Integer(ref v) => fmt::Debug::fmt(v, formatter),
158            Self::String(ref v) => formatter.debug_tuple("String").field(v).finish(),
159            Self::Array(ref v) => {
160                formatter.write_str("Array(")?;
161                fmt::Debug::fmt(v, formatter)?;
162                formatter.write_str(")")
163            }
164            Self::Object(ref v) => {
165                formatter.write_str("Object(")?;
166                fmt::Debug::fmt(v, formatter)?;
167                formatter.write_str(")")
168            }
169        }
170    }
171}
172
173impl fmt::Display for CanonicalJsonValue {
174    /// Display this value as a string.
175    ///
176    /// This `Display` implementation is intentionally unaffected by any formatting parameters,
177    /// because adding extra whitespace or otherwise pretty-printing it would make it not the
178    /// canonical form anymore.
179    ///
180    /// If you want to pretty-print a `CanonicalJsonValue` for debugging purposes, use
181    /// one of `serde_json::{to_string_pretty, to_vec_pretty, to_writer_pretty}`.
182    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
183        write!(f, "{}", to_json_string(&self).map_err(|_| fmt::Error)?)
184    }
185}
186
187impl TryFrom<JsonValue> for CanonicalJsonValue {
188    type Error = CanonicalJsonError;
189
190    fn try_from(val: JsonValue) -> Result<Self, Self::Error> {
191        Ok(match val {
192            JsonValue::Bool(b) => Self::Bool(b),
193            JsonValue::Number(num) => {
194                // Treat float separately to get a better error message.
195                //
196                // We do several checks because the docs say that `num.is_f64()` is not guaranteed
197                // to return `false` for an integer in the future.
198                if !num.is_i64() && !num.is_u64() && num.is_f64() {
199                    return Err(CanonicalJsonError::InvalidType("float".to_owned()));
200                }
201
202                Self::Integer(
203                    num.as_i64()
204                        .and_then(|num| Int::try_from(num).ok())
205                        .ok_or(CanonicalJsonError::IntegerOutOfRange)?,
206                )
207            }
208            JsonValue::Array(vec) => {
209                Self::Array(vec.into_iter().map(TryInto::try_into).collect::<Result<Vec<_>, _>>()?)
210            }
211            JsonValue::String(string) => Self::String(string),
212            JsonValue::Object(obj) => Self::Object(
213                obj.into_iter()
214                    .map(|(k, v)| Ok((k, v.try_into()?)))
215                    .collect::<Result<CanonicalJsonObject, _>>()?,
216            ),
217            JsonValue::Null => Self::Null,
218        })
219    }
220}
221
222impl From<CanonicalJsonValue> for JsonValue {
223    fn from(val: CanonicalJsonValue) -> Self {
224        match val {
225            CanonicalJsonValue::Bool(b) => Self::Bool(b),
226            CanonicalJsonValue::Integer(int) => Self::Number(i64::from(int).into()),
227            CanonicalJsonValue::String(string) => Self::String(string),
228            CanonicalJsonValue::Array(vec) => {
229                Self::Array(vec.into_iter().map(Into::into).collect())
230            }
231            CanonicalJsonValue::Object(obj) => {
232                Self::Object(obj.into_iter().map(|(k, v)| (k, v.into())).collect())
233            }
234            CanonicalJsonValue::Null => Self::Null,
235        }
236    }
237}
238
239impl<T> JsonCastable<CanonicalJsonValue> for T {}
240
241macro_rules! variant_impls {
242    ($variant:ident($ty:ty)) => {
243        impl From<$ty> for CanonicalJsonValue {
244            fn from(val: $ty) -> Self {
245                Self::$variant(val.into())
246            }
247        }
248
249        impl PartialEq<$ty> for CanonicalJsonValue {
250            fn eq(&self, other: &$ty) -> bool {
251                match self {
252                    Self::$variant(val) => val == other,
253                    _ => false,
254                }
255            }
256        }
257
258        impl PartialEq<CanonicalJsonValue> for $ty {
259            fn eq(&self, other: &CanonicalJsonValue) -> bool {
260                match other {
261                    CanonicalJsonValue::$variant(val) => self == val,
262                    _ => false,
263                }
264            }
265        }
266    };
267}
268
269variant_impls!(Bool(bool));
270variant_impls!(Integer(Int));
271variant_impls!(String(String));
272variant_impls!(String(&str));
273variant_impls!(Array(Vec<CanonicalJsonValue>));
274variant_impls!(Object(CanonicalJsonObject));
275
276impl From<UInt> for CanonicalJsonValue {
277    fn from(value: UInt) -> Self {
278        Self::Integer(value.into())
279    }
280}
281
282impl Serialize for CanonicalJsonValue {
283    #[inline]
284    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
285    where
286        S: Serializer,
287    {
288        match self {
289            Self::Null => serializer.serialize_unit(),
290            Self::Bool(b) => serializer.serialize_bool(*b),
291            Self::Integer(n) => n.serialize(serializer),
292            Self::String(s) => serializer.serialize_str(s),
293            Self::Array(v) => v.serialize(serializer),
294            Self::Object(m) => {
295                use serde::ser::SerializeMap;
296                let mut map = serializer.serialize_map(Some(m.len()))?;
297                for (k, v) in m {
298                    map.serialize_entry(k, v)?;
299                }
300                map.end()
301            }
302        }
303    }
304}
305
306impl<'de> Deserialize<'de> for CanonicalJsonValue {
307    #[inline]
308    fn deserialize<D>(deserializer: D) -> Result<CanonicalJsonValue, D::Error>
309    where
310        D: Deserializer<'de>,
311    {
312        let val = JsonValue::deserialize(deserializer)?;
313        val.try_into().map_err(serde::de::Error::custom)
314    }
315}
316
317/// The possible types of a [`CanonicalJsonValue`].
318#[derive(Debug)]
319#[allow(clippy::exhaustive_enums)]
320pub enum CanonicalJsonType {
321    /// A JSON Object.
322    Object,
323
324    /// A JSON String.
325    String,
326
327    /// A JSON Integer.
328    Integer,
329
330    /// A JSON Array.
331    Array,
332
333    /// A JSON Boolean.
334    Boolean,
335
336    /// JSON Null.
337    Null,
338}
339
340#[cfg(test)]
341mod tests {
342    use serde_json::json;
343
344    use super::CanonicalJsonValue;
345
346    #[test]
347    fn to_string() {
348        const CANONICAL_STR: &str = r#"{"city":"London","street":"10 Downing Street"}"#;
349
350        let json: CanonicalJsonValue =
351            json!({ "city": "London", "street": "10 Downing Street" }).try_into().unwrap();
352
353        assert_eq!(format!("{json}"), CANONICAL_STR);
354        assert_eq!(format!("{json:#}"), CANONICAL_STR);
355    }
356}