events: allow deserializing an event content with a type (#1850)
This allows deserializing all the `*EventContent` types into a parent `Any{...}EventContent`, assuming we know the type of the underlying event. Required for serializing/deserializing the content of events we'd like to send, across application restarts, as in https://github.com/matrix-org/matrix-rust-sdk/issues/3361 for the Rust SDK. --- * events: add deserialize_with_type to all the *EventContent types * events: add smoke test for deserializing an event content with a type * events: add a test for deserializing a secret storage key event content * events: add fix for correctly matching events with a type fragment * Address review comments.
This commit is contained in:
parent
829bf5caec
commit
fec2152d87
@ -11,6 +11,8 @@ Improvements:
|
||||
- Add unstable support for MSC3489 `m.beacon` & `m.beacon_info` events
|
||||
(unstable types `org.matrix.msc3489.beacon` & `org.matrix.msc3489.beacon_info`)
|
||||
- Stabilize support for muting in VoIP calls, according to Matrix 1.11
|
||||
- All the root `Any*EventContent` types now have a `EventContentFromType` implementations
|
||||
automatically derived by the `event_enum!` macro.
|
||||
|
||||
Breaking changes:
|
||||
|
||||
|
@ -95,7 +95,6 @@ pub trait ToDeviceEventContent: EventContent<EventType = ToDeviceEventType> {}
|
||||
/// 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<Self>;
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,15 @@
|
||||
use assert_matches2::assert_matches;
|
||||
use js_int::uint;
|
||||
use ruma_common::{serde::CanBeEmpty, MilliSecondsSinceUnixEpoch, VoipVersionId};
|
||||
use ruma_events::{AnyMessageLikeEvent, MessageLikeEvent};
|
||||
use serde_json::{from_value as from_json_value, json};
|
||||
use ruma_common::{
|
||||
serde::{CanBeEmpty, Raw},
|
||||
MilliSecondsSinceUnixEpoch, VoipVersionId,
|
||||
};
|
||||
use ruma_events::{
|
||||
secret_storage::key::{SecretStorageEncryptionAlgorithm, SecretStorageV1AesHmacSha2Properties},
|
||||
AnyGlobalAccountDataEventContent, AnyMessageLikeEvent, AnyMessageLikeEventContent,
|
||||
MessageLikeEvent, RawExt as _,
|
||||
};
|
||||
use serde_json::{from_value as from_json_value, json, value::to_raw_value as to_raw_json_value};
|
||||
|
||||
#[test]
|
||||
fn ui() {
|
||||
@ -46,3 +53,53 @@ fn deserialize_message_event() {
|
||||
assert_eq!(content.call_id, "foofoo");
|
||||
assert_eq!(content.version, VoipVersionId::V0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn text_msgtype_plain_text_deserialization_as_any() {
|
||||
let serialized = json!({
|
||||
"body": "Hello world!",
|
||||
"msgtype": "m.text"
|
||||
});
|
||||
|
||||
let raw_event: Raw<AnyMessageLikeEventContent> =
|
||||
Raw::from_json_string(serialized.to_string()).unwrap();
|
||||
|
||||
let event = raw_event.deserialize_with_type("m.room.message".into()).unwrap();
|
||||
|
||||
assert_matches!(event, AnyMessageLikeEventContent::RoomMessage(content));
|
||||
assert_eq!(content.body(), "Hello world!");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn secret_storage_key_deserialization_as_any() {
|
||||
let serialized = to_raw_json_value(&json!({
|
||||
"name": "my_key",
|
||||
"algorithm": "m.secret_storage.v1.aes-hmac-sha2",
|
||||
"iv": "YWJjZGVmZ2hpamtsbW5vcA",
|
||||
"mac": "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U"
|
||||
}))
|
||||
.unwrap();
|
||||
|
||||
let raw_event: Raw<AnyGlobalAccountDataEventContent> =
|
||||
Raw::from_json_string(serialized.to_string()).unwrap();
|
||||
|
||||
let event = raw_event.deserialize_with_type("m.secret_storage.key.test".into()).unwrap();
|
||||
|
||||
assert_matches!(event, AnyGlobalAccountDataEventContent::SecretStorageKey(content));
|
||||
|
||||
assert_eq!(content.name.unwrap(), "my_key");
|
||||
assert_eq!(content.key_id, "test");
|
||||
assert_matches!(content.passphrase, None);
|
||||
|
||||
assert_matches!(
|
||||
content.algorithm,
|
||||
SecretStorageEncryptionAlgorithm::V1AesHmacSha2(SecretStorageV1AesHmacSha2Properties {
|
||||
iv: Some(iv),
|
||||
mac: Some(mac),
|
||||
..
|
||||
})
|
||||
);
|
||||
|
||||
assert_eq!(iv.encode(), "YWJjZGVmZ2hpamtsbW5vcA");
|
||||
assert_eq!(mac.encode(), "aWRvbnRrbm93d2hhdGFtYWNsb29rc2xpa2U");
|
||||
}
|
||||
|
@ -338,6 +338,55 @@ fn expand_content_enum(
|
||||
let serialize_custom_event_error_path =
|
||||
quote! { #ruma_events::serialize_custom_event_error }.to_string();
|
||||
|
||||
// Generate an `EventContentFromType` implementation.
|
||||
let serde_json = quote! { #ruma_events::exports::serde_json };
|
||||
let event_type_match_arms: TokenStream = events
|
||||
.iter()
|
||||
.map(|event| {
|
||||
let variant = event.to_variant()?;
|
||||
let variant_attrs = {
|
||||
let attrs = &variant.attrs;
|
||||
quote! { #(#attrs)* }
|
||||
};
|
||||
let self_variant = variant.ctor(quote! { Self });
|
||||
|
||||
let ev_types = event.aliases.iter().chain([&event.ev_type]).map(|ev_type| {
|
||||
if event.has_type_fragment() {
|
||||
let ev_type = ev_type.value();
|
||||
let prefix = ev_type
|
||||
.strip_suffix('*')
|
||||
.expect("event type with type fragment must end with *");
|
||||
quote! { t if t.starts_with(#prefix) }
|
||||
} else {
|
||||
quote! { #ev_type }
|
||||
}
|
||||
});
|
||||
|
||||
let deserialize_content = if event.has_type_fragment() {
|
||||
// If the event has a type fragment, then it implements EventContentFromType itself;
|
||||
// see `generate_event_content_impl` which does that. In this case, forward to its
|
||||
// implementation.
|
||||
let content_type = event.to_event_content_path(kind, None);
|
||||
quote! {
|
||||
#content_type::from_parts(event_type, json)?
|
||||
}
|
||||
} else {
|
||||
// The event doesn't have a type fragment, so it *should* implement Deserialize:
|
||||
// use that here.
|
||||
quote! {
|
||||
#serde_json::from_str(json.get())?
|
||||
}
|
||||
};
|
||||
|
||||
Ok(quote! {
|
||||
#variant_attrs #(#ev_types)|* => {
|
||||
let content = #deserialize_content;
|
||||
Ok(#self_variant(content))
|
||||
},
|
||||
})
|
||||
})
|
||||
.collect::<syn::Result<_>>()?;
|
||||
|
||||
Ok(quote! {
|
||||
#( #attrs )*
|
||||
#[derive(Clone, Debug, #serde::Serialize)]
|
||||
@ -368,6 +417,23 @@ fn expand_content_enum(
|
||||
}
|
||||
}
|
||||
|
||||
#[automatically_derived]
|
||||
impl #ruma_events::EventContentFromType for #ident {
|
||||
fn from_parts(event_type: &str, json: &#serde_json::value::RawValue) -> serde_json::Result<Self> {
|
||||
match event_type {
|
||||
#event_type_match_arms
|
||||
|
||||
_ => {
|
||||
Ok(Self::_Custom {
|
||||
event_type: crate::PrivOwnedStr(
|
||||
::std::convert::From::from(event_type.to_owned())
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[automatically_derived]
|
||||
impl #ruma_events::#sub_trait_name for #ident {
|
||||
#state_event_content_impl
|
||||
|
Loading…
x
Reference in New Issue
Block a user