Add into_full_event and From<> impls to convert between sync and full events

This commit is contained in:
Devin Ragotzy 2020-07-24 08:45:38 -04:00 committed by GitHub
parent 3329dc7345
commit 4be63127f7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 321 additions and 12 deletions

View File

@ -4,13 +4,13 @@ use proc_macro2::{Span, TokenStream};
use quote::quote; use quote::quote;
use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident}; use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident};
use crate::event_parse::{to_kind_variation, EventKindVariation}; use crate::event_parse::{to_kind_variation, EventKind, EventKindVariation};
/// Derive `Event` macro code generation. /// Derive `Event` macro code generation.
pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> { pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
let ident = &input.ident; let ident = &input.ident;
let (_kind, var) = to_kind_variation(ident).ok_or_else(|| { let (kind, var) = to_kind_variation(ident).ok_or_else(|| {
syn::Error::new(Span::call_site(), "not a valid ruma event struct identifier") syn::Error::new(Span::call_site(), "not a valid ruma event struct identifier")
})?; })?;
@ -99,9 +99,13 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
} }
}; };
let deserialize_impl = expand_deserialize_event(input, &var, fields, is_generic)?; let deserialize_impl = expand_deserialize_event(&input, &var, &fields, is_generic)?;
let conversion_impl = expand_from_into(&input, &kind, &var, &fields);
Ok(quote! { Ok(quote! {
#conversion_impl
#serialize_impl #serialize_impl
#deserialize_impl #deserialize_impl
@ -109,9 +113,9 @@ pub fn expand_event(input: DeriveInput) -> syn::Result<TokenStream> {
} }
fn expand_deserialize_event( fn expand_deserialize_event(
input: DeriveInput, input: &DeriveInput,
var: &EventKindVariation, var: &EventKindVariation,
fields: Vec<Field>, fields: &[Field],
is_generic: bool, is_generic: bool,
) -> syn::Result<TokenStream> { ) -> syn::Result<TokenStream> {
let ident = &input.ident; let ident = &input.ident;
@ -309,6 +313,58 @@ fn expand_deserialize_event(
}) })
} }
fn expand_from_into(
input: &DeriveInput,
kind: &EventKind,
var: &EventKindVariation,
fields: &[Field],
) -> Option<TokenStream> {
let ident = &input.ident;
let (impl_generics, ty_gen, where_clause) = input.generics.split_for_impl();
let fields = fields.iter().flat_map(|f| &f.ident).collect::<Vec<_>>();
let fields_without_unsigned =
fields.iter().filter(|id| id.to_string().as_str() != "unsigned").collect::<Vec<_>>();
let (into, into_full_event) = if var.is_redacted() {
(quote! { unsigned: unsigned.into(), }, quote! { unsigned: unsigned.into_full(room_id), })
} else if kind == &EventKind::Ephemeral {
(TokenStream::new(), TokenStream::new())
} else {
(quote! { unsigned, }, quote! { unsigned, })
};
if let EventKindVariation::Sync | EventKindVariation::RedactedSync = var {
let full_struct = kind.to_event_ident(&var.to_full_variation());
Some(quote! {
impl #impl_generics From<#full_struct #ty_gen> for #ident #ty_gen #where_clause {
fn from(event: #full_struct #ty_gen) -> Self {
let #full_struct {
#( #fields, )* ..
} = event;
Self { #( #fields_without_unsigned, )* #into }
}
}
impl #impl_generics #ident #ty_gen #where_clause {
/// Convert this sync event into a full event, one with a room_id field.
pub fn into_full_event(self, room_id: ::ruma_identifiers::RoomId) -> #full_struct #ty_gen {
let Self { #( #fields, )* } = self;
#full_struct {
#( #fields_without_unsigned, )*
room_id: room_id.clone(),
#into_full_event
}
}
}
})
} else {
None
}
}
/// CamelCase's a field ident like "foo_bar" to "FooBar". /// CamelCase's a field ident like "foo_bar" to "FooBar".
fn to_camel_case(name: &Ident) -> Ident { fn to_camel_case(name: &Ident) -> Ident {
let span = name.span(); let span = name.span();

View File

@ -122,6 +122,8 @@ fn expand_any_with_deser(
} }
}; };
let event_enum_to_from_sync = expand_conversion_impl(kind, var, &variants);
let redacted_enum = expand_redacted_enum(kind, var); let redacted_enum = expand_redacted_enum(kind, var);
let field_accessor_impl = accessor_methods(kind, var, &variants); let field_accessor_impl = accessor_methods(kind, var, &variants);
@ -131,6 +133,8 @@ fn expand_any_with_deser(
Some(quote! { Some(quote! {
#any_enum #any_enum
#event_enum_to_from_sync
#field_accessor_impl #field_accessor_impl
#redact_impl #redact_impl
@ -141,6 +145,104 @@ fn expand_any_with_deser(
}) })
} }
fn expand_conversion_impl(
kind: &EventKind,
var: &EventKindVariation,
variants: &[Ident],
) -> Option<TokenStream> {
let ident = kind.to_event_enum_ident(var)?;
let variants = &variants
.iter()
.filter(|id| {
// We filter this variant out only for non redacted events.
// The type of the struct held in the enum variant is different in this case
// so we construct the variant manually.
!(id.to_string().as_str() == "RoomRedaction"
&& matches!(var, EventKindVariation::Full | EventKindVariation::Sync))
})
.collect::<Vec<_>>();
match var {
EventKindVariation::Full | EventKindVariation::Redacted => {
// the opposite event variation full -> sync, redacted -> redacted sync
let variation = if var == &EventKindVariation::Full {
EventKindVariation::Sync
} else {
EventKindVariation::RedactedSync
};
let sync = kind.to_event_enum_ident(&variation)?;
let sync_struct = kind.to_event_ident(&variation)?;
let redaction = if let (EventKind::Message, EventKindVariation::Full) = (kind, var) {
quote! {
#ident::RoomRedaction(event) => {
Self::RoomRedaction(::ruma_events::room::redaction::SyncRedactionEvent::from(event))
},
}
} else {
TokenStream::new()
};
Some(quote! {
impl From<#ident> for #sync {
fn from(event: #ident) -> Self {
match event {
#(
#ident::#variants(event) => {
Self::#variants(::ruma_events::#sync_struct::from(event))
},
)*
#redaction
#ident::Custom(event) => {
Self::Custom(::ruma_events::#sync_struct::from(event))
},
}
}
}
})
}
EventKindVariation::Sync | EventKindVariation::RedactedSync => {
let variation = if var == &EventKindVariation::Sync {
EventKindVariation::Full
} else {
EventKindVariation::Redacted
};
let full = kind.to_event_enum_ident(&variation)?;
let redaction = if let (EventKind::Message, EventKindVariation::Sync) = (kind, var) {
quote! {
Self::RoomRedaction(event) => {
#full::RoomRedaction(event.into_full_event(room_id))
},
}
} else {
TokenStream::new()
};
Some(quote! {
impl #ident {
/// Convert this sync event into a full event, one with a room_id field.
pub fn into_full_event(self, room_id: ::ruma_identifiers::RoomId) -> #full {
match self {
#(
Self::#variants(event) => {
#full::#variants(event.into_full_event(room_id))
},
)*
#redaction
Self::Custom(event) => {
#full::Custom(event.into_full_event(room_id))
},
}
}
}
})
}
_ => None,
}
}
/// Generates the 3 redacted state enums, 2 redacted message enums, /// Generates the 3 redacted state enums, 2 redacted message enums,
/// and `Deserialize` implementations. /// and `Deserialize` implementations.
/// ///

