diff --git a/ruma-serde/src/canonical_json.rs b/ruma-serde/src/canonical_json.rs index 6eec850a..d8eab49b 100644 --- a/ruma-serde/src/canonical_json.rs +++ b/ruma-serde/src/canonical_json.rs @@ -1,10 +1,12 @@ -use std::fmt; +use std::{convert::TryInto, fmt}; use serde::Serialize; -use serde_json::Error as JsonError; +use serde_json::{Error as JsonError, Map as JsonObject, Value as JsonValue}; pub mod value; +use value::Object as CanonicalJsonObject; + /// Returns a canonical JSON string according to Matrix specification. /// /// This function should be preferred over `serde_json::to_string` since it checks the size of the @@ -45,12 +47,29 @@ impl fmt::Display for Error { impl std::error::Error for Error {} +/// Fallible conversion from a `serde_json::Map` to a `CanonicalJsonObject`. +pub fn try_from_json_map( + json: JsonObject, +) -> Result { + json.into_iter().map(|(k, v)| Ok((k, v.try_into()?))).collect() +} + +/// Fallible conversion from any value that impl's `Serialize` to a `CanonicalJsonValue`. +pub fn to_canonical_value(value: T) -> Result { + serde_json::to_value(value).map_err(Error::SerDe)?.try_into() +} + #[cfg(test)] mod test { - use std::convert::TryInto; + use std::{collections::BTreeMap, convert::TryInto}; - use super::{to_string as to_canonical_json_string, value::CanonicalJsonValue}; - use serde_json::{from_str as from_json_str, json, to_string as to_json_string}; + use super::{ + to_canonical_value, to_string as to_canonical_json_string, try_from_json_map, + value::CanonicalJsonValue, + }; + + use js_int::int; + use serde_json::{from_str as from_json_str, json, to_string as to_json_string, Map}; #[test] fn serialize_canon() { @@ -97,4 +116,47 @@ mod test { 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}}"# ) } + + #[test] + fn serialize_map_to_canonical() { + let mut expected = BTreeMap::new(); + expected.insert("foo".into(), CanonicalJsonValue::String("string".into())); + expected.insert( + "bar".into(), + CanonicalJsonValue::Array(vec![ + CanonicalJsonValue::Integer(int!(0)), + CanonicalJsonValue::Integer(int!(1)), + CanonicalJsonValue::Integer(int!(2)), + ]), + ); + + let mut map = Map::new(); + map.insert("foo".into(), json!("string")); + map.insert("bar".into(), json!(vec![0, 1, 2,])); + + assert_eq!(try_from_json_map(map).unwrap(), expected); + } + + #[test] + fn to_canonical() { + #[derive(Debug, serde::Serialize)] + struct Thing { + foo: String, + bar: Vec, + } + let t = Thing { foo: "string".into(), bar: vec![0, 1, 2] }; + + let mut expected = BTreeMap::new(); + expected.insert("foo".into(), CanonicalJsonValue::String("string".into())); + expected.insert( + "bar".into(), + CanonicalJsonValue::Array(vec![ + CanonicalJsonValue::Integer(int!(0)), + CanonicalJsonValue::Integer(int!(1)), + CanonicalJsonValue::Integer(int!(2)), + ]), + ); + + assert_eq!(to_canonical_value(t).unwrap(), CanonicalJsonValue::Object(expected)); + } } diff --git a/ruma-serde/src/canonical_json/value.rs b/ruma-serde/src/canonical_json/value.rs index 8cd94748..07a73059 100644 --- a/ruma-serde/src/canonical_json/value.rs +++ b/ruma-serde/src/canonical_json/value.rs @@ -10,6 +10,9 @@ use serde_json::{to_string as to_json_string, Value as JsonValue}; use super::Error; +/// The inner type of `CanonicalJsonValue::Object`. +pub type Object = BTreeMap; + #[derive(Clone, Eq, PartialEq)] pub enum CanonicalJsonValue { /// Represents a JSON null value. @@ -72,7 +75,7 @@ pub enum CanonicalJsonValue { /// # use ruma_serde::CanonicalJsonValue; /// let v: CanonicalJsonValue = json!({ "an": "object" }).try_into().unwrap(); /// ``` - Object(BTreeMap), + Object(Object), } impl Default for CanonicalJsonValue { @@ -133,7 +136,7 @@ impl TryFrom for CanonicalJsonValue { JsonValue::Object(obj) => Self::Object( obj.into_iter() .map(|(k, v)| Ok((k, v.try_into()?))) - .collect::, _>>()?, + .collect::>()?, ), JsonValue::Null => Self::Null, }) diff --git a/ruma-serde/src/lib.rs b/ruma-serde/src/lib.rs index 5e0acba5..aa4717c9 100644 --- a/ruma-serde/src/lib.rs +++ b/ruma-serde/src/lib.rs @@ -15,7 +15,9 @@ pub mod urlencoded; pub use can_be_empty::{is_empty, CanBeEmpty}; pub use canonical_json::{ - to_string as to_canonical_json_string, value::CanonicalJsonValue, Error as CanonicalJsonError, + to_canonical_value, to_string as to_canonical_json_string, try_from_json_map, + value::{CanonicalJsonValue, Object as CanonicalJsonObject}, + Error as CanonicalJsonError, }; pub use cow::deserialize_cow_str; pub use empty::vec_as_map_of_empty; diff --git a/ruma-signatures/src/functions.rs b/ruma-signatures/src/functions.rs index 6f08c1f7..00ea352a 100644 --- a/ruma-signatures/src/functions.rs +++ b/ruma-signatures/src/functions.rs @@ -5,7 +5,7 @@ use std::{collections::BTreeMap, mem}; use base64::{decode_config, encode_config, STANDARD_NO_PAD, URL_SAFE_NO_PAD}; use ring::digest::{digest, SHA256}; use ruma_identifiers::RoomVersionId; -use ruma_serde::{to_canonical_json_string, CanonicalJsonValue}; +use ruma_serde::{to_canonical_json_string, CanonicalJsonObject, CanonicalJsonValue}; use serde_json::from_str as from_json_str; use crate::{ @@ -73,9 +73,6 @@ static CONTENT_HASH_FIELDS_TO_REMOVE: &[&str] = &["hashes", "signatures", "unsig /// The fields to remove from a JSON object when creating a reference hash of an event. static REFERENCE_HASH_FIELDS_TO_REMOVE: &[&str] = &["age_ts", "signatures", "unsigned"]; -/// The inner type of `CanonicalJsonValue::Object`. -pub type CanonicalJsonObject = BTreeMap; - /// Signs an arbitrary JSON object and adds the signature to an object under the key `signatures`. /// /// If `signatures` is already present, the new signature will be appended to the existing ones. diff --git a/ruma-signatures/src/lib.rs b/ruma-signatures/src/lib.rs index 33ea30e2..37190755 100644 --- a/ruma-signatures/src/lib.rs +++ b/ruma-signatures/src/lib.rs @@ -51,7 +51,7 @@ use std::{ pub use functions::{ canonical_json, content_hash, hash_and_sign_event, redact, reference_hash, sign_json, - verify_event, verify_json, CanonicalJsonObject, + verify_event, verify_json, }; pub use keys::{Ed25519KeyPair, KeyPair, PublicKeyMap, PublicKeySet}; pub use signatures::Signature;