1use 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
24pub fn to_canonical_value<T: Serialize>(
36 value: T,
37) -> Result<CanonicalJsonValue, CanonicalJsonError> {
38 value.serialize(Serializer)
39}
40
41pub 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#[derive(Debug)]
50#[allow(clippy::exhaustive_enums)]
51pub enum CanonicalJsonError {
52 IntegerOutOfRange,
54
55 InvalidType(String),
57
58 InvalidObjectKeyType(String),
60
61 DuplicateObjectKey(String),
63
64 InvalidRawValue(serde_json::Error),
66
67 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#[derive(Debug)]
101#[allow(clippy::exhaustive_enums)]
102pub enum JsonType {
103 Object,
105
106 String,
108
109 Integer,
111
112 Array,
114
115 Boolean,
117
118 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}