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, RedactionError, 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#[cfg(test)]
123mod tests {
124    use std::collections::BTreeMap;
125
126    use assert_matches2::assert_matches;
127    use js_int::int;
128    use serde_json::{
129        from_str as from_json_str, json, to_string as to_json_string,
130        value::RawValue as RawJsonValue,
131    };
132
133    use super::{
134        CanonicalJsonError, assert_to_canonical_json_eq, to_canonical_value, try_from_json_map,
135        value::CanonicalJsonValue,
136    };
137
138    #[test]
139    fn serialize_canon() {
140        let json: CanonicalJsonValue = json!({
141            "a": [1, 2, 3],
142            "other": { "stuff": "hello" },
143            "string": "Thing"
144        })
145        .try_into()
146        .unwrap();
147
148        let ser = to_json_string(&json).unwrap();
149        let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();
150
151        assert_eq!(json, back);
152    }
153
154    #[test]
155    fn check_canonical_sorts_keys() {
156        let json: CanonicalJsonValue = json!({
157            "auth": {
158                "success": true,
159                "mxid": "@john.doe:example.com",
160                "profile": {
161                    "display_name": "John Doe",
162                    "three_pids": [
163                        {
164                            "medium": "email",
165                            "address": "john.doe@example.org"
166                        },
167                        {
168                            "medium": "msisdn",
169                            "address": "123456789"
170                        }
171                    ]
172                }
173            }
174        })
175        .try_into()
176        .unwrap();
177
178        assert_eq!(
179            to_json_string(&json).unwrap(),
180            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}}"#
181        );
182    }
183
184    #[test]
185    fn serialize_map_to_canonical() {
186        let mut expected = BTreeMap::new();
187        expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
188        expected.insert(
189            "bar".into(),
190            CanonicalJsonValue::Array(vec![
191                CanonicalJsonValue::Integer(int!(0)),
192                CanonicalJsonValue::Integer(int!(1)),
193                CanonicalJsonValue::Integer(int!(2)),
194            ]),
195        );
196
197        let mut map = serde_json::Map::new();
198        map.insert("foo".into(), json!("string"));
199        map.insert("bar".into(), json!(vec![0, 1, 2,]));
200
201        assert_eq!(try_from_json_map(map).unwrap(), expected);
202    }
203
204    #[test]
205    fn to_canonical_value_success() {
206        #[derive(Debug, serde::Serialize)]
207        struct MyStruct {
208            string: String,
209            array: Vec<u8>,
210            boolean: Option<bool>,
211            object: BTreeMap<String, MyEnum>,
212            null: (),
213            raw: Box<RawJsonValue>,
214        }
215
216        #[derive(Debug, serde::Serialize)]
217        enum MyEnum {
218            Foo,
219            #[serde(rename = "bar")]
220            Bar,
221        }
222
223        let t = MyStruct {
224            string: "string".into(),
225            array: vec![0, 1, 2],
226            boolean: Some(true),
227            object: [("foo".to_owned(), MyEnum::Foo), ("bar".to_owned(), MyEnum::Bar)].into(),
228            null: (),
229            raw: RawJsonValue::from_string(r#"{"baz":false}"#.to_owned()).unwrap(),
230        };
231
232        let mut expected = BTreeMap::new();
233        expected.insert("string".to_owned(), CanonicalJsonValue::String("string".to_owned()));
234        expected.insert(
235            "array".to_owned(),
236            CanonicalJsonValue::Array(vec![
237                CanonicalJsonValue::Integer(int!(0)),
238                CanonicalJsonValue::Integer(int!(1)),
239                CanonicalJsonValue::Integer(int!(2)),
240            ]),
241        );
242        expected.insert("boolean".to_owned(), CanonicalJsonValue::Bool(true));
243        let mut child_object = BTreeMap::new();
244        child_object.insert("foo".to_owned(), CanonicalJsonValue::String("Foo".to_owned()));
245        child_object.insert("bar".to_owned(), CanonicalJsonValue::String("bar".to_owned()));
246        expected.insert("object".to_owned(), CanonicalJsonValue::Object(child_object));
247        expected.insert("null".to_owned(), CanonicalJsonValue::Null);
248        let mut raw_object = BTreeMap::new();
249        raw_object.insert("baz".to_owned(), CanonicalJsonValue::Bool(false));
250        expected.insert("raw".to_owned(), CanonicalJsonValue::Object(raw_object));
251
252        let expected = CanonicalJsonValue::Object(expected);
253        assert_eq!(to_canonical_value(&t).unwrap(), expected);
254        assert_to_canonical_json_eq!(t, expected.into());
255    }
256
257    #[test]
258    fn to_canonical_value_out_of_range_int() {
259        #[derive(Debug, serde::Serialize)]
260        struct StructWithInt {
261            foo: i64,
262        }
263
264        let t = StructWithInt { foo: i64::MAX };
265        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::IntegerOutOfRange));
266    }
267
268    #[test]
269    fn to_canonical_value_invalid_type() {
270        #[derive(Debug, serde::Serialize)]
271        struct StructWithFloat {
272            foo: f32,
273        }
274
275        let t = StructWithFloat { foo: 10.0 };
276        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::InvalidType(_)));
277    }
278
279    #[test]
280    fn to_canonical_value_invalid_object_key_type() {
281        {
282            #[derive(Debug, serde::Serialize)]
283            struct StructWithBoolKey {
284                foo: BTreeMap<bool, String>,
285            }
286
287            let t = StructWithBoolKey { foo: [(true, "bar".to_owned())].into() };
288            assert_matches!(
289                to_canonical_value(t),
290                Err(CanonicalJsonError::InvalidObjectKeyType(_))
291            );
292        }
293
294        {
295            #[derive(Debug, serde::Serialize)]
296            struct StructWithIntKey {
297                foo: BTreeMap<i8, String>,
298            }
299
300            let t = StructWithIntKey { foo: [(4, "bar".to_owned())].into() };
301            assert_matches!(
302                to_canonical_value(t),
303                Err(CanonicalJsonError::InvalidObjectKeyType(_))
304            );
305        }
306
307        {
308            #[derive(Debug, serde::Serialize)]
309            struct StructWithUnitKey {
310                foo: BTreeMap<(), String>,
311            }
312
313            let t = StructWithUnitKey { foo: [((), "bar".to_owned())].into() };
314            assert_matches!(
315                to_canonical_value(t),
316                Err(CanonicalJsonError::InvalidObjectKeyType(_))
317            );
318        }
319
320        {
321            #[derive(Debug, serde::Serialize)]
322            struct StructWithTupleKey {
323                foo: BTreeMap<(String, String), bool>,
324            }
325
326            let t =
327                StructWithTupleKey { foo: [(("bar".to_owned(), "baz".to_owned()), false)].into() };
328            assert_matches!(
329                to_canonical_value(t),
330                Err(CanonicalJsonError::InvalidObjectKeyType(_))
331            );
332        }
333    }
334
335    #[test]
336    fn to_canonical_value_duplicate_object_key() {
337        #[derive(Debug, serde::Serialize)]
338        struct StructWithDuplicateKey {
339            foo: String,
340            #[serde(rename = "foo")]
341            bar: Vec<u8>,
342        }
343
344        let t = StructWithDuplicateKey { foo: "string".into(), bar: vec![0, 1, 2] };
345        assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::DuplicateObjectKey(_)));
346    }
347}