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