Skip to main content

ruma_common/
canonical_json.rs

1//! Canonical JSON types and related functions.
2
3use std::fmt;
4
5use serde::Serialize;
6use serde_json::Value as JsonValue;
7
8mod macros;
9mod redaction;
10mod serializer;
11mod value;
12
13pub use self::{
14    redaction::{
15        RedactedBecause, RedactingSerializer, RedactionEvent, redact, redact_content_in_place,
16        redact_in_place,
17    },
18    serializer::Serializer,
19    value::{CanonicalJsonObject, CanonicalJsonType, CanonicalJsonValue},
20};
21#[doc(inline)]
22pub use crate::assert_to_canonical_json_eq;
23
24/// Fallible conversion from any value that implements [`Serialize`] to a [`CanonicalJsonValue`].
25///
26/// This behaves similarly to [`serde_json::to_value()`], except for the following restrictions
27/// which return errors:
28///
29/// - Integers must be in the range accepted by [`js_int::Int`].
30/// - Floats and bytes are not serializable.
31/// - Booleans and integers cannot be used as keys for an object. `serde_json` accepts those types
32///   as keys by serializing them as strings.
33/// - The same key cannot be serialized twice in an object. `serde_json` uses the last value that is
34///   serialized for the same key.
35pub fn to_canonical_value<T: Serialize>(
36    value: T,
37) -> Result<CanonicalJsonValue, CanonicalJsonError> {
38    value.serialize(Serializer)
39}
40
41/// Fallible conversion from a `serde_json::Map` to a `CanonicalJsonObject`.
42pub fn try_from_json_map(
43    json: serde_json::Map<String, JsonValue>,
44) -> Result<CanonicalJsonObject, CanonicalJsonError> {
45    json.into_iter().map(|(k, v)| Ok((k, v.try_into()?))).collect()
46}
47
48/// The set of possible errors when serializing to canonical JSON.
49#[derive(Debug)]
50#[allow(clippy::exhaustive_enums)]
51pub enum CanonicalJsonError {
52    /// The integer value is out of the range of [`js_int::Int`].
53    IntegerOutOfRange,
54
55    /// The given type cannot be serialized to canonical JSON.
56    InvalidType(String),
57
58    /// The given type cannot be serialized to an object key.
59    InvalidObjectKeyType(String),
60
61    /// The same object key was serialized twice.
62    DuplicateObjectKey(String),
63
64    /// An error occurred while re-serializing a [`serde_json::value::RawValue`].
65    InvalidRawValue(serde_json::Error),
66
67    /// An other error happened.
68    Other(String),
69}
70
71impl fmt::Display for CanonicalJsonError {
72    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
73        match self {
74            Self::IntegerOutOfRange => f.write_str("integer is out of the range of `js_int::Int`"),
75            Self::InvalidType(ty) => write!(f, "{ty} cannot be serialized as canonical JSON"),
76            Self::InvalidObjectKeyType(ty) => {
77                write!(f, "{ty} cannot be used as an object key, expected a string type")
78            }
79            Self::InvalidRawValue(error) => {
80                write!(f, "invalid raw value: {error}")
81            }
82            Self::DuplicateObjectKey(key) => write!(f, "duplicate object key `{key}`"),
83            Self::Other(msg) => f.write_str(msg),
84        }
85    }
86}
87
88impl std::error::Error for CanonicalJsonError {}
89
90impl serde::ser::Error for CanonicalJsonError {
91    fn custom<T>(msg: T) -> Self
92    where
93        T: fmt::Display,
94    {
95        Self::Other(msg.to_string())
96    }
97}
98
99/// The possible types of a JSON value.
100#[derive(Debug)]
101#[allow(clippy::exhaustive_enums)]
102pub enum JsonType {
103    /// A JSON Object.
104    Object,
105
106    /// A JSON String.
107    String,
108
109    /// A JSON Integer.
110    Integer,
111
112    /// A JSON Array.
113    Array,
114
115    /// A JSON Boolean.
116    Boolean,
117
118    /// JSON Null.
119    Null,
120}
121
122/// Helper trait to interact with a [`CanonicalJsonObject`].
123pub trait CanonicalJsonObjectExt {
124    /// Get the given field as an object.
125    ///
126    /// # Parameters
127    ///
128    /// * `field`: The name of the field to access.
129    /// * `path`: The full path of the field that will be used in errors. This can be different than
130    ///   the `field`, to clarify if this is a field nested under several objects.
131    ///
132    /// # Errors
133    ///
134    /// Returns an error if the field is invalid.
135    fn get_as_object(
136        &self,
137        field: &str,
138        path: impl Into<String>,
139    ) -> Result<Option<&CanonicalJsonObject>, CanonicalJsonFieldError>;
140
141    /// Get the given required field as an object.
142    ///
143    /// # Parameters
144    ///
145    /// * `field`: The name of the field to access.
146    /// * `path`: The full path of the field that will be used in errors. This can be different than
147    ///   the `field`, to clarify if this is a field nested under several objects.
148    ///
149    /// # Errors
150    ///
151    /// Returns an error if the field is missing or invalid.
152    fn get_as_required_object(
153        &self,
154        field: &str,
155        path: impl Into<String>,
156    ) -> Result<&CanonicalJsonObject, CanonicalJsonFieldError> {
157        let path = path.into();
158        self.get_as_object(field, &path)?.ok_or(CanonicalJsonFieldError::Missing { path })
159    }
160
161    /// Get the given field as a mutable object.
162    ///
163    /// # Parameters
164    ///
165    /// * `field`: The name of the field to access.
166    /// * `path`: The full path of the field that will be used in errors. This can be different than
167    ///   the `field`, to clarify if this is a field nested under several objects.
168    ///
169    /// # Errors
170    ///
171    /// Returns an error if the field is invalid.
172    fn get_as_object_mut(
173        &mut self,
174        field: &str,
175        path: impl Into<String>,
176    ) -> Result<Option<&mut CanonicalJsonObject>, CanonicalJsonFieldError>;
177
178    /// Get the given required field as a mutable object.
179    ///
180    /// # Parameters
181    ///
182    /// * `field`: The name of the field to access.
183    /// * `path`: The full path of the field that will be used in errors. This can be different than
184    ///   the `field`, to clarify if this is a field nested under several objects.
185    ///
186    /// # Errors
187    ///
188    /// Returns an error if the field is missing or invalid.
189    fn get_as_required_object_mut(
190        &mut self,
191        field: &str,
192        path: impl Into<String>,
193    ) -> Result<&mut CanonicalJsonObject, CanonicalJsonFieldError> {
194        let path = path.into();
195        self.get_as_object_mut(field, &path)?.ok_or(CanonicalJsonFieldError::Missing { path })
196    }
197
198    /// Get the given required field as a mutable object or insert it if it is missing.
199    ///
200    /// # Parameters
201    ///
202    /// * `field`: The name of the field to access.
203    /// * `path`: The full path of the field that will be used in errors. This can be different than
204    ///   the `field`, to clarify if this is a field nested under several objects.
205    ///
206    /// # Errors
207    ///
208    /// Returns an error if the field is already be present but invalid.
209    fn get_as_object_or_insert_default(
210        &mut self,
211        field: impl Into<String>,
212        path: impl Into<String>,
213    ) -> Result<&mut CanonicalJsonObject, CanonicalJsonFieldError>;
214
215    /// Get the given field as a string.
216    ///
217    /// # Parameters
218    ///
219    /// * `field`: The name of the field to access.
220    /// * `path`: The full path of the field that will be used in errors. This can be different than
221    ///   the `field`, to clarify if this is a field nested under several objects.
222    ///
223    /// # Errors
224    ///
225    /// Returns an error if the field is invalid.
226    fn get_as_string(
227        &self,
228        field: &str,
229        path: impl Into<String>,
230    ) -> Result<Option<&str>, CanonicalJsonFieldError>;
231
232    /// Get the given required field as a string.
233    ///
234    /// # Parameters
235    ///
236    /// * `field`: The name of the field to access.
237    /// * `path`: The full path of the field that will be used in errors. This can be different than
238    ///   the `field`, to clarify if this is a field nested under several objects.
239    ///
240    /// # Errors
241    ///
242    /// Returns an error if the field is missing or invalid.
243    fn get_as_required_string(
244        &self,
245        field: &str,
246        path: impl Into<String>,
247    ) -> Result<&str, CanonicalJsonFieldError> {
248        let path = path.into();
249        self.get_as_string(field, &path)?.ok_or(CanonicalJsonFieldError::Missing { path })
250    }
251}
252
253impl CanonicalJsonObjectExt for CanonicalJsonObject {
254    fn get_as_object(
255        &self,
256        field: &str,
257        path: impl Into<String>,
258    ) -> Result<Option<&CanonicalJsonObject>, CanonicalJsonFieldError> {
259        match self.get(field) {
260            Some(CanonicalJsonValue::Object(object)) => Ok(Some(object)),
261            Some(value) => Err(CanonicalJsonFieldError::InvalidType {
262                path: path.into(),
263                expected: CanonicalJsonType::Object,
264                found: value.json_type(),
265            }),
266            None => Ok(None),
267        }
268    }
269
270    fn get_as_object_mut(
271        &mut self,
272        field: &str,
273        path: impl Into<String>,
274    ) -> Result<Option<&mut CanonicalJsonObject>, CanonicalJsonFieldError> {
275        match self.get_mut(field) {
276            Some(CanonicalJsonValue::Object(object)) => Ok(Some(object)),
277            Some(value) => Err(CanonicalJsonFieldError::InvalidType {
278                path: path.into(),
279                expected: CanonicalJsonType::Object,
280                found: value.json_type(),
281            }),
282            None => Ok(None),
283        }
284    }
285
286    fn get_as_object_or_insert_default(
287        &mut self,
288        field: impl Into<String>,
289        path: impl Into<String>,
290    ) -> Result<&mut CanonicalJsonObject, CanonicalJsonFieldError> {
291        let value = self
292            .entry(field.into())
293            .or_insert_with(|| CanonicalJsonValue::Object(Default::default()));
294        match value {
295            CanonicalJsonValue::Object(object) => Ok(object),
296            value => Err(CanonicalJsonFieldError::InvalidType {
297                path: path.into(),
298                expected: CanonicalJsonType::String,
299                found: value.json_type(),
300            }),
301        }
302    }
303
304    fn get_as_string(
305        &self,
306        field: &str,
307        path: impl Into<String>,
308    ) -> Result<Option<&str>, CanonicalJsonFieldError> {
309        match self.get(field) {
310            Some(CanonicalJsonValue::String(string)) => Ok(Some(string)),
311            Some(value) => Err(CanonicalJsonFieldError::InvalidType {
312                path: path.into(),
313                expected: CanonicalJsonType::String,
314                found: value.json_type(),
315            }),
316            None => Ok(None),
317        }
318    }
319}
320
321/// Errors that can happen when trying to access a field from a [`CanonicalJsonObject`].
322#[derive(Debug)]
323#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
324pub enum CanonicalJsonFieldError {
325    /// The field at `path` was expected to be of type `expected`, but was received as `found`.
326    InvalidType {
327        /// The path of the invalid field.
328        path: String,
329
330        /// The type that was expected.
331        expected: CanonicalJsonType,
332
333        /// The type that was found.
334        found: CanonicalJsonType,
335    },
336
337    /// A required field is missing from a JSON object.
338    Missing {
339        /// The path of the missing field.
340        path: String,
341    },
342}
343
344impl fmt::Display for CanonicalJsonFieldError {
345    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346        match self {
347            Self::InvalidType { path, expected, found } => {
348                write!(f, "invalid type at `{path}`: expected {expected:?}, found {found:?}")
349            }
350            Self::Missing { path } => {
351                write!(f, "missing field: `{path}`")
352            }
353        }
354    }
355}
356
357impl std::error::Error for CanonicalJsonFieldError {}
358
359#[cfg(test)]
360mod tests {
361    use std::collections::BTreeMap;
362
363    use assert_matches2::assert_matches;
364    use js_int::int;
365    use serde_json::{
366        from_str as from_json_str, json, to_string as to_json_string,
367        value::RawValue as RawJsonValue,
368    };
369
370    use super::{
371        CanonicalJsonError, assert_to_canonical_json_eq, to_canonical_value, try_from_json_map,
372        value::CanonicalJsonValue,
373    };
374
375    #[test]
376    fn serialize_canon() {
377        let json: CanonicalJsonValue = json!({
378            "a": [1, 2, 3],
379            "other": { "stuff": "hello" },
380            "string": "Thing"
381        })
382        .try_into()
383        .unwrap();
384
385        let ser = to_json_string(&json).unwrap();
386        let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();
387
388        assert_eq!(json, back);
389    }
390
391    #[test]
392    fn check_canonical_sorts_keys() {
393        let json: CanonicalJsonValue = json!({
394            "auth": {
395                "success": true,
396                "mxid": "@john.doe:example.com",
397                "profile": {
398                    "display_name": "John Doe",
399                    "three_pids": [
400                        {
401                            "medium": "email",
402                            "address": "john.doe@example.org"
403                        },
404                        {
405                            "medium": "msisdn",
406                            "address": "123456789"
407                        }
408                    ]
409                }
410            }
411        })
412        .try_into()
413        .unwrap();
414
415        assert_eq!(
416            to_json_string(&json).unwrap(),
417            r#"{"auth":{"mxid":"@john.doe:example.com","profile":{"display_name":"John Doe","three_pids":[{"address":"john.doe@example.org","medium":"email"},{"address":"123456789","medium":"msisdn"}]},"success":true}}"#
418        );
419    }
420
421    #[test]
422    fn serialize_map_to_canonical() {
423        let mut expected = BTreeMap::new();
424        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
425        expected.insert(
426            "bar".into(),
427            CanonicalJsonValue::Array(vec![
428                CanonicalJsonValue::Integer(int!(0)),
429                CanonicalJsonValue::Integer(int!(1)),
430                CanonicalJsonValue::Integer(int!(2)),
431            ]),
432        );
433
434        let mut map = serde_json::Map::new();
435        map.insert("foo".into(), json!("string"));
436        map.insert("bar".into(), json!(vec![0, 1, 2,]));
437
438        assert_eq!(try_from_json_map(map).unwrap(), expected);
439    }
440
441    #[test]
442    fn to_canonical_value_success() {
443        #[derive(Debug, serde::Serialize)]
444        struct MyStruct {
445            string: String,
446            array: Vec<u8>,
447            boolean: Option<bool>,
448            object: BTreeMap<String, MyEnum>,
449            null: (),
450            raw: Box<RawJsonValue>,
451        }
452
453        #[derive(Debug, serde::Serialize)]
454        enum MyEnum {
455            Foo,
456            #[serde(rename = "bar")]
457            Bar,
458        }
459
460        let t = MyStruct {
461            string: "string".into(),
462            array: vec![0, 1, 2],
463            boolean: Some(true),
464            object: [("foo".to_owned(), MyEnum::Foo), ("bar".to_owned(), MyEnum::Bar)].into(),
465            null: (),
466            raw: RawJsonValue::from_string(r#"{"baz":false}"#.to_owned()).unwrap(),
467        };
468
469        let mut expected = BTreeMap::new();
470        expected.insert("string".to_owned(), CanonicalJsonValue::String("string".to_owned()));
471        expected.insert(
472            "array".to_owned(),
473            CanonicalJsonValue::Array(vec![
474                CanonicalJsonValue::Integer(int!(0)),
475                CanonicalJsonValue::Integer(int!(1)),
476                CanonicalJsonValue::Integer(int!(2)),
477            ]),
478        );
479        expected.insert("boolean".to_owned(), CanonicalJsonValue::Bool(true));
480        let mut child_object = BTreeMap::new();
481        child_object.insert("foo".to_owned(), CanonicalJsonValue::String("Foo".to_owned()));
482        child_object.insert("bar".to_owned(), CanonicalJsonValue::String("bar".to_owned()));
483        expected.insert("object".to_owned(), CanonicalJsonValue::Object(child_object));
484        expected.insert("null".to_owned(), CanonicalJsonValue::Null);
485        let mut raw_object = BTreeMap::new();
486        raw_object.insert("baz".to_owned(), CanonicalJsonValue::Bool(false));
487        expected.insert("raw".to_owned(), CanonicalJsonValue::Object(raw_object));
488
489        let expected = CanonicalJsonValue::Object(expected);
490        assert_eq!(to_canonical_value(&t).unwrap(), expected);
491        assert_to_canonical_json_eq!(t, expected.into());
492    }
493
494    #[test]
495    fn to_canonical_value_out_of_range_int() {
496        #[derive(Debug, serde::Serialize)]
497        struct StructWithInt {
498            foo: i64,
499        }
500
501        let t = StructWithInt { foo: i64::MAX };
502        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::IntegerOutOfRange));
503    }
504
505    #[test]
506    fn to_canonical_value_invalid_type() {
507        #[derive(Debug, serde::Serialize)]
508        struct StructWithFloat {
509            foo: f32,
510        }
511
512        let t = StructWithFloat { foo: 10.0 };
513        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::InvalidType(_)));
514    }
515
516    #[test]
517    fn to_canonical_value_invalid_object_key_type() {
518        {
519            #[derive(Debug, serde::Serialize)]
520            struct StructWithBoolKey {
521                foo: BTreeMap<bool, String>,
522            }
523
524            let t = StructWithBoolKey { foo: [(true, "bar".to_owned())].into() };
525            assert_matches!(
526                to_canonical_value(t),
527                Err(CanonicalJsonError::InvalidObjectKeyType(_))
528            );
529        }
530
531        {
532            #[derive(Debug, serde::Serialize)]
533            struct StructWithIntKey {
534                foo: BTreeMap<i8, String>,
535            }
536
537            let t = StructWithIntKey { foo: [(4, "bar".to_owned())].into() };
538            assert_matches!(
539                to_canonical_value(t),
540                Err(CanonicalJsonError::InvalidObjectKeyType(_))
541            );
542        }
543
544        {
545            #[derive(Debug, serde::Serialize)]
546            struct StructWithUnitKey {
547                foo: BTreeMap<(), String>,
548            }
549
550            let t = StructWithUnitKey { foo: [((), "bar".to_owned())].into() };
551            assert_matches!(
552                to_canonical_value(t),
553                Err(CanonicalJsonError::InvalidObjectKeyType(_))
554            );
555        }
556
557        {
558            #[derive(Debug, serde::Serialize)]
559            struct StructWithTupleKey {
560                foo: BTreeMap<(String, String), bool>,
561            }
562
563            let t =
564                StructWithTupleKey { foo: [(("bar".to_owned(), "baz".to_owned()), false)].into() };
565            assert_matches!(
566                to_canonical_value(t),
567                Err(CanonicalJsonError::InvalidObjectKeyType(_))
568            );
569        }
570    }
571
572    #[test]
573    fn to_canonical_value_duplicate_object_key() {
574        #[derive(Debug, serde::Serialize)]
575        struct StructWithDuplicateKey {
576            foo: String,
577            #[serde(rename = "foo")]
578            bar: Vec<u8>,
579        }
580
581        let t = StructWithDuplicateKey { foo: "string".into(), bar: vec![0, 1, 2] };
582        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::DuplicateObjectKey(_)));
583    }
584}