diff --git a/crates/ruma-common/CHANGELOG.md b/crates/ruma-common/CHANGELOG.md index acca21ad..acda36fd 100644 --- a/crates/ruma-common/CHANGELOG.md +++ b/crates/ruma-common/CHANGELOG.md @@ -49,6 +49,8 @@ Breaking changes: * Remove the `serde::urlencoded` module * Query string (de)serialization is now done by the `serde_html_form` crate * Rename `RoomEventType` to `TimelineEventType` +* Remove `SecretStorageKeyEventContent`'s implementation of `Deserialize` + * Use `EventContentFromType::from_parts` instead Improvements: diff --git a/crates/ruma-common/src/events/_custom.rs b/crates/ruma-common/src/events/_custom.rs index 24697a92..3a1ba065 100644 --- a/crates/ruma-common/src/events/_custom.rs +++ b/crates/ruma-common/src/events/_custom.rs @@ -2,9 +2,9 @@ use serde::Serialize; use serde_json::value::RawValue as RawJsonValue; use super::{ - EphemeralRoomEventContent, EphemeralRoomEventType, EventContent, GlobalAccountDataEventContent, - GlobalAccountDataEventType, MessageLikeEventContent, MessageLikeEventType, - OriginalStateEventContent, RedactContent, RedactedEventContent, + EphemeralRoomEventContent, EphemeralRoomEventType, EventContent, EventContentFromType, + GlobalAccountDataEventContent, GlobalAccountDataEventType, MessageLikeEventContent, + MessageLikeEventType, OriginalStateEventContent, RedactContent, RedactedEventContent, RedactedMessageLikeEventContent, RedactedStateEventContent, RoomAccountDataEventContent, RoomAccountDataEventType, StateEventContent, StateEventType, StateUnsigned, ToDeviceEventContent, ToDeviceEventType, @@ -34,6 +34,12 @@ macro_rules! custom_event_content { Ok(Self { event_type: event_type.into() }) } } + + impl EventContentFromType for $i { + fn from_parts(event_type: &str, _content: &RawJsonValue) -> serde_json::Result { + Ok(Self { event_type: event_type.into() }) + } + } }; } diff --git a/crates/ruma-common/src/events/content.rs b/crates/ruma-common/src/events/content.rs index 3a0a5797..d6272fef 100644 --- a/crates/ruma-common/src/events/content.rs +++ b/crates/ruma-common/src/events/content.rs @@ -1,7 +1,7 @@ use std::fmt; use serde::{de::DeserializeOwned, Serialize}; -use serde_json::value::RawValue as RawJsonValue; +use serde_json::{from_str as from_json_str, value::RawValue as RawJsonValue}; use crate::serde::{CanBeEmpty, Raw}; @@ -140,3 +140,19 @@ pub trait RedactedStateEventContent: StateEventContent + RedactedEventContent {} /// Content of a to-device event. pub trait ToDeviceEventContent: EventContent {} + +/// Event content that can be deserialized with its event type. +pub trait EventContentFromType: EventContent { + /// Constructs this event content from the given event type and JSON. + #[doc(hidden)] + fn from_parts(event_type: &str, content: &RawJsonValue) -> serde_json::Result; +} + +impl EventContentFromType for T +where + T: EventContent + DeserializeOwned, +{ + fn from_parts(_event_type: &str, content: &RawJsonValue) -> serde_json::Result { + from_json_str(content.get()) + } +} diff --git a/crates/ruma-common/src/events/secret_storage/key.rs b/crates/ruma-common/src/events/secret_storage/key.rs index b8b1d6f4..61131aa3 100644 --- a/crates/ruma-common/src/events/secret_storage/key.rs +++ b/crates/ruma-common/src/events/secret_storage/key.rs @@ -46,7 +46,7 @@ fn is_default_bits(val: &UInt) -> bool { /// A key description encrypted using a specified algorithm. #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[derive(Clone, Debug, Serialize, Deserialize, EventContent)] +#[derive(Clone, Debug, Serialize, EventContent)] #[ruma_event(type = "m.secret_storage.key.*", kind = GlobalAccountData)] pub struct SecretStorageKeyEventContent { /// The ID of the key. @@ -98,10 +98,17 @@ pub enum SecretEncryptionAlgorithm { mod tests { use assert_matches::assert_matches; use js_int::uint; - use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; + use serde_json::{ + from_value as from_json_value, json, to_value as to_json_value, + value::to_raw_value as to_raw_json_value, + }; use super::{PassPhrase, SecretEncryptionAlgorithm, SecretStorageKeyEventContent}; - use crate::{events::GlobalAccountDataEvent, serde::Base64, KeyDerivationAlgorithm}; + use crate::{ + events::{EventContentFromType, GlobalAccountDataEvent}, + serde::Base64, + KeyDerivationAlgorithm, + }; #[test] fn test_key_description_serialization() { @@ -126,14 +133,19 @@ mod tests { #[test] fn test_key_description_deserialization() { - let json = json!({ + let json = to_raw_json_value(&json!({ "name": "my_key", "algorithm": "m.secret_storage.v1.aes-hmac-sha2", "iv": "YWJjZGVmZ2hpamtsbW5vcA", "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U" - }); + })) + .unwrap(); - let content = from_json_value::(json).unwrap(); + let content = ::from_parts( + "m.secret_storage.key.test", + &json, + ) + .unwrap(); assert_eq!(content.name.unwrap(), "my_key"); assert_matches!(content.passphrase, None); @@ -150,13 +162,18 @@ mod tests { #[test] fn test_key_description_deserialization_without_name() { - let json = json!({ + let json = to_raw_json_value(&json!({ "algorithm": "m.secret_storage.v1.aes-hmac-sha2", "iv": "YWJjZGVmZ2hpamtsbW5vcA", "mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U" - }); + })) + .unwrap(); - let content = from_json_value::(json).unwrap(); + let content = ::from_parts( + "m.secret_storage.key.test", + &json, + ) + .unwrap(); assert!(content.name.is_none()); assert_matches!(content.passphrase, None); @@ -202,7 +219,7 @@ mod tests { #[test] fn test_key_description_with_passphrase_deserialization() { - let json = json!({ + let json = to_raw_json_value(&json!({ "name": "my_key", "algorithm": "m.secret_storage.v1.aes-hmac-sha2", "iv": "YWJjZGVmZ2hpamtsbW5vcA", @@ -213,9 +230,14 @@ mod tests { "iterations": 8, "bits": 256 } - }); + })) + .unwrap(); - let content = from_json_value::(json).unwrap(); + let content = ::from_parts( + "m.secret_storage.key.test", + &json, + ) + .unwrap(); assert_eq!(content.name.unwrap(), "my_key"); let passphrase = content.passphrase.unwrap(); diff --git a/crates/ruma-common/tests/events/ui/10-content-wildcard.rs b/crates/ruma-common/tests/events/ui/10-content-wildcard.rs index f3dbb916..c8d9e108 100644 --- a/crates/ruma-common/tests/events/ui/10-content-wildcard.rs +++ b/crates/ruma-common/tests/events/ui/10-content-wildcard.rs @@ -1,7 +1,7 @@ use ruma_macros::EventContent; -use serde::{Deserialize, Serialize}; +use serde::Serialize; -#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[derive(Clone, Debug, Serialize, EventContent)] #[ruma_event(type = "m.macro.test.*", kind = GlobalAccountData)] pub struct MacroTestContent { #[ruma_event(type_fragment)] diff --git a/crates/ruma-macros/src/events/event_content.rs b/crates/ruma-macros/src/events/event_content.rs index c8302c17..28e996ac 100644 --- a/crates/ruma-macros/src/events/event_content.rs +++ b/crates/ruma-macros/src/events/event_content.rs @@ -894,31 +894,12 @@ fn generate_event_content_impl<'a>( let event_types = aliases.iter().chain([event_type]); - let from_parts_fn_impl = if let Some((_, type_fragment_field)) = &type_suffix_data { - let type_prefixes = event_types.map(|ev_type| { - ev_type - .value() - .strip_suffix('*') - .expect("aliases have already been checked to have the same suffix") - .to_owned() - }); - let type_prefixes = quote! { - [#(#type_prefixes,)*] - }; - + let from_parts_fn_impl = if type_suffix_data.is_some() { quote! { - if let Some(type_fragment) = #type_prefixes.iter().find_map(|prefix| ev_type.strip_prefix(prefix)) { - let mut content: Self = #serde_json::from_str(content.get())?; - content.#type_fragment_field = type_fragment.to_owned(); - - ::std::result::Result::Ok(content) - } else { - ::std::result::Result::Err(#serde::de::Error::custom( - ::std::format!("expected event type starting with one of `{:?}`, found `{}`", #type_prefixes, ev_type) - )) - } + ::from_parts(ev_type, content) } } else { + let event_types = event_types.clone(); let event_types = quote! { [#(#event_types,)*] }; @@ -934,6 +915,52 @@ fn generate_event_content_impl<'a>( } }; + let event_content_from_type_impl = type_suffix_data.map(|(_, type_fragment_field)| { + let type_prefixes = event_types.map(|ev_type| { + ev_type + .value() + .strip_suffix('*') + .expect("aliases have already been checked to have the same suffix") + .to_owned() + }); + let type_prefixes = quote! { + [#(#type_prefixes,)*] + }; + let fields_without_type_fragment = fields.filter(|f| { + !f.attrs.iter().any(|a| { + a.path.is_ident("ruma_event") && matches!(a.parse_args(), Ok(EventFieldMeta::TypeFragment)) + }) + }).collect::>(); + let fields_ident_without_type_fragment = fields_without_type_fragment.iter().filter_map(|f| f.ident.as_ref()); + + quote! { + impl #ruma_common::events::EventContentFromType for #ident { + fn from_parts( + ev_type: &::std::primitive::str, + content: &#serde_json::value::RawValue, + ) -> #serde_json::Result { + #[derive(#serde::Deserialize)] + struct WithoutTypeFragment { + #( #fields_without_type_fragment, )* + } + + if let ::std::option::Option::Some(type_fragment) = #type_prefixes.iter().find_map(|prefix| ev_type.strip_prefix(prefix)) { + let c: WithoutTypeFragment = #serde_json::from_str(content.get())?; + + ::std::result::Result::Ok(Self { + #( #fields_ident_without_type_fragment: c.#fields_ident_without_type_fragment, )* + #type_fragment_field: type_fragment.to_owned(), + }) + } else { + ::std::result::Result::Err(#serde::de::Error::custom( + ::std::format!("expected event type starting with one of `{:?}`, found `{}`", #type_prefixes, ev_type) + )) + } + } + } + } + }); + Ok(quote! { #event_type_ty_decl @@ -953,6 +980,8 @@ fn generate_event_content_impl<'a>( } } + #event_content_from_type_impl + #sub_trait_impl }) }