Repository: ruma/ruma
Pull request #138 (2020-07-16 19:56:22)

Add redact method to all event_enum! generated enums
I did some commit manipulation on this I think I'm getting the hang of it!

What do you think of `redact` returning an `Option<RedactedEvent>`, the method can validate the `EventId`s match and whatever else is needed (I think the event ids are it but maybe in the future it could be more complicated)?

jimmycuadra: Thanks!
jplatte: > What do you think of `redact` returning an `Option<RedactedEvent>`, the method can validate the `EventId`s match and whatever else is needed (I think the event ids are it but maybe in the future it could be more complicated)?

I thought about that when proposing these methods, but I feel that it would actually be worse because any code calling this will already have to look for the correct event to apply the redaction to so would end up `.unwrap()`ing that `Option` anyway.

> maybe in the future it could be more complicated

I don't see how.
DevinR528: Yes much better! Thanks

Patch:
diff --git a/ruma-events-macros/src/event_content.rs b/ruma-events-macros/src/event_content.rs
index fc51ff07..a4226dc9 100644
--- a/ruma-events-macros/src/event_content.rs
+++ b/ruma-events-macros/src/event_content.rs
@@ -153,7 +153,7 @@ pub fn expand_event_content(input: &DeriveInput, emit_redacted: bool) -> syn::Re
             // this is the non redacted event content's impl
             impl #ident {
                 /// Transforms the full event content into a redacted content according to spec.
-                pub fn redact(self) -> #redacted_ident {
+                pub fn redact(self, version: ::ruma_identifiers::RoomVersionId) -> #redacted_ident {
                     #redacted_ident { #( #redaction_struct_fields: self.#redaction_struct_fields, )* }
                 }
             }
diff --git a/ruma-events-macros/src/event_enum.rs b/ruma-events-macros/src/event_enum.rs
index 4487139a..31e7f23f 100644
--- a/ruma-events-macros/src/event_enum.rs
+++ b/ruma-events-macros/src/event_enum.rs
@@ -131,15 +131,122 @@ fn expand_any_with_deser(
 
     let field_accessor_impl = accessor_methods(kind, var, &variants);
 
+    let redact_impl = generate_redact(&ident, kind, var, &variants);
+
     Some(quote! {
         #any_enum
 
         #field_accessor_impl
 
+        #redact_impl
+
         #event_deserialize_impl
     })
 }
 
