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    serde::CanBeEmpty, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedRoomId,
11    OwnedTransactionId, OwnedUserId, RoomId, RoomVersionId, UserId,
12};
13use ruma_macros::{Event, EventContent};
14use serde::{Deserialize, Serialize};
15use tracing::error;
16
17use crate::{
18    BundledMessageLikeRelations, EventContent, 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, version: &RoomVersionId) -> Self::Redacted {
218        let redacts = match version {
219            RoomVersionId::V1
220            | RoomVersionId::V2
221            | RoomVersionId::V3
222            | RoomVersionId::V4
223            | RoomVersionId::V5
224            | RoomVersionId::V6
225            | RoomVersionId::V7
226            | RoomVersionId::V8
227            | RoomVersionId::V9
228            | RoomVersionId::V10 => None,
229            _ => self.redacts,
230        };
231
232        RedactedRoomRedactionEventContent { redacts }
233    }
234}
235
236/// A redacted redaction event.
237#[derive(Clone, Debug, Default, Deserialize, Serialize)]
238#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
239pub struct RedactedRoomRedactionEventContent {
240    /// The ID of the event that was redacted.
241    ///
242    /// This field is required starting from room version 11.
243    #[serde(skip_serializing_if = "Option::is_none")]
244    pub redacts: Option<OwnedEventId>,
245}
246
247impl EventContent for RedactedRoomRedactionEventContent {
248    type EventType = MessageLikeEventType;
249
250    fn event_type(&self) -> Self::EventType {
251        MessageLikeEventType::RoomRedaction
252    }
253}
254
255impl StaticEventContent for RedactedRoomRedactionEventContent {
256    const TYPE: &'static str = "m.room.redaction";
257}
258
259impl RedactedMessageLikeEventContent for RedactedRoomRedactionEventContent {}
260
261impl RoomRedactionEvent {
262    /// Returns the `type` of this event.
263    pub fn event_type(&self) -> MessageLikeEventType {
264        match self {
265            Self::Original(ev) => ev.content.event_type(),
266            Self::Redacted(ev) => ev.content.event_type(),
267        }
268    }
269
270    /// Returns this event's `event_id` field.
271    pub fn event_id(&self) -> &EventId {
272        match self {
273            Self::Original(ev) => &ev.event_id,
274            Self::Redacted(ev) => &ev.event_id,
275        }
276    }
277
278    /// Returns this event's `sender` field.
279    pub fn sender(&self) -> &UserId {
280        match self {
281            Self::Original(ev) => &ev.sender,
282            Self::Redacted(ev) => &ev.sender,
283        }
284    }
285
286    /// Returns this event's `origin_server_ts` field.
287    pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
288        match self {
289            Self::Original(ev) => ev.origin_server_ts,
290            Self::Redacted(ev) => ev.origin_server_ts,
291        }
292    }
293
294    /// Returns this event's `room_id` field.
295    pub fn room_id(&self) -> &RoomId {
296        match self {
297            Self::Original(ev) => &ev.room_id,
298            Self::Redacted(ev) => &ev.room_id,
299        }
300    }
301
302    /// Returns the ID of the event that this event redacts, according to the given room version.
303    ///
304    /// # Panics
305    ///
306    /// Panics if this is a non-redacted event and both `redacts` field are `None`, which is only
307    /// possible if the event was modified after being deserialized.
308    pub fn redacts(&self, room_version: &RoomVersionId) -> Option<&EventId> {
309        match self {
310            Self::Original(ev) => Some(ev.redacts(room_version)),
311            Self::Redacted(ev) => ev.content.redacts.as_deref(),
312        }
313    }
314
315    /// Get the inner `RoomRedactionEvent` if this is an unredacted event.
316    pub fn as_original(&self) -> Option<&OriginalRoomRedactionEvent> {
317        as_variant!(self, Self::Original)
318    }
319}
320
321impl SyncRoomRedactionEvent {
322    /// Returns the `type` of this event.
323    pub fn event_type(&self) -> MessageLikeEventType {
324        match self {
325            Self::Original(ev) => ev.content.event_type(),
326            Self::Redacted(ev) => ev.content.event_type(),
327        }
328    }
329
330    /// Returns this event's `event_id` field.
331    pub fn event_id(&self) -> &EventId {
332        match self {
333            Self::Original(ev) => &ev.event_id,
334            Self::Redacted(ev) => &ev.event_id,
335        }
336    }
337
338    /// Returns this event's `sender` field.
339    pub fn sender(&self) -> &UserId {
340        match self {
341            Self::Original(ev) => &ev.sender,
342            Self::Redacted(ev) => &ev.sender,
343        }
344    }
345
346    /// Returns this event's `origin_server_ts` field.
347    pub fn origin_server_ts(&self) -> MilliSecondsSinceUnixEpoch {
348        match self {
349            Self::Original(ev) => ev.origin_server_ts,
350            Self::Redacted(ev) => ev.origin_server_ts,
351        }
352    }
353
354    /// Returns the ID of the event that this event redacts, according to the given room version.
355    ///
356    /// # Panics
357    ///
358    /// Panics if this is a non-redacted event and both `redacts` field are `None`, which is only
359    /// possible if the event was modified after being deserialized.
360    pub fn redacts(&self, room_version: &RoomVersionId) -> Option<&EventId> {
361        match self {
362            Self::Original(ev) => Some(ev.redacts(room_version)),
363            Self::Redacted(ev) => ev.content.redacts.as_deref(),
364        }
365    }
366
367    /// Get the inner `SyncRoomRedactionEvent` if this is an unredacted event.
368    pub fn as_original(&self) -> Option<&OriginalSyncRoomRedactionEvent> {
369        as_variant!(self, Self::Original)
370    }
371
372    /// Convert this sync event into a full event (one with a `room_id` field).
373    pub fn into_full_event(self, room_id: OwnedRoomId) -> RoomRedactionEvent {
374        match self {
375            Self::Original(ev) => RoomRedactionEvent::Original(ev.into_full_event(room_id)),
376            Self::Redacted(ev) => RoomRedactionEvent::Redacted(ev.into_full_event(room_id)),
377        }
378    }
379}
380
381impl From<RoomRedactionEvent> for SyncRoomRedactionEvent {
382    fn from(full: RoomRedactionEvent) -> Self {
383        match full {
384            RoomRedactionEvent::Original(ev) => Self::Original(ev.into()),
385            RoomRedactionEvent::Redacted(ev) => Self::Redacted(ev.into()),
386        }
387    }
388}
389
390impl OriginalRoomRedactionEvent {
391    /// Returns the ID of the event that this event redacts, according to the proper `redacts` field
392    /// for the given room version.
393    ///
394    /// If the `redacts` field is not the proper one for the given room version, this falls back to
395    /// the one 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, room_version: &RoomVersionId) -> &EventId {
402        redacts(room_version, self.redacts.as_deref(), self.content.redacts.as_deref())
403    }
404}
405
406impl OriginalSyncRoomRedactionEvent {
407    /// Returns the ID of the event that this event redacts, according to the proper `redacts` field
408    /// for the given room version.
409    ///
410    /// If the `redacts` field is not the proper one for the given room version, this falls back to
411    /// the one that is available.
412    ///
413    /// # Panics
414    ///
415    /// Panics if both `redacts` field are `None`, which is only possible if the event was modified
416    /// after being deserialized.
417    pub fn redacts(&self, room_version: &RoomVersionId) -> &EventId {
418        redacts(room_version, self.redacts.as_deref(), self.content.redacts.as_deref())
419    }
420}
421
422#[cfg(feature = "canonical-json")]
423impl RedactionEvent for OriginalRoomRedactionEvent {}
424#[cfg(feature = "canonical-json")]
425impl RedactionEvent for OriginalSyncRoomRedactionEvent {}
426#[cfg(feature = "canonical-json")]
427impl RedactionEvent for RoomRedactionEvent {}
428#[cfg(feature = "canonical-json")]
429impl RedactionEvent for SyncRoomRedactionEvent {}
430
431/// Extra information about a redaction that is not incorporated into the event's hash.
432#[derive(Clone, Debug, Deserialize)]
433#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
434pub struct RoomRedactionUnsigned {
435    /// The time in milliseconds that has elapsed since the event was sent.
436    ///
437    /// This field is generated by the local homeserver, and may be incorrect if the local time on
438    /// at least one of the two servers is out of sync, which can cause the age to either be
439    /// negative or greater than it actually is.
440    pub age: Option<Int>,
441
442    /// The client-supplied transaction ID, if the client being given the event is the same one
443    /// which sent it.
444    pub transaction_id: Option<OwnedTransactionId>,
445
446    /// [Bundled aggregations] of related child events.
447    ///
448    /// [Bundled aggregations]: https://spec.matrix.org/latest/client-server-api/#aggregations-of-child-events
449    #[serde(rename = "m.relations", default)]
450    pub relations: BundledMessageLikeRelations<OriginalSyncRoomRedactionEvent>,
451}
452
453impl RoomRedactionUnsigned {
454    /// Create a new `Unsigned` with fields set to `None`.
455    pub fn new() -> Self {
456        Self { age: None, transaction_id: None, relations: BundledMessageLikeRelations::default() }
457    }
458}
459
460impl Default for RoomRedactionUnsigned {
461    fn default() -> Self {
462        Self::new()
463    }
464}
465
466impl CanBeEmpty for RoomRedactionUnsigned {
467    /// Whether this unsigned data is empty (all fields are `None`).
468    ///
469    /// This method is used to determine whether to skip serializing the `unsigned` field in room
470    /// events. Do not use it to determine whether an incoming `unsigned` field was present - it
471    /// could still have been present but contained none of the known fields.
472    fn is_empty(&self) -> bool {
473        self.age.is_none() && self.transaction_id.is_none() && self.relations.is_empty()
474    }
475}
476
477/// Returns the value of the proper `redacts` field for the given room version.
478///
479/// If the `redacts` field is not the proper one for the given room version, this falls back to
480/// the one that is available.
481///
482/// # Panics
483///
484/// Panics if both `redacts` and `content_redacts` are `None`.
485fn redacts<'a>(
486    room_version: &'_ RoomVersionId,
487    redacts: Option<&'a EventId>,
488    content_redacts: Option<&'a EventId>,
489) -> &'a EventId {
490    match room_version {
491            RoomVersionId::V1
492            | RoomVersionId::V2
493            | RoomVersionId::V3
494            | RoomVersionId::V4
495            | RoomVersionId::V5
496            | RoomVersionId::V6
497            | RoomVersionId::V7
498            | RoomVersionId::V8
499            | RoomVersionId::V9
500            | RoomVersionId::V10 => redacts
501                .or_else(|| {
502                    error!("Redacts field at event level not available, falling back to the one inside content");
503                    content_redacts
504        })
505                .expect("At least one redacts field is set"),
506            _ => content_redacts
507                .or_else(|| {
508                    error!("Redacts field inside content not available, falling back to the one at the event level");
509                    redacts
510        })
511                .expect("At least one redacts field is set"),
512        }
513}