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