ruma_serde/
canonical_json.rs1use std::{convert::TryInto, fmt};
2
3use serde::Serialize;
4use serde_json::{Error as JsonError, Value as JsonValue};
5
6pub mod value;
7
8use value::Object as CanonicalJsonObject;
9
10#[derive(Debug)]
12#[allow(clippy::exhaustive_enums)]
13pub enum Error {
14 IntConvert,
16
17 SerDe(JsonError),
19}
20
21impl fmt::Display for Error {
22 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
23 match self {
24 Error::IntConvert => f.write_str("number found is not a valid `js_int::Int`"),
25 Error::SerDe(err) => write!(f, "serde Error: {}", err),
26 }
27 }
28}
29
30impl std::error::Error for Error {}
31
32pub fn try_from_json_map(
34 json: serde_json::Map<String, JsonValue>,
35) -> Result<CanonicalJsonObject, Error> {
36 json.into_iter().map(|(k, v)| Ok((k, v.try_into()?))).collect()
37}
38
39pub fn to_canonical_value<T: Serialize>(value: T) -> Result<value::CanonicalJsonValue, Error> {
41 serde_json::to_value(value).map_err(Error::SerDe)?.try_into()
42}
43
44#[cfg(test)]
45mod tests {
46 use std::{collections::BTreeMap, convert::TryInto};
47
48 use js_int::int;
49 use serde_json::{from_str as from_json_str, json, to_string as to_json_string};
50
51 use super::{to_canonical_value, try_from_json_map, value::CanonicalJsonValue};
52
53 #[test]
54 fn serialize_canon() {
55 let json: CanonicalJsonValue = json!({
56 "a": [1, 2, 3],
57 "other": { "stuff": "hello" },
58 "string": "Thing"
59 })
60 .try_into()
61 .unwrap();
62
63 let ser = to_json_string(&json).unwrap();
64 let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();
65
66 assert_eq!(json, back);
67 }
68
69 #[test]
70 fn check_canonical_sorts_keys() {
71 let json: CanonicalJsonValue = json!({
72 "auth": {
73 "success": true,
74 "mxid": "@john.doe:example.com",
75 "profile": {
76 "display_name": "John Doe",
77 "three_pids": [
78 {
79 "medium": "email",
80 "address": "john.doe@example.org"
81 },
82 {
83 "medium": "msisdn",
84 "address": "123456789"
85 }
86 ]
87 }
88 }
89 })
90 .try_into()
91 .unwrap();
92
93 assert_eq!(
94 to_json_string(&json).unwrap(),
95 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}}"#
96 )
97 }
98
99 #[test]
100 fn serialize_map_to_canonical() {
101 let mut expected = BTreeMap::new();
102 expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
103 expected.insert(
104 "bar".into(),
105 CanonicalJsonValue::Array(vec![
106 CanonicalJsonValue::Integer(int!(0)),
107 CanonicalJsonValue::Integer(int!(1)),
108 CanonicalJsonValue::Integer(int!(2)),
109 ]),
110 );
111
112 let mut map = serde_json::Map::new();
113 map.insert("foo".into(), json!("string"));
114 map.insert("bar".into(), json!(vec![0, 1, 2,]));
115
116 assert_eq!(try_from_json_map(map).unwrap(), expected);
117 }
118
119 #[test]
120 fn to_canonical() {
121 #[derive(Debug, serde::Serialize)]
122 struct Thing {
123 foo: String,
124 bar: Vec<u8>,
125 }
126 let t = Thing { foo: "string".into(), bar: vec![0, 1, 2] };
127
128 let mut expected = BTreeMap::new();
129 expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
130 expected.insert(
131 "bar".into(),
132 CanonicalJsonValue::Array(vec![
133 CanonicalJsonValue::Integer(int!(0)),
134 CanonicalJsonValue::Integer(int!(1)),
135 CanonicalJsonValue::Integer(int!(2)),
136 ]),
137 );
138
139 assert_eq!(to_canonical_value(t).unwrap(), CanonicalJsonValue::Object(expected));
140 }
141}