+fn generate_redact(
+    ident: &Ident,
+    kind: &EventKind,
+    var: &EventKindVariation,
+    variants: &[Ident],
+) -> Option<TokenStream> {
+    if let EventKindVariation::Full | EventKindVariation::Sync | EventKindVariation::Stripped = var
+    {
+        let (param, redaction_type, redaction_enum) = match var {
+            EventKindVariation::Full => {
+                let struct_id = kind.to_event_ident(&EventKindVariation::Redacted)?;
+                (
+                    quote! { ::ruma_events::room::redaction::RedactionEvent },
+                    quote! { ::ruma_events::#struct_id },
+                    kind.to_event_enum_ident(&EventKindVariation::Redacted)?,
+                )
+            }
+            EventKindVariation::Sync => {
+                let struct_id = kind.to_event_ident(&EventKindVariation::RedactedSync)?;
+                (
+                    quote! { ::ruma_events::room::redaction::SyncRedactionEvent },
+                    quote! { ::ruma_events::#struct_id },
+                    kind.to_event_enum_ident(&EventKindVariation::RedactedSync)?,
+                )
+            }
+            EventKindVariation::Stripped => {
+                let struct_id = kind.to_event_ident(&EventKindVariation::RedactedStripped)?;
+                (
+                    quote! { ::ruma_events::room::redaction::SyncRedactionEvent },
+                    quote! { ::ruma_events::#struct_id },
+                    kind.to_event_enum_ident(&EventKindVariation::RedactedStripped)?,
+                )
+            }
+            _ => return None,
+        };
+
+        let fields = EVENT_FIELDS
+            .iter()
+            .map(|(name, has_field)| generate_redacted_fields(name, kind, var, *has_field));
+
+        let fields = quote! { #( #fields )* };
+
+        Some(quote! {
+            impl #ident {
+                /// Redacts `Self` given a valid `Redaction[Sync]Event`.
+                pub fn redact(self, redaction: #param, version: ::ruma_identifiers::RoomVersionId) -> #redaction_enum {
+                    match self {
+                        #(
+                            Self::#variants(event) => {
+                                let content = event.content.redact(version);
+                                #redaction_enum::#variants(#redaction_type {
+                                    content,
+                                    #fields
+                                })
+                            }
+                        )*
+                        Self::Custom(event) => {
+                            let content = event.content.redact(version);
+                            #redaction_enum::Custom(#redaction_type {
+                                content,
+                                #fields
+                            })
+                        }
+                    }
+                }
+            }
+        })
+    } else {
+        None
+    }
+}
+
+fn generate_redacted_fields(
+    name: &str,
+    kind: &EventKind,
+    var: &EventKindVariation,
+    is_event_kind: EventKindFn,
+) -> TokenStream {
+    if is_event_kind(kind, var) {
+        let name = Ident::new(name, Span::call_site());
+
+        if name == "unsigned" {
+            let redaction_type = if let EventKindVariation::Sync = var {
+                quote! { RedactedSyncUnsigned }
+            } else {
+                quote! { RedactedUnsigned }
+            };
+
+            quote! {
+                unsigned: ::ruma_events::#redaction_type {
+                    redacted_because: Some(::ruma_events::EventJson::from(redaction)),
+                },
+            }
+        } else {
+            quote! {
+                #name: event.#name,
+            }
+        }
+    } else {
+        TokenStream::new()
+    }
+}
+
 /// Generates the 3 redacted state enums, 2 redacted message enums,
 /// and `Deserialize` implementations.
 ///
diff --git a/ruma-events/src/custom.rs b/ruma-events/src/custom.rs
index b17e20bc..394412c7 100644
--- a/ruma-events/src/custom.rs
+++ b/ruma-events/src/custom.rs
@@ -1,5 +1,6 @@
 //! Types for custom events outside of the Matrix specification.
 
+use ruma_identifiers::RoomVersionId;
 use serde::Serialize;
 use serde_json::{value::RawValue as RawJsonValue, Value as JsonValue};
 
