events: Create EventContentFromType

Implement it for:
- event contents that implement Deserialize
- event contents with event types with a suffix
- custom event contents
This commit is contained in:
Kévin Commaille 2023-01-06 14:27:58 +01:00 committed by Kévin Commaille
parent 9b239663fb
commit 97b212795a
6 changed files with 115 additions and 40 deletions

View File

@ -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:

View File

@ -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<Self> {
Ok(Self { event_type: event_type.into() })
}
}
};
}

View File

@ -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<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>;
}
impl<T> EventContentFromType for T
where
T: EventContent + DeserializeOwned,
{
fn from_parts(_event_type: &str, content: &RawJsonValue) -> serde_json::Result<Self> {
from_json_str(content.get())
}
}

View File

@ -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::<SecretStorageKeyEventContent>(json).unwrap();
let content = <SecretStorageKeyEventContent as EventContentFromType>::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::<SecretStorageKeyEventContent>(json).unwrap();
let content = <SecretStorageKeyEventContent as EventContentFromType>::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::<SecretStorageKeyEventContent>(json).unwrap();
let content = <SecretStorageKeyEventContent as EventContentFromType>::from_parts(
"m.secret_storage.key.test",
&json,
)
.unwrap();
assert_eq!(content.name.unwrap(), "my_key");
let passphrase = content.passphrase.unwrap();

View File

@ -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)]

View File

@ -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)
))
}
<Self as #ruma_common::events::EventContentFromType>::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::<Vec<_>>();
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<Self> {
#[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
})
}