View File

@ -2,7 +2,6 @@
use std::fmt; use std::fmt;
use matches::matches;
use proc_macro2::Span; use proc_macro2::Span;
use quote::format_ident; use quote::format_ident;
use syn::{ use syn::{
@ -44,9 +43,21 @@ impl EventKindVariation {
pub fn is_redacted(&self) -> bool { pub fn is_redacted(&self) -> bool {
matches!(self, Self::Redacted | Self::RedactedSync | Self::RedactedStripped) matches!(self, Self::Redacted | Self::RedactedSync | Self::RedactedStripped)
} }
pub fn to_full_variation(&self) -> Self {
match self {
EventKindVariation::Redacted
| EventKindVariation::RedactedSync
| EventKindVariation::RedactedStripped => EventKindVariation::Redacted,
EventKindVariation::Full | EventKindVariation::Sync | EventKindVariation::Stripped => {
EventKindVariation::Full
}
}
}
} }
// If the variants of this enum change `to_event_path` needs to be updated as well. // If the variants of this enum change `to_event_path` needs to be updated as well.
#[derive(Debug, Eq, PartialEq)]
pub enum EventKind { pub enum EventKind {
Basic, Basic,
Ephemeral, Ephemeral,

View File

@ -121,6 +121,7 @@ use std::fmt::Debug;
use js_int::Int; use js_int::Int;
use ruma_common::Raw; use ruma_common::Raw;
use ruma_identifiers::RoomId;
use serde::{ use serde::{
de::{self, IgnoredAny}, de::{self, IgnoredAny},
Deserialize, Serialize, Deserialize, Serialize,
@ -246,6 +247,32 @@ pub struct RedactedSyncUnsigned {
pub redacted_because: Option<Box<SyncRedactionEvent>>, pub redacted_because: Option<Box<SyncRedactionEvent>>,
} }
impl From<RedactedUnsigned> for RedactedSyncUnsigned {
fn from(redacted: RedactedUnsigned) -> Self {
match redacted.redacted_because.map(|b| *b) {
Some(RedactionEvent {
sender,
event_id,
origin_server_ts,
redacts,
unsigned,
content,
..
}) => Self {
redacted_because: Some(Box::new(SyncRedactionEvent {
sender,
event_id,
origin_server_ts,
redacts,
unsigned,
content,
})),
},
_ => Self { redacted_because: None },
}
}
}
impl RedactedSyncUnsigned { impl RedactedSyncUnsigned {
/// Whether this unsigned data is empty (`redacted_because` is `None`). /// Whether this unsigned data is empty (`redacted_because` is `None`).
/// ///
@ -256,6 +283,32 @@ impl RedactedSyncUnsigned {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.redacted_because.is_none() self.redacted_because.is_none()
} }
/// Convert a `RedactedSyncUnsigned` into `RedactedUnsigned`, converting the
/// underlying sync redaction event to a full redaction event (with room_id).
pub fn into_full(self, room_id: RoomId) -> RedactedUnsigned {
match self.redacted_because.map(|b| *b) {
Some(SyncRedactionEvent {
sender,
event_id,
origin_server_ts,
redacts,
unsigned,
content,
}) => RedactedUnsigned {
redacted_because: Some(Box::new(RedactionEvent {
room_id,
sender,
event_id,
origin_server_ts,
redacts,
unsigned,
content,
})),
},
_ => RedactedUnsigned { redacted_because: None },
}
}
} }
/// The base trait that all event content types implement. /// The base trait that all event content types implement.

View File

@ -3,14 +3,14 @@ use std::{
time::{Duration, UNIX_EPOCH}, time::{Duration, UNIX_EPOCH},
}; };
use js_int::UInt; use js_int::{uint, UInt};
use matches::assert_matches; use matches::assert_matches;
use ruma_common::Raw; use ruma_common::Raw;
use ruma_events::{ use ruma_events::{
call::{answer::AnswerEventContent, SessionDescription, SessionDescriptionType}, call::{answer::AnswerEventContent, SessionDescription, SessionDescriptionType},
room::{ImageInfo, ThumbnailInfo}, room::{ImageInfo, ThumbnailInfo},
sticker::StickerEventContent, sticker::StickerEventContent,
AnyMessageEventContent, MessageEvent, RawExt, Unsigned, AnyMessageEventContent, AnySyncMessageEvent, MessageEvent, RawExt, Unsigned,
}; };
use ruma_identifiers::{EventId, RoomId, UserId}; use ruma_identifiers::{EventId, RoomId, UserId};
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};
@ -222,3 +222,58 @@ fn deserialize_message_sticker() {
&& unsigned.is_empty() && unsigned.is_empty()
); );
} }
#[test]
fn deserialize_message_then_convert_to_full() {
let rid = RoomId::try_from("!roomid:room.com").unwrap();
let json_data = json!({
"content": {
"answer": {
"type": "answer",
"sdp": "Hello"
},
"call_id": "foofoo",
"version": 1
},
"event_id": "$h29iv0s8:example.com",
"origin_server_ts": 1,
"sender": "@carl:example.com",
"type": "m.call.answer"
});
let sync_ev =
from_json_value::<Raw<AnySyncMessageEvent>>(json_data).unwrap().deserialize().unwrap();
// Test conversion method
let full = sync_ev.into_full_event(rid);
let full_json = to_json_value(full).unwrap();
assert_matches!(
from_json_value::<Raw<MessageEvent<AnyMessageEventContent>>>(full_json)
.unwrap()
.deserialize()
.unwrap(),
MessageEvent {
content: AnyMessageEventContent::CallAnswer(AnswerEventContent {
answer: SessionDescription {
session_type: SessionDescriptionType::Answer,
sdp,
},
call_id,
version,
}),
event_id,
origin_server_ts,
room_id,
sender,
unsigned,
} if sdp == "Hello"
&& call_id == "foofoo"
&& version == uint!(1)
&& event_id == "$h29iv0s8:example.com"
&& origin_server_ts == UNIX_EPOCH + Duration::from_millis(1)
&& room_id == "!roomid:room.com"
&& sender == "@carl:example.com"
&& unsigned.is_empty()
);
}