@@ -23,7 +24,7 @@ pub struct CustomEventContent {
 
 impl CustomEventContent {
     /// Transforms the full event content into a redacted content according to spec.
-    pub fn redact(self) -> RedactedCustomEventContent {
+    pub fn redact(self, _: RoomVersionId) -> RedactedCustomEventContent {
         RedactedCustomEventContent { event_type: self.event_type }
     }
 }
diff --git a/ruma-events/src/room/aliases.rs b/ruma-events/src/room/aliases.rs
index a5d4610d..0f0cd61d 100644
--- a/ruma-events/src/room/aliases.rs
+++ b/ruma-events/src/room/aliases.rs
@@ -1,7 +1,7 @@
 //! Types for the *m.room.aliases* event.
 
 use ruma_events_macros::StateEventContent;
-use ruma_identifiers::RoomAliasId;
+use ruma_identifiers::{RoomAliasId, RoomVersionId};
 use serde::{Deserialize, Serialize};
 use serde_json::value::RawValue as RawJsonValue;
 
@@ -27,6 +27,22 @@ impl AliasesEventContent {
     pub fn new(aliases: Vec<RoomAliasId>) -> Self {
         Self { aliases }
     }
+
+    /// Redact an `AliasesEventContent` according to current Matrix spec.
+    pub fn redact(self, version: RoomVersionId) -> RedactedAliasesEventContent {
+        // We compare the long way to avoid pre version 6 behavior if/when
+        // a new room version is introduced.
+        if version.is_version_1()
+            || version.is_version_2()
+            || version.is_version_3()
+            || version.is_version_4()
+            || version.is_version_5()
+        {
+            RedactedAliasesEventContent { aliases: Some(self.aliases) }
+        } else {
+            RedactedAliasesEventContent { aliases: None }
+        }
+    }
 }
 
 /// An aliases event that has been redacted.


Test patch:
diff --git a/ruma-events/tests/redacted.rs b/ruma-events/tests/redacted.rs
index cad50308..cf96dc3f 100644
--- a/ruma-events/tests/redacted.rs
+++ b/ruma-events/tests/redacted.rs
@@ -13,11 +13,12 @@ use ruma_events::{
         message::RedactedMessageEventContent,
         redaction::{RedactionEvent, RedactionEventContent, SyncRedactionEvent},
     },
-    AnyRedactedMessageEvent, AnyRedactedSyncMessageEvent, AnyRedactedSyncStateEvent, AnyRoomEvent,
-    AnySyncRoomEvent, RedactedMessageEvent, RedactedSyncMessageEvent, RedactedSyncStateEvent,
-    RedactedSyncUnsigned, RedactedUnsigned, Unsigned,
+    AnyMessageEvent, AnyRedactedMessageEvent, AnyRedactedSyncMessageEvent,
+    AnyRedactedSyncStateEvent, AnyRoomEvent, AnySyncRoomEvent, RedactedMessageEvent,
+    RedactedSyncMessageEvent, RedactedSyncStateEvent, RedactedSyncUnsigned, RedactedUnsigned,
+    Unsigned,
 };
-use ruma_identifiers::{EventId, RoomId, UserId};
+use ruma_identifiers::{EventId, RoomId, RoomVersionId, UserId};
 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
 
 fn full_unsigned() -> RedactedSyncUnsigned {
@@ -263,10 +264,8 @@ fn redacted_custom_event_serialize() {
             && event_type == "m.made.up"
     );
 
-    let x = from_json_value::<Raw<crate::AnyRedactedSyncStateEvent>>(redacted)
-        .unwrap()
-        .deserialize()
-        .unwrap();
+    let x =
+        from_json_value::<Raw<AnyRedactedSyncStateEvent>>(redacted).unwrap().deserialize().unwrap();
     assert_eq!(x.event_id(), &EventId::try_from("$h29iv0s8:example.com").unwrap())
 }
 
@@ -295,3 +294,47 @@ fn redacted_custom_event_deserialize() {
     let actual = to_json_value(&redacted).unwrap();
     assert_eq!(actual, expected);
 }
+
+#[test]
+fn redact_method_properly_redacts() {
+    let ev = json!({
+        "type": "m.room.message",
+        "event_id": "$143273582443PhrSn:example.com",
+        "origin_server_ts": 1,
+        "room_id": "!roomid:room.com",
+        "sender": "@user:example.com",
+        "content": {
+            "body": "test",
+            "msgtype": "m.audio",
+            "url": "http://example.com/audio.mp3",
+        }
+    });
+
+    let redaction = RedactionEvent {
+        content: RedactionEventContent { reason: Some("redacted because".into()) },
+        redacts: EventId::try_from("$143273582443PhrSn: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: Unsigned::default(),
+    };
+
+    let event = from_json_value::<Raw<AnyMessageEvent>>(ev).unwrap().deserialize().unwrap();
+
+    assert_matches!(
+        event.redact(redaction, RoomVersionId::version_6()),
+        AnyRedactedMessageEvent::RoomMessage(RedactedMessageEvent {
+            content: RedactedMessageEventContent,
+            event_id,
+            room_id,
+            sender,
+            origin_server_ts,
+            unsigned,
+        }) if event_id == EventId::try_from("$143273582443PhrSn:example.com").unwrap()
+            && unsigned.redacted_because.is_some()
+            && room_id == RoomId::try_from("!roomid:room.com").unwrap()
+            && sender == UserId::try_from("@user:example.com").unwrap()
+            && origin_server_ts == UNIX_EPOCH + Duration::from_millis(1)
+    );
+}