From 1fdf986bae5d4cd29944e574c3aa2016f0f2d7f5 Mon Sep 17 00:00:00 2001 From: "Ragotzy.devin" Date: Tue, 9 Jun 2020 06:43:21 -0400 Subject: [PATCH] Add RedactionEvent and convert RedactionEventContent to struct def PresenceEventContent and RedactionEventContent now also derive *EventContent --- ruma-events-macros/src/event.rs | 66 ++++++------ ruma-events-macros/src/event_content.rs | 7 +- ruma-events-macros/src/lib.rs | 22 +++- src/presence.rs | 5 +- src/room/redaction.rs | 134 +++++++++++++++++++++--- 5 files changed, 178 insertions(+), 56 deletions(-) diff --git a/ruma-events-macros/src/event.rs b/ruma-events-macros/src/event.rs index d154f2c1..c1f4a3a5 100644 --- a/ruma-events-macros/src/event.rs +++ b/ruma-events-macros/src/event.rs @@ -8,7 +8,7 @@ use syn::{Data, DataStruct, DeriveInput, Field, Fields, FieldsNamed, Ident}; pub fn expand_event(input: DeriveInput) -> syn::Result { let ident = &input.ident; let (impl_gen, ty_gen, where_clause) = input.generics.split_for_impl(); - let is_presence_event = ident == "PresenceEvent"; + let is_generic = !input.generics.params.is_empty(); let fields = if let Data::Struct(DataStruct { fields, .. }) = input.data.clone() { if let Fields::Named(FieldsNamed { named, .. }) = fields { @@ -67,14 +67,6 @@ pub fn expand_event(input: DeriveInput) -> syn::Result { }) .collect::>(); - let event_ty = if is_presence_event { - quote! { - "m.presence"; - } - } else { - quote! { self.content.event_type(); } - }; - let serialize_impl = quote! { impl #impl_gen ::serde::ser::Serialize for #ident #ty_gen #where_clause { fn serialize(&self, serializer: S) -> Result @@ -83,7 +75,7 @@ pub fn expand_event(input: DeriveInput) -> syn::Result { { use ::serde::ser::{SerializeStruct as _, Error as _}; - let event_type = #event_ty; + let event_type = ::ruma_events::EventContent::event_type(&self.content); let mut state = serializer.serialize_struct(stringify!(#ident), 7)?; @@ -94,7 +86,7 @@ pub fn expand_event(input: DeriveInput) -> syn::Result { } }; - let deserialize_impl = expand_deserialize_event(is_presence_event, input, fields)?; + let deserialize_impl = expand_deserialize_event(is_generic, input, fields)?; Ok(quote! { #serialize_impl @@ -104,7 +96,7 @@ pub fn expand_event(input: DeriveInput) -> syn::Result { } fn expand_deserialize_event( - is_presence_event: bool, + is_generic: bool, input: DeriveInput, fields: Vec, ) -> syn::Result { @@ -126,10 +118,10 @@ fn expand_deserialize_event( let name = field.ident.as_ref().unwrap(); let ty = &field.ty; if name == "content" || name == "prev_content" { - if is_presence_event { - quote! { #content_ident } - } else { + if is_generic { quote! { Box<::serde_json::value::RawValue> } + } else { + quote! { #content_ident } } } else if name == "origin_server_ts" { quote! { ::js_int::UInt } @@ -144,23 +136,33 @@ fn expand_deserialize_event( .map(|field| { let name = field.ident.as_ref().unwrap(); if name == "content" { - if is_presence_event { - quote! { - let content = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?; - } - } else { + if is_generic { quote! { let json = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?; let content = C::from_parts(&event_type, json).map_err(A::Error::custom)?; } + } else { + quote! { + let content = content.ok_or_else(|| ::serde::de::Error::missing_field("content"))?; + } } } else if name == "prev_content" { - quote! { - let prev_content = if let Some(json) = prev_content { - Some(C::from_parts(&event_type, json).map_err(A::Error::custom)?) - } else { - None - }; + if is_generic { + quote! { + let prev_content = if let Some(json) = prev_content { + Some(C::from_parts(&event_type, json).map_err(A::Error::custom)?) + } else { + None + }; + } + } else { + quote! { + let prev_content = if let Some(content) = prev_content { + Some(content) + } else { + None + }; + } } } else if name == "origin_server_ts" { quote! { @@ -185,16 +187,16 @@ fn expand_deserialize_event( let field_names = fields.iter().flat_map(|f| &f.ident).collect::>(); - let deserialize_impl_gen = if is_presence_event { - quote! { <'de> } - } else { + let deserialize_impl_gen = if is_generic { let gen = &input.generics.params; quote! { <'de, #gen> } - }; - let deserialize_phantom_type = if is_presence_event { - quote! {} } else { + quote! { <'de> } + }; + let deserialize_phantom_type = if is_generic { quote! { ::std::marker::PhantomData } + } else { + quote! {} }; Ok(quote! { diff --git a/ruma-events-macros/src/event_content.rs b/ruma-events-macros/src/event_content.rs index e02b0ae6..ed4e2922 100644 --- a/ruma-events-macros/src/event_content.rs +++ b/ruma-events-macros/src/event_content.rs @@ -23,7 +23,8 @@ impl Parse for EventMeta { } } -fn expand_event_content(input: DeriveInput) -> syn::Result { +/// Create an `EventContent` implementation for a struct. +pub fn expand_event_content(input: DeriveInput) -> syn::Result { let ident = &input.ident; let event_type_attr = input @@ -89,9 +90,7 @@ pub fn expand_ephemeral_room_event_content(input: DeriveInput) -> syn::Result syn::Result { +pub fn expand_room_event_content(input: DeriveInput) -> syn::Result { let ident = input.ident.clone(); let event_content_impl = expand_event_content(input)?; diff --git a/ruma-events-macros/src/lib.rs b/ruma-events-macros/src/lib.rs index b171de11..fdfea401 100644 --- a/ruma-events-macros/src/lib.rs +++ b/ruma-events-macros/src/lib.rs @@ -18,8 +18,8 @@ use self::{ content_enum::{expand_content_enum, parse::ContentEnumInput}, event::expand_event, event_content::{ - expand_basic_event_content, expand_ephemeral_room_event_content, - expand_message_event_content, expand_state_event_content, + expand_basic_event_content, expand_ephemeral_room_event_content, expand_event_content, + expand_message_event_content, expand_room_event_content, expand_state_event_content, }, gen::RumaEvent, parse::RumaEventInput, @@ -137,6 +137,15 @@ pub fn event_content_enum(input: TokenStream) -> TokenStream { .into() } +/// Generates an implementation of `ruma_events::EventContent`. +#[proc_macro_derive(EventContent, attributes(ruma_event))] +pub fn derive_event_content(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand_event_content(input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + /// Generates an implementation of `ruma_events::BasicEventContent` and it's super traits. #[proc_macro_derive(BasicEventContent, attributes(ruma_event))] pub fn derive_basic_event_content(input: TokenStream) -> TokenStream { @@ -146,6 +155,15 @@ pub fn derive_basic_event_content(input: TokenStream) -> TokenStream { .into() } +/// Generates an implementation of `ruma_events::RoomEventContent` and it's super traits. +#[proc_macro_derive(RoomEventContent, attributes(ruma_event))] +pub fn derive_room_event_content(input: TokenStream) -> TokenStream { + let input = parse_macro_input!(input as DeriveInput); + expand_room_event_content(input) + .unwrap_or_else(|err| err.to_compile_error()) + .into() +} + /// Generates an implementation of `ruma_events::MessageEventContent` and it's super traits. #[proc_macro_derive(MessageEventContent, attributes(ruma_event))] pub fn derive_message_event_content(input: TokenStream) -> TokenStream { diff --git a/src/presence.rs b/src/presence.rs index dd44a47b..8e829258 100644 --- a/src/presence.rs +++ b/src/presence.rs @@ -4,7 +4,7 @@ use js_int::UInt; pub use ruma_common::presence::PresenceState; -use ruma_events_macros::Event; +use ruma_events_macros::{Event, EventContent}; use ruma_identifiers::UserId; use serde::{Deserialize, Serialize}; @@ -22,7 +22,8 @@ pub struct PresenceEvent { /// /// This is the only event content a `PresenceEvent` can contain as it's /// `content` field. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "m.presence")] pub struct PresenceEventContent { /// The current avatar URL for this user. #[serde(skip_serializing_if = "Option::is_none")] diff --git a/src/room/redaction.rs b/src/room/redaction.rs index 2952335a..2ae9d604 100644 --- a/src/room/redaction.rs +++ b/src/room/redaction.rs @@ -1,21 +1,123 @@ //! Types for the *m.room.redaction* event. -use ruma_events_macros::ruma_event; -use ruma_identifiers::EventId; +use std::time::SystemTime; -ruma_event! { - /// A redaction of an event. - RedactionEvent { - kind: RoomEvent, - event_type: "m.room.redaction", - fields: { - /// The ID of the event that was redacted. - pub redacts: EventId, - }, - content: { - /// The reason for the redaction, if any. - #[serde(skip_serializing_if = "Option::is_none")] - pub reason: Option, - } +use ruma_events_macros::{Event, EventContent}; +use ruma_identifiers::{EventId, RoomId, UserId}; +use serde::{Deserialize, Serialize}; + +use crate::UnsignedData; + +/// Redaction event. +#[derive(Clone, Debug, Event)] +pub struct RedactionEvent { + /// Data specific to the event type. + pub content: RedactionEventContent, + + /// The ID of the event that was redacted. + pub redacts: EventId, + + /// The globally unique event identifier for the user who sent the event. + pub event_id: EventId, + + /// The fully-qualified ID of the user who sent this event. + pub sender: UserId, + + /// Timestamp in milliseconds on originating homeserver when this event was sent. + pub origin_server_ts: SystemTime, + + /// The ID of the room associated with this event. + pub room_id: RoomId, + + /// Additional key-value pairs not signed by the homeserver. + pub unsigned: UnsignedData, +} + +/// A redaction of an event. +#[derive(Clone, Debug, Deserialize, Serialize, EventContent)] +#[ruma_event(type = "m.room.redaction")] +pub struct RedactionEventContent { + /// The reason for the redaction, if any. + #[serde(skip_serializing_if = "Option::is_none")] + pub reason: Option, +} + +#[cfg(test)] +mod tests { + use std::{ + convert::TryFrom, + time::{Duration, UNIX_EPOCH}, + }; + + use matches::assert_matches; + use ruma_identifiers::{EventId, RoomId, UserId}; + use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; + + use super::{RedactionEvent, RedactionEventContent}; + use crate::{EventJson, UnsignedData}; + + #[test] + fn serialization() { + let event = RedactionEvent { + content: RedactionEventContent { + reason: Some("redacted because".into()), + }, + redacts: EventId::try_from("$h29iv0s8:example.com").unwrap(), + event_id: EventId::try_from("$h29iv0s8:example.com").unwrap(), + origin_server_ts: UNIX_EPOCH + Duration::from_millis(1), + room_id: RoomId::try_from("!roomid:room.com").unwrap(), + sender: UserId::try_from("@carl:example.com").unwrap(), + unsigned: UnsignedData::default(), + }; + + let json = json!({ + "content": { + "reason": "redacted because" + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 1, + "redacts": "$h29iv0s8:example.com", + "room_id": "!roomid:room.com", + "sender": "@carl:example.com", + "type": "m.room.redaction", + }); + + assert_eq!(to_json_value(&event).unwrap(), json); + } + + #[test] + fn deserialization() { + let e_id = EventId::try_from("$h29iv0s8:example.com").unwrap(); + + let json = json!({ + "content": { + "reason": "redacted because" + }, + "event_id": "$h29iv0s8:example.com", + "origin_server_ts": 1, + "redacts": "$h29iv0s8:example.com", + "room_id": "!roomid:room.com", + "sender": "@carl:example.com", + "type": "m.room.redaction", + }); + + assert_matches!( + from_json_value::>(json) + .unwrap() + .deserialize() + .unwrap(), + RedactionEvent { + content: RedactionEventContent { + reason: Some(reason), + }, + sender, + event_id, origin_server_ts, redacts, room_id, unsigned, + } if reason == "redacted because" && redacts == e_id + && event_id == e_id + && sender == "@carl:example.com" + && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1) + && room_id == RoomId::try_from("!roomid:room.com").unwrap() + && unsigned.is_empty() + ); } }