View File

@ -8,8 +8,8 @@ use matches::assert_matches;
use ruma_common::Raw; use ruma_common::Raw;
use ruma_events::{ use ruma_events::{
room::{aliases::AliasesEventContent, avatar::AvatarEventContent, ImageInfo, ThumbnailInfo}, room::{aliases::AliasesEventContent, avatar::AvatarEventContent, ImageInfo, ThumbnailInfo},
AnyRoomEvent, AnyStateEvent, AnyStateEventContent, RawExt, StateEvent, SyncStateEvent, AnyRoomEvent, AnyStateEvent, AnyStateEventContent, AnySyncStateEvent, RawExt, StateEvent,
Unsigned, SyncStateEvent, Unsigned,
}; };
use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId}; use ruma_identifiers::{EventId, RoomAliasId, RoomId, UserId};
use serde_json::{ use serde_json::{
@ -134,6 +134,7 @@ fn deserialize_aliases_with_prev_content() {
#[test] #[test]
fn deserialize_aliases_sync_with_room_id() { fn deserialize_aliases_sync_with_room_id() {
// The same JSON can be used to create a sync event, it just ignores the `room_id` field
let json_data = aliases_event_with_prev_content(); let json_data = aliases_event_with_prev_content();
assert_matches!( assert_matches!(
@ -284,3 +285,36 @@ fn deserialize_member_event_with_top_level_membership_field() {
&& content.displayname == Some("example".into()) && content.displayname == Some("example".into())
); );
} }
#[test]
fn deserialize_full_event_convert_to_sync() {
let json_data = aliases_event_with_prev_content();
let full_ev = from_json_value::<Raw<AnyStateEvent>>(json_data).unwrap().deserialize().unwrap();
// Test conversion to sync event (without room_id field)
let sync: AnySyncStateEvent = full_ev.into();
let sync_json = to_json_value(sync).unwrap();
assert_matches!(
from_json_value::<Raw<AnySyncStateEvent>>(sync_json)
.unwrap()
.deserialize()
.unwrap(),
AnySyncStateEvent::RoomAliases(SyncStateEvent {
content,
event_id,
origin_server_ts,
prev_content: Some(prev_content),
sender,
state_key,
unsigned,
}) if content.aliases == vec![RoomAliasId::try_from("#somewhere:localhost").unwrap()]
&& event_id == "$h29iv0s8:example.com"
&& origin_server_ts == UNIX_EPOCH + Duration::from_millis(1)
&& prev_content.aliases == vec![RoomAliasId::try_from("#inner:localhost").unwrap()]
&& sender == "@carl:example.com"
&& state_key == ""
&& unsigned.is_empty()
);
}

View File

@ -385,8 +385,6 @@ mod tests {
#[cfg(feature = "serde")] #[cfg(feature = "serde")]
#[test] #[test]
fn deserialize_official_room_id() { fn deserialize_official_room_id() {
use matches::assert_matches;
let deserialized = let deserialized =
from_str::<RoomVersionId>(r#""1""#).expect("Failed to convert RoomVersionId to JSON."); from_str::<RoomVersionId>(r#""1""#).expect("Failed to convert RoomVersionId to JSON.");