diff --git a/crates/ruma-events-macros/src/event_content.rs b/crates/ruma-events-macros/src/event_content.rs index 81abb3ea..7177ba86 100644 --- a/crates/ruma-events-macros/src/event_content.rs +++ b/crates/ruma-events-macros/src/event_content.rs @@ -129,7 +129,8 @@ pub fn expand_event_content( let content_derives = content_attr.iter().flat_map(|args| args.get_event_kinds()).collect::>(); - let redacted = if needs_redacted(&content_attr) { + // We only generate redacted content structs for state and message events + let redacted = if needs_redacted(&content_attr, &content_derives) { let doc = format!("The payload for a redacted `{}`", ident); let redacted_ident = format_ident!("Redacted{}", ident); let kept_redacted_fields = if let syn::Data::Struct(syn::DataStruct { @@ -358,9 +359,10 @@ fn generate_event_content_impl( } } -fn needs_redacted(input: &[MetaAttrs]) -> bool { +fn needs_redacted(input: &[MetaAttrs], content_derives: &[&EventKind]) -> bool { // `is_custom` means that the content struct does not need a generated // redacted struct also. If no `custom_redacted` attrs are found the content // needs a redacted struct generated. !input.iter().any(|a| a.is_custom()) + && content_derives.iter().any(|e| e.is_message() || e.is_state()) } diff --git a/crates/ruma-events-macros/src/event_enum.rs b/crates/ruma-events-macros/src/event_enum.rs index fe6eb4de..bb0e47fb 100644 --- a/crates/ruma-events-macros/src/event_enum.rs +++ b/crates/ruma-events-macros/src/event_enum.rs @@ -364,16 +364,18 @@ fn expand_content_enum( variants: &[EventEnumVariant], ruma_events: &TokenStream, ) -> TokenStream { + let ruma_identifiers = quote! { #ruma_events::exports::ruma_identifiers }; let serde = quote! { #ruma_events::exports::serde }; let serde_json = quote! { #ruma_events::exports::serde_json }; let ident = kind.to_content_enum(); + let event_type_str = events; let content: Vec<_> = - events.iter().map(|ev| to_event_content_path(kind, ev, ruma_events)).collect(); + events.iter().map(|ev| to_event_content_path(kind, ev, None, ruma_events)).collect(); - let variant_decls = variants.iter().map(|v| v.decl()); + let variant_decls = variants.iter().map(|v| v.decl()).collect::>(); let content_enum = quote! { #( #attrs )* @@ -394,7 +396,7 @@ fn expand_content_enum( let attrs = &v.attrs; quote! { #(#attrs)* } }); - let variant_arms = variants.iter().map(|v| v.match_arm(quote!(Self))); + let variant_arms = variants.iter().map(|v| v.match_arm(quote!(Self))).collect::>(); let variant_ctors = variants.iter().map(|v| v.ctor(quote!(Self))); let event_content_impl = quote! { @@ -430,12 +432,65 @@ fn expand_content_enum( let marker_trait_impls = marker_traits(kind, ruma_events); + let redacted_content_enum = if kind.is_state() || kind.is_message() { + let redacted_ident = kind.to_redacted_content_enum(); + let redaction_variants = variants.iter().map(|v| v.ctor(&redacted_ident)); + let redacted_content: Vec<_> = events + .iter() + .map(|ev| to_event_content_path(kind, ev, Some("Redacted"), ruma_events)) + .collect(); + + quote! { + #( #attrs )* + #[derive(Clone, Debug, #serde::Serialize)] + #[serde(untagged)] + #[allow(clippy::large_enum_variant)] + pub enum #redacted_ident { + #( + #[doc = #event_type_str] + #variant_decls(#redacted_content), + )* + /// Content of a redacted event not defined by the Matrix specification. + Custom(#ruma_events::custom::RedactedCustomEventContent), + } + + impl #ruma_events::RedactContent for #ident { + type Redacted = #redacted_ident; + + /// Redacts `Self` given a `RoomVersionId`. + fn redact( + self, + version: &#ruma_identifiers::RoomVersionId, + ) -> #redacted_ident { + match self { + #( + #variant_arms(content) => { + #redaction_variants( + #ruma_events::RedactContent::redact(content, version) + ) + }, + )* + Self::Custom(content) => { + #redacted_ident::Custom( + #ruma_events::RedactContent::redact(content, version) + ) + }, + } + } + } + } + } else { + TokenStream::new() + }; + quote! { #content_enum #event_content_impl #marker_trait_impls + + #redacted_content_enum } } @@ -859,6 +914,7 @@ fn to_event_path(name: &LitStr, struct_name: &Ident, ruma_events: &TokenStream) fn to_event_content_path( kind: &EventKind, name: &LitStr, + prefix: Option<&str>, ruma_events: &TokenStream, ) -> TokenStream { let span = name.span(); @@ -876,8 +932,10 @@ fn to_event_content_path( .collect(); let content_str = match kind { - EventKind::ToDevice => format_ident!("{}ToDeviceEventContent", event), - _ => format_ident!("{}EventContent", event), + EventKind::ToDevice => { + format_ident!("{}{}ToDeviceEventContent", prefix.unwrap_or(""), event) + } + _ => format_ident!("{}{}EventContent", prefix.unwrap_or(""), event), }; let path = path.iter().map(|s| Ident::new(s, span)); diff --git a/crates/ruma-events-macros/src/event_parse.rs b/crates/ruma-events-macros/src/event_parse.rs index 41372cae..97343450 100644 --- a/crates/ruma-events-macros/src/event_parse.rs +++ b/crates/ruma-events-macros/src/event_parse.rs @@ -3,7 +3,7 @@ use std::fmt; use proc_macro2::Span; -use quote::format_ident; +use quote::{format_ident, IdentFragment}; use syn::{ braced, parse::{self, Parse, ParseStream}, @@ -88,6 +88,26 @@ impl fmt::Display for EventKind { } } +impl IdentFragment for EventKind { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } + + fn span(&self) -> Option { + Some(Span::call_site()) + } +} + +impl IdentFragment for EventKindVariation { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } + + fn span(&self) -> Option { + Some(Span::call_site()) + } +} + impl EventKind { pub fn is_state(&self) -> bool { matches!(self, Self::State) @@ -112,9 +132,7 @@ impl EventKind { | (Self::State, V::Redacted) | (Self::Message, V::RedactedSync) | (Self::State, V::RedactedSync) - | (Self::State, V::RedactedStripped) => { - Some(Ident::new(&format!("{}{}", var, self), Span::call_site())) - } + | (Self::State, V::RedactedStripped) => Some(format_ident!("{}{}", var, self)), _ => None, } } @@ -125,7 +143,12 @@ impl EventKind { /// `Any[kind]EventContent` pub fn to_content_enum(&self) -> Ident { - Ident::new(&format!("Any{}Content", self), Span::call_site()) + format_ident!("Any{}Content", self) + } + + /// `AnyRedacted[kind]EventContent` + pub fn to_redacted_content_enum(&self) -> Ident { + format_ident!("AnyRedacted{}Content", self) } } diff --git a/crates/ruma-events/src/lib.rs b/crates/ruma-events/src/lib.rs index 602e7472..f657f9b3 100644 --- a/crates/ruma-events/src/lib.rs +++ b/crates/ruma-events/src/lib.rs @@ -82,8 +82,8 @@ //! } //! }); //! -//! // The downside of this event is we cannot use it with event enums, but could be deserialized -//! // from a `Raw<_>` that has failed to deserialize. +//! // The downside of this event is we cannot use it with event enums, +//! // but could be deserialized from a `Raw<_>` that has failed to deserialize. //! matches::assert_matches!( //! serde_json::from_value::>(json), //! Ok(SyncMessageEvent { diff --git a/crates/ruma-events/src/room/redaction.rs b/crates/ruma-events/src/room/redaction.rs index 45485e9d..32601be7 100644 --- a/crates/ruma-events/src/room/redaction.rs +++ b/crates/ruma-events/src/room/redaction.rs @@ -5,10 +5,7 @@ use ruma_events_macros::{Event, EventContent}; use ruma_identifiers::{EventId, RoomId, UserId}; use serde::{Deserialize, Serialize}; -use crate::{ - MessageEventContent, RedactedMessageEventContent, RedactedStateEventContent, RoomEventContent, - Unsigned, -}; +use crate::{RedactedStateEventContent, Unsigned}; /// Redaction event. #[derive(Clone, Debug, Event)] @@ -59,17 +56,11 @@ pub struct SyncRedactionEvent { /// A redaction of an event. #[derive(Clone, Debug, Deserialize, Serialize, EventContent)] -#[ruma_event(type = "m.room.redaction")] +#[ruma_event(type = "m.room.redaction", kind = Message)] pub struct RedactionEventContent { /// The reason for the redaction, if any. #[serde(skip_serializing_if = "Option::is_none")] pub reason: Option, } -impl RoomEventContent for RedactionEventContent {} - -impl MessageEventContent for RedactionEventContent {} - -impl RedactedMessageEventContent for RedactedRedactionEventContent {} - impl RedactedStateEventContent for RedactedRedactionEventContent {} diff --git a/crates/ruma-events/tests/redacted.rs b/crates/ruma-events/tests/redacted.rs index b7c79016..932a8278 100644 --- a/crates/ruma-events/tests/redacted.rs +++ b/crates/ruma-events/tests/redacted.rs @@ -9,13 +9,17 @@ use ruma_events::{ message::RedactedMessageEventContent, redaction::{RedactionEventContent, SyncRedactionEvent}, }, - AnyMessageEvent, AnyRedactedMessageEvent, AnyRedactedSyncMessageEvent, - AnyRedactedSyncStateEvent, AnyRoomEvent, AnySyncRoomEvent, Redact, RedactedMessageEvent, - RedactedSyncMessageEvent, RedactedSyncStateEvent, RedactedUnsigned, Unsigned, + AnyMessageEvent, AnyMessageEventContent, AnyRedactedMessageEvent, + AnyRedactedMessageEventContent, AnyRedactedStateEventContent, AnyRedactedSyncMessageEvent, + AnyRedactedSyncStateEvent, AnyRoomEvent, AnyStateEventContent, AnySyncRoomEvent, EventContent, + Redact, RedactContent, RedactedMessageEvent, RedactedSyncMessageEvent, RedactedSyncStateEvent, + RedactedUnsigned, Unsigned, }; use ruma_identifiers::{event_id, room_id, user_id, RoomVersionId}; use ruma_serde::Raw; -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, +}; fn unsigned() -> RedactedUnsigned { let mut unsigned = RedactedUnsigned::default(); @@ -332,3 +336,39 @@ fn redact_method_properly_redacts() { && origin_server_ts == MilliSecondsSinceUnixEpoch(uint!(1)) ); } + +#[test] +fn redact_message_content() { + let json = json!({ + "body": "test", + "msgtype": "m.audio", + "url": "mxc://example.com/AuDi0", + }); + + let content = + AnyMessageEventContent::from_parts("m.room.message", to_raw_value(&json).unwrap()).unwrap(); + + assert_matches!( + content.redact(&RoomVersionId::Version6), + AnyRedactedMessageEventContent::RoomMessage(RedactedMessageEventContent) + ); +} + +#[test] +fn redact_state_content() { + let json = json!({ + "creator": "@carl:example.com", + "m.federate": true, + "room_version": "4" + }); + + let content = + AnyStateEventContent::from_parts("m.room.create", to_raw_value(&json).unwrap()).unwrap(); + + assert_matches!( + content.redact(&RoomVersionId::Version6), + AnyRedactedStateEventContent::RoomCreate(RedactedCreateEventContent { + creator + }) if creator == user_id!("@carl:example.com") + ); +}