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, RedactingSerializer, 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
122pub trait CanonicalJsonObjectExt {
124 fn get_as_object(
136 &self,
137 field: &str,
138 path: impl Into<String>,
139 ) -> Result<Option<&CanonicalJsonObject>, CanonicalJsonFieldError>;
140
141 fn get_as_required_object(
153 &self,
154 field: &str,
155 path: impl Into<String>,
156 ) -> Result<&CanonicalJsonObject, CanonicalJsonFieldError> {
157 let path = path.into();
158 self.get_as_object(field, &path)?.ok_or(CanonicalJsonFieldError::Missing { path })
159 }
160
161 fn get_as_object_mut(
173 &mut self,
174 field: &str,
175 path: impl Into<String>,
176 ) -> Result<Option<&mut CanonicalJsonObject>, CanonicalJsonFieldError>;
177
178 fn get_as_required_object_mut(
190 &mut self,
191 field: &str,
192 path: impl Into<String>,
193 ) -> Result<&mut CanonicalJsonObject, CanonicalJsonFieldError> {
194 let path = path.into();
195 self.get_as_object_mut(field, &path)?.ok_or(CanonicalJsonFieldError::Missing { path })
196 }
197
198 fn get_as_object_or_insert_default(
210 &mut self,
211 field: impl Into<String>,
212 path: impl Into<String>,
213 ) -> Result<&mut CanonicalJsonObject, CanonicalJsonFieldError>;
214
215 fn get_as_string(
227 &self,
228 field: &str,
229 path: impl Into<String>,
230 ) -> Result<Option<&str>, CanonicalJsonFieldError>;
231
232 fn get_as_required_string(
244 &self,
245 field: &str,
246 path: impl Into<String>,
247 ) -> Result<&str, CanonicalJsonFieldError> {
248 let path = path.into();
249 self.get_as_string(field, &path)?.ok_or(CanonicalJsonFieldError::Missing { path })
250 }
251}
252
253impl CanonicalJsonObjectExt for CanonicalJsonObject {
254 fn get_as_object(
255 &self,
256 field: &str,
257 path: impl Into<String>,
258 ) -> Result<Option<&CanonicalJsonObject>, CanonicalJsonFieldError> {
259 match self.get(field) {
260 Some(CanonicalJsonValue::Object(object)) => Ok(Some(object)),
261 Some(value) => Err(CanonicalJsonFieldError::InvalidType {
262 path: path.into(),
263 expected: CanonicalJsonType::Object,
264 found: value.json_type(),
265 }),
266 None => Ok(None),
267 }
268 }
269
270 fn get_as_object_mut(
271 &mut self,
272 field: &str,
273 path: impl Into<String>,
274 ) -> Result<Option<&mut CanonicalJsonObject>, CanonicalJsonFieldError> {
275 match self.get_mut(field) {
276 Some(CanonicalJsonValue::Object(object)) => Ok(Some(object)),
277 Some(value) => Err(CanonicalJsonFieldError::InvalidType {
278 path: path.into(),
279 expected: CanonicalJsonType::Object,
280 found: value.json_type(),
281 }),
282 None => Ok(None),
283 }
284 }
285
286 fn get_as_object_or_insert_default(
287 &mut self,
288 field: impl Into<String>,
289 path: impl Into<String>,
290 ) -> Result<&mut CanonicalJsonObject, CanonicalJsonFieldError> {
291 let value = self
292 .entry(field.into())
293 .or_insert_with(|| CanonicalJsonValue::Object(Default::default()));
294 match value {
295 CanonicalJsonValue::Object(object) => Ok(object),
296 value => Err(CanonicalJsonFieldError::InvalidType {
297 path: path.into(),
298 expected: CanonicalJsonType::String,
299 found: value.json_type(),
300 }),
301 }
302 }
303
304 fn get_as_string(
305 &self,
306 field: &str,
307 path: impl Into<String>,
308 ) -> Result<Option<&str>, CanonicalJsonFieldError> {
309 match self.get(field) {
310 Some(CanonicalJsonValue::String(string)) => Ok(Some(string)),
311 Some(value) => Err(CanonicalJsonFieldError::InvalidType {
312 path: path.into(),
313 expected: CanonicalJsonType::String,
314 found: value.json_type(),
315 }),
316 None => Ok(None),
317 }
318 }
319}
320
321#[derive(Debug)]
323#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
324pub enum CanonicalJsonFieldError {
325 InvalidType {
327 path: String,
329
330 expected: CanonicalJsonType,
332
333 found: CanonicalJsonType,
335 },
336
337 Missing {
339 path: String,
341 },
342}
343
344impl fmt::Display for CanonicalJsonFieldError {
345 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
346 match self {
347 Self::InvalidType { path, expected, found } => {
348 write!(f, "invalid type at `{path}`: expected {expected:?}, found {found:?}")
349 }
350 Self::Missing { path } => {
351 write!(f, "missing field: `{path}`")
352 }
353 }
354 }
355}
356
357impl std::error::Error for CanonicalJsonFieldError {}
358
359#[cfg(test)]
360mod tests {
361 use std::collections::BTreeMap;
362
363 use assert_matches2::assert_matches;
364 use js_int::int;
365 use serde_json::{
366 from_str as from_json_str, json, to_string as to_json_string,
367 value::RawValue as RawJsonValue,
368 };
369
370 use super::{
371 CanonicalJsonError, assert_to_canonical_json_eq, to_canonical_value, try_from_json_map,
372 value::CanonicalJsonValue,
373 };
374
375 #[test]
376 fn serialize_canon() {
377 let json: CanonicalJsonValue = json!({
378 "a": [1, 2, 3],
379 "other": { "stuff": "hello" },
380 "string": "Thing"
381 })
382 .try_into()
383 .unwrap();
384
385 let ser = to_json_string(&json).unwrap();
386 let back = from_json_str::<CanonicalJsonValue>(&ser).unwrap();
387
388 assert_eq!(json, back);
389 }
390
391 #[test]
392 fn check_canonical_sorts_keys() {
393 let json: CanonicalJsonValue = json!({
394 "auth": {
395 "success": true,
396 "mxid": "@john.doe:example.com",
397 "profile": {
398 "display_name": "John Doe",
399 "three_pids": [
400 {
401 "medium": "email",
402 "address": "john.doe@example.org"
403 },
404 {
405 "medium": "msisdn",
406 "address": "123456789"
407 }
408 ]
409 }
410 }
411 })
412 .try_into()
413 .unwrap();
414
415 assert_eq!(
416 to_json_string(&json).unwrap(),
417 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}}"#
418 );
419 }
420
421 #[test]
422 fn serialize_map_to_canonical() {
423 let mut expected = BTreeMap::new();
424 expected.insert("foo".into(), CanonicalJsonValue::String("string".into()));
425 expected.insert(
426 "bar".into(),
427 CanonicalJsonValue::Array(vec![
428 CanonicalJsonValue::Integer(int!(0)),
429 CanonicalJsonValue::Integer(int!(1)),
430 CanonicalJsonValue::Integer(int!(2)),
431 ]),
432 );
433
434 let mut map = serde_json::Map::new();
435 map.insert("foo".into(), json!("string"));
436 map.insert("bar".into(), json!(vec![0, 1, 2,]));
437
438 assert_eq!(try_from_json_map(map).unwrap(), expected);
439 }
440
441 #[test]
442 fn to_canonical_value_success() {
443 #[derive(Debug, serde::Serialize)]
444 struct MyStruct {
445 string: String,
446 array: Vec<u8>,
447 boolean: Option<bool>,
448 object: BTreeMap<String, MyEnum>,
449 null: (),
450 raw: Box<RawJsonValue>,
451 }
452
453 #[derive(Debug, serde::Serialize)]
454 enum MyEnum {
455 Foo,
456 #[serde(rename = "bar")]
457 Bar,
458 }
459
460 let t = MyStruct {
461 string: "string".into(),
462 array: vec![0, 1, 2],
463 boolean: Some(true),
464 object: [("foo".to_owned(), MyEnum::Foo), ("bar".to_owned(), MyEnum::Bar)].into(),
465 null: (),
466 raw: RawJsonValue::from_string(r#"{"baz":false}"#.to_owned()).unwrap(),
467 };
468
469 let mut expected = BTreeMap::new();
470 expected.insert("string".to_owned(), CanonicalJsonValue::String("string".to_owned()));
471 expected.insert(
472 "array".to_owned(),
473 CanonicalJsonValue::Array(vec![
474 CanonicalJsonValue::Integer(int!(0)),
475 CanonicalJsonValue::Integer(int!(1)),
476 CanonicalJsonValue::Integer(int!(2)),
477 ]),
478 );
479 expected.insert("boolean".to_owned(), CanonicalJsonValue::Bool(true));
480 let mut child_object = BTreeMap::new();
481 child_object.insert("foo".to_owned(), CanonicalJsonValue::String("Foo".to_owned()));
482 child_object.insert("bar".to_owned(), CanonicalJsonValue::String("bar".to_owned()));
483 expected.insert("object".to_owned(), CanonicalJsonValue::Object(child_object));
484 expected.insert("null".to_owned(), CanonicalJsonValue::Null);
485 let mut raw_object = BTreeMap::new();
486 raw_object.insert("baz".to_owned(), CanonicalJsonValue::Bool(false));
487 expected.insert("raw".to_owned(), CanonicalJsonValue::Object(raw_object));
488
489 let expected = CanonicalJsonValue::Object(expected);
490 assert_eq!(to_canonical_value(&t).unwrap(), expected);
491 assert_to_canonical_json_eq!(t, expected.into());
492 }
493
494 #[test]
495 fn to_canonical_value_out_of_range_int() {
496 #[derive(Debug, serde::Serialize)]
497 struct StructWithInt {
498 foo: i64,
499 }
500
501 let t = StructWithInt { foo: i64::MAX };
502 assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::IntegerOutOfRange));
503 }
504
505 #[test]
506 fn to_canonical_value_invalid_type() {
507 #[derive(Debug, serde::Serialize)]
508 struct StructWithFloat {
509 foo: f32,
510 }
511
512 let t = StructWithFloat { foo: 10.0 };
513 assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::InvalidType(_)));
514 }
515
516 #[test]
517 fn to_canonical_value_invalid_object_key_type() {
518 {
519 #[derive(Debug, serde::Serialize)]
520 struct StructWithBoolKey {
521 foo: BTreeMap<bool, String>,
522 }
523
524 let t = StructWithBoolKey { foo: [(true, "bar".to_owned())].into() };
525 assert_matches!(
526 to_canonical_value(t),
527 Err(CanonicalJsonError::InvalidObjectKeyType(_))
528 );
529 }
530
531 {
532 #[derive(Debug, serde::Serialize)]
533 struct StructWithIntKey {
534 foo: BTreeMap<i8, String>,
535 }
536
537 let t = StructWithIntKey { foo: [(4, "bar".to_owned())].into() };
538 assert_matches!(
539 to_canonical_value(t),
540 Err(CanonicalJsonError::InvalidObjectKeyType(_))
541 );
542 }
543
544 {
545 #[derive(Debug, serde::Serialize)]
546 struct StructWithUnitKey {
547 foo: BTreeMap<(), String>,
548 }
549
550 let t = StructWithUnitKey { foo: [((), "bar".to_owned())].into() };
551 assert_matches!(
552 to_canonical_value(t),
553 Err(CanonicalJsonError::InvalidObjectKeyType(_))
554 );
555 }
556
557 {
558 #[derive(Debug, serde::Serialize)]
559 struct StructWithTupleKey {
560 foo: BTreeMap<(String, String), bool>,
561 }
562
563 let t =
564 StructWithTupleKey { foo: [(("bar".to_owned(), "baz".to_owned()), false)].into() };
565 assert_matches!(
566 to_canonical_value(t),
567 Err(CanonicalJsonError::InvalidObjectKeyType(_))
568 );
569 }
570 }
571
572 #[test]
573 fn to_canonical_value_duplicate_object_key() {
574 #[derive(Debug, serde::Serialize)]
575 struct StructWithDuplicateKey {
576 foo: String,
577 #[serde(rename = "foo")]
578 bar: Vec<u8>,
579 }
580
581 let t = StructWithDuplicateKey { foo: "string".into(), bar: vec![0, 1, 2] };
582 assert_matches!(to_canonical_value(t), Err(CanonicalJsonError::DuplicateObjectKey(_)));
583 }
584}