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, 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
23pub fn to_canonical_value<T: Serialize>(
35 value: T,
36) -> Result<CanonicalJsonValue, CanonicalJsonError> {
37 value.serialize(Serializer)
38}
39
40pub 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#[derive(Debug)]
49#[allow(clippy::exhaustive_enums)]
50pub enum CanonicalJsonError {
51 IntegerOutOfRange,
53
54 InvalidType(String),
56
57 InvalidObjectKeyType(String),
59
60 DuplicateObjectKey(String),
62
63 InvalidRawValue(serde_json::Error),
65
66 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#[derive(Debug)]
100#[allow(clippy::exhaustive_enums)]
101pub enum JsonType {
102 Object,
104
105 String,
107
108 Integer,
110
111 Array,
113
114 Boolean,
116
117 Null,
119}
120
121pub trait CanonicalJsonObjectExt {
123 fn get_as_object(
135 &self,
136 field: &str,
137 path: impl Into<String>,
138 ) -> Result<Option<&CanonicalJsonObject>, CanonicalJsonFieldError>;
139
140 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 fn get_as_object_mut(
172 &mut self,
173 field: &str,
174 path: impl Into<String>,
175 ) -> Result<Option<&mut CanonicalJsonObject>, CanonicalJsonFieldError>;
176
177 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 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 fn get_as_string(
226 &self,
227 field: &str,
228 path: impl Into<String>,
229 ) -> Result<Option<&str>, CanonicalJsonFieldError>;
230
231 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#[derive(Debug)]
322#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
323pub enum CanonicalJsonFieldError {
324 InvalidType {
326 path: String,
328
329 expected: CanonicalJsonType,
331
332 found: CanonicalJsonType,
334 },
335
336 Missing {
338 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}