matrix_sdk_base/
utils.rs

1use ruma::{
2    EventId, OwnedEventId, assign,
3    events::{
4        AnySyncStateEvent, AnySyncTimelineEvent, RedactContent, RedactedStateEventContent,
5        StateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
6        SyncStateEvent,
7        room::{
8            avatar::{RoomAvatarEventContent, StrippedRoomAvatarEvent},
9            canonical_alias::{RoomCanonicalAliasEventContent, StrippedRoomCanonicalAliasEvent},
10            create::{StrippedRoomCreateEvent, SyncRoomCreateEvent},
11            guest_access::{
12                RedactedRoomGuestAccessEventContent, RoomGuestAccessEventContent,
13                StrippedRoomGuestAccessEvent,
14            },
15            history_visibility::{
16                RoomHistoryVisibilityEventContent, StrippedRoomHistoryVisibilityEvent,
17            },
18            join_rules::{RoomJoinRulesEventContent, StrippedRoomJoinRulesEvent},
19            member::{MembershipState, RoomMemberEventContent},
20            name::{RedactedRoomNameEventContent, RoomNameEventContent, StrippedRoomNameEvent},
21            tombstone::{
22                RedactedRoomTombstoneEventContent, RoomTombstoneEventContent,
23                StrippedRoomTombstoneEvent,
24            },
25            topic::{RedactedRoomTopicEventContent, RoomTopicEventContent, StrippedRoomTopicEvent},
26        },
27    },
28    room_version_rules::RedactionRules,
29    serde::Raw,
30};
31use serde::{Deserialize, Serialize, de::DeserializeOwned};
32use tracing::{error, warn};
33
34use crate::room::RoomCreateWithCreatorEventContent;
35
36// #[serde(bound)] instead of DeserializeOwned in type where clause does not
37// work, it can only be a single bound that replaces the default and if a helper
38// trait is used, the compiler still complains about Deserialize not being
39// implemented for C::Redacted.
40//
41// It is unclear why a Serialize bound on C::Redacted is not also required.
42
43/// A minimal state event.
44///
45/// This type can hold a possibly-redacted state event with an optional
46/// event ID. The event ID is optional so this type can also hold events from
47/// invited rooms, where event IDs are not available.
48#[derive(Clone, Debug, Deserialize, Serialize)]
49#[serde(bound(
50    serialize = "C: Serialize, C::Redacted: Serialize",
51    deserialize = "C: DeserializeOwned, C::Redacted: DeserializeOwned"
52))]
53pub enum MinimalStateEvent<C: StateEventContent + RedactContent>
54where
55    C::Redacted: RedactedStateEventContent,
56{
57    /// An unredacted event.
58    Original(OriginalMinimalStateEvent<C>),
59    /// A redacted event.
60    Redacted(RedactedMinimalStateEvent<C::Redacted>),
61}
62
63/// An unredacted minimal state event.
64///
65/// For more details see [`MinimalStateEvent`].
66#[derive(Clone, Debug, Deserialize, Serialize)]
67pub struct OriginalMinimalStateEvent<C>
68where
69    C: StateEventContent,
70{
71    /// The event's content.
72    pub content: C,
73    /// The event's ID, if known.
74    pub event_id: Option<OwnedEventId>,
75}
76
77/// A redacted minimal state event.
78///
79/// For more details see [`MinimalStateEvent`].
80#[derive(Clone, Debug, Deserialize, Serialize)]
81pub struct RedactedMinimalStateEvent<C>
82where
83    C: RedactedStateEventContent,
84{
85    /// The event's content.
86    pub content: C,
87    /// The event's ID, if known.
88    pub event_id: Option<OwnedEventId>,
89}
90
91impl<C> MinimalStateEvent<C>
92where
93    C: StateEventContent + RedactContent,
94    C::Redacted: RedactedStateEventContent,
95{
96    /// Get the inner event's ID.
97    pub fn event_id(&self) -> Option<&EventId> {
98        match self {
99            MinimalStateEvent::Original(ev) => ev.event_id.as_deref(),
100            MinimalStateEvent::Redacted(ev) => ev.event_id.as_deref(),
101        }
102    }
103
104    /// Returns the inner event, if it isn't redacted.
105    pub fn as_original(&self) -> Option<&OriginalMinimalStateEvent<C>> {
106        match self {
107            MinimalStateEvent::Original(ev) => Some(ev),
108            MinimalStateEvent::Redacted(_) => None,
109        }
110    }
111
112    /// Converts `self` to the inner `OriginalMinimalStateEvent<C>`, if it isn't
113    /// redacted.
114    pub fn into_original(self) -> Option<OriginalMinimalStateEvent<C>> {
115        match self {
116            MinimalStateEvent::Original(ev) => Some(ev),
117            MinimalStateEvent::Redacted(_) => None,
118        }
119    }
120
121    /// Redacts this event.
122    ///
123    /// Does nothing if it is already redacted.
124    pub fn redact(&mut self, rules: &RedactionRules)
125    where
126        C: Clone,
127    {
128        if let MinimalStateEvent::Original(ev) = self {
129            *self = MinimalStateEvent::Redacted(RedactedMinimalStateEvent {
130                content: ev.content.clone().redact(rules),
131                event_id: ev.event_id.clone(),
132            });
133        }
134    }
135}
136
137/// A minimal `m.room.member` event.
138pub type MinimalRoomMemberEvent = MinimalStateEvent<RoomMemberEventContent>;
139
140impl MinimalRoomMemberEvent {
141    /// Obtain the membership state, regardless of whether this event is
142    /// redacted.
143    pub fn membership(&self) -> &MembershipState {
144        match self {
145            MinimalStateEvent::Original(ev) => &ev.content.membership,
146            MinimalStateEvent::Redacted(ev) => &ev.content.membership,
147        }
148    }
149}
150
151impl<C> From<SyncStateEvent<C>> for MinimalStateEvent<C>
152where
153    C: StaticStateEventContent + RedactContent,
154    C::Redacted: RedactedStateEventContent,
155{
156    fn from(ev: SyncStateEvent<C>) -> Self {
157        match ev {
158            SyncStateEvent::Original(ev) => Self::Original(OriginalMinimalStateEvent {
159                content: ev.content,
160                event_id: Some(ev.event_id),
161            }),
162            SyncStateEvent::Redacted(ev) => Self::Redacted(RedactedMinimalStateEvent {
163                content: ev.content,
164                event_id: Some(ev.event_id),
165            }),
166        }
167    }
168}
169
170impl<C> From<&SyncStateEvent<C>> for MinimalStateEvent<C>
171where
172    C: Clone + StaticStateEventContent + RedactContent,
173    C::Redacted: Clone + RedactedStateEventContent,
174{
175    fn from(ev: &SyncStateEvent<C>) -> Self {
176        match ev {
177            SyncStateEvent::Original(ev) => Self::Original(OriginalMinimalStateEvent {
178                content: ev.content.clone(),
179                event_id: Some(ev.event_id.clone()),
180            }),
181            SyncStateEvent::Redacted(ev) => Self::Redacted(RedactedMinimalStateEvent {
182                content: ev.content.clone(),
183                event_id: Some(ev.event_id.clone()),
184            }),
185        }
186    }
187}
188
189impl From<&SyncRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
190    fn from(ev: &SyncRoomCreateEvent) -> Self {
191        match ev {
192            SyncStateEvent::Original(ev) => Self::Original(OriginalMinimalStateEvent {
193                content: RoomCreateWithCreatorEventContent::from_event_content(
194                    ev.content.clone(),
195                    ev.sender.clone(),
196                ),
197                event_id: Some(ev.event_id.clone()),
198            }),
199            SyncStateEvent::Redacted(ev) => Self::Redacted(RedactedMinimalStateEvent {
200                content: RoomCreateWithCreatorEventContent::from_event_content(
201                    ev.content.clone(),
202                    ev.sender.clone(),
203                ),
204                event_id: Some(ev.event_id.clone()),
205            }),
206        }
207    }
208}
209
210impl From<&StrippedRoomAvatarEvent> for MinimalStateEvent<RoomAvatarEventContent> {
211    fn from(event: &StrippedRoomAvatarEvent) -> Self {
212        let content = assign!(RoomAvatarEventContent::new(), {
213            info: event.content.info.clone(),
214            url: event.content.url.clone(),
215        });
216        // event might actually be redacted, there is no way to tell for
217        // stripped state events.
218        Self::Original(OriginalMinimalStateEvent { content, event_id: None })
219    }
220}
221
222impl From<&StrippedRoomNameEvent> for MinimalStateEvent<RoomNameEventContent> {
223    fn from(event: &StrippedRoomNameEvent) -> Self {
224        match event.content.name.clone() {
225            Some(name) => {
226                let content = RoomNameEventContent::new(name);
227                Self::Original(OriginalMinimalStateEvent { content, event_id: None })
228            }
229            None => {
230                let content = RedactedRoomNameEventContent::new();
231                Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
232            }
233        }
234    }
235}
236
237impl From<&StrippedRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
238    fn from(event: &StrippedRoomCreateEvent) -> Self {
239        let content = RoomCreateWithCreatorEventContent::from_event_content(
240            event.content.clone(),
241            event.sender.clone(),
242        );
243        Self::Original(OriginalMinimalStateEvent { content, event_id: None })
244    }
245}
246
247impl From<&StrippedRoomHistoryVisibilityEvent>
248    for MinimalStateEvent<RoomHistoryVisibilityEventContent>
249{
250    fn from(event: &StrippedRoomHistoryVisibilityEvent) -> Self {
251        let content =
252            RoomHistoryVisibilityEventContent::new(event.content.history_visibility.clone());
253        Self::Original(OriginalMinimalStateEvent { content, event_id: None })
254    }
255}
256
257impl From<&StrippedRoomGuestAccessEvent> for MinimalStateEvent<RoomGuestAccessEventContent> {
258    fn from(event: &StrippedRoomGuestAccessEvent) -> Self {
259        match &event.content.guest_access {
260            Some(guest_access) => {
261                let content = RoomGuestAccessEventContent::new(guest_access.clone());
262                Self::Original(OriginalMinimalStateEvent { content, event_id: None })
263            }
264            None => {
265                let content = RedactedRoomGuestAccessEventContent::new();
266                Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
267            }
268        }
269    }
270}
271
272impl From<&StrippedRoomJoinRulesEvent> for MinimalStateEvent<RoomJoinRulesEventContent> {
273    fn from(event: &StrippedRoomJoinRulesEvent) -> Self {
274        let content = RoomJoinRulesEventContent::new(event.content.join_rule.clone());
275        Self::Original(OriginalMinimalStateEvent { content, event_id: None })
276    }
277}
278
279impl From<&StrippedRoomCanonicalAliasEvent> for MinimalStateEvent<RoomCanonicalAliasEventContent> {
280    fn from(event: &StrippedRoomCanonicalAliasEvent) -> Self {
281        let content = assign!(RoomCanonicalAliasEventContent::new(), {
282            alias: event.content.alias.clone(),
283            alt_aliases: event.content.alt_aliases.clone(),
284        });
285        Self::Original(OriginalMinimalStateEvent { content, event_id: None })
286    }
287}
288
289impl From<&StrippedRoomTopicEvent> for MinimalStateEvent<RoomTopicEventContent> {
290    fn from(event: &StrippedRoomTopicEvent) -> Self {
291        match &event.content.topic {
292            Some(topic) => {
293                let content = RoomTopicEventContent::new(topic.clone());
294                Self::Original(OriginalMinimalStateEvent { content, event_id: None })
295            }
296            None => {
297                let content = RedactedRoomTopicEventContent::new();
298                Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
299            }
300        }
301    }
302}
303
304impl From<&StrippedRoomTombstoneEvent> for MinimalStateEvent<RoomTombstoneEventContent> {
305    fn from(event: &StrippedRoomTombstoneEvent) -> Self {
306        match (&event.content.body, &event.content.replacement_room) {
307            (Some(body), Some(replacement_room)) => {
308                let content =
309                    RoomTombstoneEventContent::new(body.clone(), replacement_room.clone());
310                Self::Original(OriginalMinimalStateEvent { content, event_id: None })
311            }
312            _ => {
313                let content = RedactedRoomTombstoneEventContent::new();
314                Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
315            }
316        }
317    }
318}
319
320/// A raw sync state event and its `(type, state_key)` tuple that identifies it
321/// in the state map of the room.
322///
323/// This type can also cache the deserialized event lazily when using
324/// [`RawSyncStateEventWithKeys::deserialize_as()`].
325#[derive(Debug, Clone)]
326pub struct RawSyncStateEventWithKeys {
327    /// The raw state event.
328    pub raw: Raw<AnySyncStateEvent>,
329    /// The type of the state event.
330    pub event_type: StateEventType,
331    /// The state key of the state event.
332    pub state_key: String,
333    /// The cached deserialized event.
334    cached_event: Option<Result<AnySyncStateEvent, ()>>,
335}
336
337impl RawSyncStateEventWithKeys {
338    /// Try to construct a `RawSyncStateEventWithKeys` from the given raw state
339    /// event.
340    ///
341    /// Returns `None` if extracting the `type` or `state_key` fails.
342    pub fn try_from_raw_state_event(raw: Raw<AnySyncStateEvent>) -> Option<Self> {
343        let StateEventWithKeysDeHelper { event_type, state_key } =
344            match raw.deserialize_as_unchecked() {
345                Ok(fields) => fields,
346                Err(error) => {
347                    warn!(?error, "Couldn't deserialize type and state key of state event");
348                    return None;
349                }
350            };
351
352        // It should be a state event, so log if there is no state key.
353        let Some(state_key) = state_key else {
354            warn!(
355                ?event_type,
356                "Couldn't deserialize type and state key of state event: missing state key"
357            );
358            return None;
359        };
360
361        Some(Self { raw, event_type, state_key, cached_event: None })
362    }
363
364    /// Try to construct a `RawSyncStateEventWithKeys` from the given raw
365    /// timeline event.
366    ///
367    /// Returns `None` if deserializing the `type` or `state_key` fails, or if
368    /// the event is not a state event.
369    pub fn try_from_raw_timeline_event(raw: &Raw<AnySyncTimelineEvent>) -> Option<Self> {
370        let StateEventWithKeysDeHelper { event_type, state_key } = match raw
371            .deserialize_as_unchecked()
372        {
373            Ok(fields) => fields,
374            Err(error) => {
375                warn!(?error, "Couldn't deserialize type and optional state key of timeline event");
376                return None;
377            }
378        };
379
380        // If the state key is missing, it is not a state event according to the spec.
381        Some(Self {
382            event_type,
383            state_key: state_key?,
384            raw: raw.clone().cast_unchecked(),
385            cached_event: None,
386        })
387    }
388
389    /// Try to deserialize the raw event and return the selected variant of
390    /// `AnySyncStateEvent`.
391    ///
392    /// This method should only be called if the variant is already known. It is
393    /// considered a developer error for `as_variant_fn` to return `None`, but
394    /// this API was chosen to simplify closures that use the
395    /// [`as_variant!`](as_variant::as_variant) macro.
396    ///
397    /// The result of the event deserialization is cached for future calls to
398    /// this method.
399    ///
400    /// Returns `None` if the deserialization failed or if `as_variant_fn`
401    /// returns `None`.
402    pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&SyncStateEvent<C>>
403    where
404        F: FnOnce(&AnySyncStateEvent) -> Option<&SyncStateEvent<C>>,
405        C: StaticEventContent + StaticStateEventContent + RedactContent,
406        C::Redacted: RedactedStateEventContent,
407    {
408        let any_event = self
409            .cached_event
410            .get_or_insert_with(|| {
411                self.raw.deserialize().map_err(|error| {
412                    warn!(event_type = ?C::TYPE, ?error, "Couldn't deserialize state event");
413                })
414            })
415            .as_ref()
416            .ok()?;
417
418        let event = as_variant_fn(any_event);
419
420        if event.is_none() {
421            // This should be a developer error, or an upstream error.
422            error!(
423                expected_event_type = ?C::TYPE,
424                actual_event_type = ?any_event.event_type().to_string(),
425                "Couldn't deserialize state event: unexpected type",
426            );
427        }
428
429        event
430    }
431
432    /// Override the event cached by
433    /// [`RawSyncStateEventWithKeys::deserialize_as()`].
434    ///
435    /// When validating the content of the deserialized event, this can be used
436    /// to edit the parts that fail validation and pass the edited event down
437    /// the chain.
438    pub(crate) fn set_cached_event(&mut self, event: AnySyncStateEvent) {
439        self.cached_event = Some(Ok(event));
440    }
441}
442
443/// Helper type to deserialize a [`RawSyncStateEventWithKeys`].
444#[derive(Deserialize)]
445struct StateEventWithKeysDeHelper {
446    #[serde(rename = "type")]
447    event_type: StateEventType,
448    /// The state key is optional to be able to differentiate state events from
449    /// other messages in the timeline.
450    state_key: Option<String>,
451}