ruma_events/room/
redaction.rs

1//! Types for the [`m.room.redaction`] event.
2//!
3//! [`m.room.redaction`]: https://spec.matrix.org/latest/client-server-api/#mroomredaction
4
5use as_variant::as_variant;
6use js_int::Int;
7#[cfg(feature = "canonical-json")]
8use ruma_common::canonical_json::RedactionEvent;
9use ruma_common::{
10    room_version_rules::RedactionRules, serde::CanBeEmpty, EventId, MilliSecondsSinceUnixEpoch,
11    OwnedEventId, OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, UserId,
12};
13use ruma_macros::{Event, EventContent};
14use serde::{Deserialize, Serialize};
15use tracing::error;
16
17use crate::{
18    BundledMessageLikeRelations, MessageLikeEventContent, MessageLikeEventType, RedactContent,
19    RedactedMessageLikeEventContent, RedactedUnsigned, StaticEventContent,
20};
21
22mod event_serde;
23
24/// A possibly-redacted redaction event.
25#[allow(clippy::exhaustive_enums)]
26#[derive(Clone, Debug)]
27pub enum RoomRedactionEvent {
28    /// Original, unredacted form of the event.
29    Original(OriginalRoomRedactionEvent),
30
31    /// Redacted form of the event with minimal fields.
32    Redacted(RedactedRoomRedactionEvent),
33}
34
35/// A possibly-redacted redaction event without a `room_id`.
36#[allow(clippy::exhaustive_enums)]
37#[derive(Clone, Debug)]
38pub enum SyncRoomRedactionEvent {
39    /// Original, unredacted form of the event.
40    Original(OriginalSyncRoomRedactionEvent),
41
42    /// Redacted form of the event with minimal fields.
43    Redacted(RedactedSyncRoomRedactionEvent),
44}
45
46/// Redaction event.
47#[derive(Clone, Debug)]
48#[allow(clippy::exhaustive_structs)]
49pub struct OriginalRoomRedactionEvent {
50    /// Data specific to the event type.
51    pub content: RoomRedactionEventContent,
52
53    /// The ID of the event that was redacted.
54    ///
55    /// This field is required in room versions prior to 11.
56    pub redacts: Option<OwnedEventId>,
57
58    /// The globally unique event identifier for the user who sent the event.
59    pub event_id: OwnedEventId,
60
61    /// The fully-qualified ID of the user who sent this event.
62    pub sender: OwnedUserId,
63
64    /// Timestamp in milliseconds on originating homeserver when this event was sent.
65    pub origin_server_ts: MilliSecondsSinceUnixEpoch,
66
67    /// The ID of the room associated with this event.
68    pub room_id: OwnedRoomId,
69
70    /// Additional key-value pairs not signed by the homeserver.
71    pub unsigned: RoomRedactionUnsigned,
72}
73
74impl From<OriginalRoomRedactionEvent> for OriginalSyncRoomRedactionEvent {
75    fn from(value: OriginalRoomRedactionEvent) -> Self {
76        let OriginalRoomRedactionEvent {
77            content,
78            redacts,
79            event_id,
80            sender,
81            origin_server_ts,
82            unsigned,
83            ..
84        } = value;
85
86        Self { content, redacts, event_id, sender, origin_server_ts, unsigned }
87    }
88}
89
90/// Redacted redaction event.
91#[derive(Clone, Debug, Event)]
92#[allow(clippy::exhaustive_structs)]
93pub struct RedactedRoomRedactionEvent {
94    /// Data specific to the event type.
95    pub content: RedactedRoomRedactionEventContent,
96
97    /// The globally unique event identifier for the user who sent the event.
98    pub event_id: OwnedEventId,
99
100    /// The fully-qualified ID of the user who sent this event.
101    pub sender: OwnedUserId,
102
103    /// Timestamp in milliseconds on originating homeserver when this event was sent.
104    pub origin_server_ts: MilliSecondsSinceUnixEpoch,
105
106    /// The ID of the room associated with this event.
107    pub room_id: OwnedRoomId,
108
109    /// Additional key-value pairs not signed by the homeserver.
110    pub unsigned: RedactedUnsigned,
111}
112
113/// Redaction event without a `room_id`.
114#[derive(Clone, Debug)]
115#[allow(clippy::exhaustive_structs)]
116pub struct OriginalSyncRoomRedactionEvent {
117    /// Data specific to the event type.
118    pub content: RoomRedactionEventContent,
119
120    /// The ID of the event that was redacted.
121    ///
122    /// This field is required in room versions prior to 11.
123    pub redacts: Option<OwnedEventId>,
124
125    /// The globally unique event identifier for the user who sent the event.
126    pub event_id: OwnedEventId,
127
128    /// The fully-qualified ID of the user who sent this event.
129    pub sender: OwnedUserId,
130
131    /// Timestamp in milliseconds on originating homeserver when this event was sent.
132    pub origin_server_ts: MilliSecondsSinceUnixEpoch,
133
134    /// Additional key-value pairs not signed by the homeserver.
135    pub unsigned: RoomRedactionUnsigned,
136}
137
138impl OriginalSyncRoomRedactionEvent {
139    /// Convert this sync event into a full event, one with a `room_id` field.
140    pub fn into_full_event(self, room_id: OwnedRoomId) -> OriginalRoomRedactionEvent {
141        let Self { content, redacts, event_id, sender, origin_server_ts, unsigned } = self;
142
143        OriginalRoomRedactionEvent {
144            content,
145            redacts,
146            event_id,
147            sender,
148            origin_server_ts,
149            room_id,
150            unsigned,
151        }
152    }
153
154    pub(crate) fn into_maybe_redacted(self) -> SyncRoomRedactionEvent {
155        SyncRoomRedactionEvent::Original(self)
156    }
157}
158
159/// Redacted redaction event without a `room_id`.
160#[derive(Clone, Debug, Event)]
161#[allow(clippy::exhaustive_structs)]
162pub struct RedactedSyncRoomRedactionEvent {
163    /// Data specific to the event type.
164    pub content: RedactedRoomRedactionEventContent,
165
166    /// The globally unique event identifier for the user who sent the event.
167    pub event_id: OwnedEventId,
168
169    /// The fully-qualified ID of the user who sent this event.
170    pub sender: OwnedUserId,
171
172    /// Timestamp in milliseconds on originating homeserver when this event was sent.
173    pub origin_server_ts: MilliSecondsSinceUnixEpoch,
174
175    /// Additional key-value pairs not signed by the homeserver.
176    pub unsigned: RedactedUnsigned,
177}
178
179/// A redaction of an event.
180#[derive(Clone, Debug, Default, Deserialize, Serialize, EventContent)]
181#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
182#[ruma_event(type = "m.room.redaction", kind = MessageLike, custom_redacted)]
183pub struct RoomRedactionEventContent {
184    /// The ID of the event that was redacted.
185    ///
186    /// This field is required starting from room version 11.
187    #[serde(skip_serializing_if = "Option::is_none")]
188    pub redacts: Option<OwnedEventId>,
189
190    /// The reason for the redaction, if any.
191    #[serde(skip_serializing_if = "Option::is_none")]
192    pub reason: Option<String>,
193}
194
195impl RoomRedactionEventContent {
196    /// Creates an empty `RoomRedactionEventContent` according to room versions 1 through 10.
197    pub fn new_v1() -> Self {
198        Self::default()
199    }
200
201    /// Creates a `RoomRedactionEventContent` with the required `redacts` field introduced in room
202    /// version 11.
203    pub fn new_v11(redacts: OwnedEventId) -> Self {
204        Self { redacts: Some(redacts), ..Default::default() }
205    }
206
207    /// Add the given reason to this `RoomRedactionEventContent`.
208    pub fn with_reason(mut self, reason: String) -> Self {
209        self.reason = Some(reason);
210        self
211    }
212}
213
214impl RedactContent for RoomRedactionEventContent {
215    type Redacted = RedactedRoomRedactionEventContent;
216
217    fn redact(self, rules: &RedactionRules) -> Self::Redacted {
218        let redacts = self.redacts.filter(|_| rules.keep_room_redaction_redacts);
219        RedactedRoomRedactionEventContent { redacts }
220    }
221}
222
223/// A redacted redaction event.
224#[derive(Clone, Debug, Default, Deserialize, Serialize)]
225#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
226pub struct RedactedRoomRedactionEventContent {
227    /// The ID of the event that was redacted.
228    ///
229    /// This field is required starting from room version 11.
230    #[serde(skip_serializing_if = "Option::is_none")]
231    pub redacts: Option<OwnedEventId>,
232}
233
234impl StaticEventContent for RedactedRoomRedactionEventContent {
235    const TYPE: &'static str = RoomRedactionEventContent::TYPE;
236    type IsPrefix = <RoomRedactionEventContent as StaticEventContent>::IsPrefix;
237}
238
239impl RedactedMessageLikeEventContent for RedactedRoomRedactionEventContent {
240    fn event_type(&self) -> MessageLikeEventType {
241        MessageLikeEventType::RoomRedaction
242    }
243}
244
245impl RoomRedactionEvent {
246    /// Returns the `type` of this event.
247    pub fn event_type(&self) -> MessageLikeEventType {
248        match self {
249            Self::Original(ev) => ev.content.event_type(),
250            Self::Redacted(ev) => ev.content.event_type(),
251        }
252    }
253
254    /// Returns this event's `event_id` field.
255    pub fn event_id(&self) -> &EventId {
256        match self {
257            Self::Original(ev) => &ev.event_id,
258            Self::Redacted(ev) => &ev.event_id,
259        }
260    }
261
262    /// Returns this event's `sender` field.
263    pub fn sender(&self) -> &UserId {
264        match self {
265            Self::Original(ev) => &ev.sender,
266            Self::Redacted(ev) => &ev.sender,
267        }
268    }
269
270    /// Returns this event's `origin_server_ts` field.
271    pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
272        match self {
273            Self::Original(ev) => ev.origin_server_ts,
274            Self::Redacted(ev) => ev.origin_server_ts,
275        }
276    }
277
278    /// Returns this event's `room_id` field.
279    pub fn room_id(&self) -> &RoomId {
280        match self {
281            Self::Original(ev) => &ev.room_id,
282            Self::Redacted(ev) => &ev.room_id,
283        }
284    }
285
286    /// Returns the ID of the event that this event redacts, according to the given redaction rules.
287    ///
288    /// # Panics
289    ///
290    /// Panics if this is a non-redacted event and both `redacts` field are `None`, which is only
291    /// possible if the event was modified after being deserialized.
292    pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
293        match self {
294            Self::Original(ev) => Some(ev.redacts(rules)),
295            Self::Redacted(ev) => ev.content.redacts.as_deref(),
296        }
297    }
298
299    /// Get the inner `RoomRedactionEvent` if this is an unredacted event.
300    pub fn as_original(&self) -> Option<&OriginalRoomRedactionEvent> {
301        as_variant!(self, Self::Original)
302    }
303}
304
305impl SyncRoomRedactionEvent {
306    /// Returns the `type` of this event.
307    pub fn event_type(&self) -> MessageLikeEventType {
308        match self {
309            Self::Original(ev) => ev.content.event_type(),
310            Self::Redacted(ev) => ev.content.event_type(),
311        }
312    }
313
314    /// Returns this event's `event_id` field.
315    pub fn event_id(&self) -> &EventId {
316        match self {
317            Self::Original(ev) => &ev.event_id,
318            Self::Redacted(ev) => &ev.event_id,
319        }
320    }
321
322    /// Returns this event's `sender` field.
323    pub fn sender(&self) -> &UserId {
324        match self {
325            Self::Original(ev) => &ev.sender,
326            Self::Redacted(ev) => &ev.sender,
327        }
328    }
329
330    /// Returns this event's `origin_server_ts` field.
331    pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
332        match self {
333            Self::Original(ev) => ev.origin_server_ts,
334            Self::Redacted(ev) => ev.origin_server_ts,
335        }
336    }
337
338    /// Returns the ID of the event that this event redacts, according to the given redaction rules.
339    ///
340    /// # Panics
341    ///
342    /// Panics if this is a non-redacted event and both `redacts` field are `None`, which is only
343    /// possible if the event was modified after being deserialized.
344    pub fn redacts(&self, rules: &RedactionRules) -> Option<&EventId> {
345        match self {
346            Self::Original(ev) => Some(ev.redacts(rules)),
347            Self::Redacted(ev) => ev.content.redacts.as_deref(),
348        }
349    }
350
351    /// Get the inner `SyncRoomRedactionEvent` if this is an unredacted event.
352    pub fn as_original(&self) -> Option<&OriginalSyncRoomRedactionEvent> {
353        as_variant!(self, Self::Original)
354    }
355
356    /// Convert this sync event into a full event (one with a `room_id` field).
357    pub fn into_full_event(self, room_id: OwnedRoomId) -> RoomRedactionEvent {
358        match self {
359            Self::Original(ev) => RoomRedactionEvent::Original(ev.into_full_event(room_id)),
360            Self::Redacted(ev) => RoomRedactionEvent::Redacted(ev.into_full_event(room_id)),
361        }
362    }
363}
364
365impl From<RoomRedactionEvent> for SyncRoomRedactionEvent {
366    fn from(full: RoomRedactionEvent) -> Self {
367        match full {
368            RoomRedactionEvent::Original(ev) => Self::Original(ev.into()),
369            RoomRedactionEvent::Redacted(ev) => Self::Redacted(ev.into()),
370        }
371    }
372}
373
374impl OriginalRoomRedactionEvent {
375    /// Returns the ID of the event that this event redacts, according to the proper `redacts` field
376    /// for the given redaction rules.
377    ///
378    /// If the `redacts` field is not the proper one for the given rules, this falls back to the one
379    /// that is available.
380    ///
381    /// # Panics
382    ///
383    /// Panics if both `redacts` field are `None`, which is only possible if the event was modified
384    /// after being deserialized.
385    pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
386        redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
387    }
388}
389
390impl OriginalSyncRoomRedactionEvent {
391    /// Returns the ID of the event that this event redacts, according to the proper `redacts` field
392    /// for the given redaction rules.
393    ///
394    /// If the `redacts` field is not the proper one for the given rules, this falls back to the one
395    /// that is available.
396    ///
397    /// # Panics
398    ///
399    /// Panics if both `redacts` field are `None`, which is only possible if the event was modified
400    /// after being deserialized.
401    pub fn redacts(&self, rules: &RedactionRules) -> &EventId {
402        redacts(rules, self.redacts.as_deref(), self.content.redacts.as_deref())
403    }
404}
405
406#[cfg(feature = "canonical-json")]
407impl RedactionEvent for OriginalRoomRedactionEvent {}
408#[cfg(feature = "canonical-json")]
409impl RedactionEvent for OriginalSyncRoomRedactionEvent {}
410#[cfg(feature = "canonical-json")]
411impl RedactionEvent for RoomRedactionEvent {}
412#[cfg(feature = "canonical-json")]
413impl RedactionEvent for SyncRoomRedactionEvent {}
414
415/// Extra information about a redaction that is not incorporated into the event's hash.
416#[derive(Clone, Debug, Deserialize)]
417#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
418pub struct RoomRedactionUnsigned {
419    /// The time in milliseconds that has elapsed since the event was sent.
420    ///
421    /// This field is generated by the local homeserver, and may be incorrect if the local time on
422    /// at least one of the two servers is out of sync, which can cause the age to either be
423    /// negative or greater than it actually is.
424    pub age: Option<Int>,
425
426    /// The client-supplied transaction ID, if the client being given the event is the same one
427    /// which sent it.
428    pub transaction_id: Option<OwnedTransactionId>,
429
430    /// [Bundled aggregations] of related child events.
431    ///
432    /// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations-of-child-events
433    #[serde(rename = "m.relations", default)]
434    pub relations: BundledMessageLikeRelations<OriginalSyncRoomRedactionEvent>,
435}
436
437impl RoomRedactionUnsigned {
438    /// Create a new `Unsigned` with fields set to `None`.
439    pub fn new() -> Self {
440        Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
441    }
442}
443
444impl Default for RoomRedactionUnsigned {
445    fn default() -> Self {
446        Self::new()
447    }
448}
449
450impl CanBeEmpty for RoomRedactionUnsigned {
451    /// Whether this unsigned data is empty (all fields are `None`).
452    ///
453    /// This method is used to determine whether to skip serializing the `unsigned` field in room
454    /// events. Do not use it to determine whether an incoming `unsigned` field was present - it
455    /// could still have been present but contained none of the known fields.
456    fn is_empty(&self) -> bool {
457        self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
458    }
459}
460
461/// Returns the value of the proper `redacts` field for the given redaction rules.
462///
463/// If the `redacts` field is not the proper one for the given rules, this falls back to the one
464/// that is available.
465///
466/// # Panics
467///
468/// Panics if both `redacts` and `content_redacts` are `None`.
469fn redacts<'a>(
470    rules: &'_ RedactionRules,
471    redacts: Option<&'a EventId>,
472    content_redacts: Option<&'a EventId>,
473) -> &'a EventId {
474    if rules.content_field_redacts {
475        content_redacts.or_else(|| {
476            error!(
477                "Redacts field inside content not available, \
478                 falling back to the one at the event level"
479            );
480            redacts
481        })
482    } else {
483        redacts.or_else(|| {
484            error!(
485                "Redacts field at event level not available, \
486                 falling back to the one inside content"
487            );
488            content_redacts
489        })
490    }
491    .expect("At least one redacts field is set")
492}