Skip to main content

matrix_sdk_base/
utils.rs

1use ruma::{
2    EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, UserId,
3    events::{
4        AnyPossiblyRedactedStateEventContent, AnyStrippedStateEvent, AnySyncStateEvent,
5        AnySyncTimelineEvent, PossiblyRedactedStateEventContent, RedactContent,
6        RedactedStateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
7        StrippedStateEvent, SyncStateEvent,
8        room::{
9            create::{StrippedRoomCreateEvent, SyncRoomCreateEvent},
10            member::PossiblyRedactedRoomMemberEventContent,
11        },
12    },
13    room_version_rules::RedactionRules,
14    serde::Raw,
15};
16use serde::{Deserialize, Serialize, de::DeserializeOwned};
17use tracing::{error, warn};
18
19use crate::room::RoomCreateWithCreatorEventContent;
20
21/// A minimal state event.
22///
23/// This type can hold a possibly-redacted state event with an optional
24/// event ID. The event ID is optional so this type can also hold events from
25/// invited rooms, where event IDs are not available.
26#[derive(Clone, Debug, Deserialize, Serialize)]
27#[serde(
28    bound(serialize = "C: Serialize + Clone"),
29    from = "MinimalStateEventSerdeHelper<C>",
30    into = "MinimalStateEventSerdeHelper<C>"
31)]
32pub struct MinimalStateEvent<C: PossiblyRedactedStateEventContent + RedactContent> {
33    /// The event's content.
34    pub content: C,
35    /// The event's ID, if known.
36    pub event_id: Option<OwnedEventId>,
37}
38
39impl<C> MinimalStateEvent<C>
40where
41    C: PossiblyRedactedStateEventContent + RedactContent,
42    C::Redacted: Into<C>,
43{
44    /// Redacts this event.
45    ///
46    /// Does nothing if it is already redacted.
47    pub fn redact(&mut self, rules: &RedactionRules)
48    where
49        C: Clone,
50    {
51        self.content = self.content.clone().redact(rules).into()
52    }
53}
54
55/// Helper type to (de)serialize [`MinimalStateEvent`].
56#[derive(Serialize, Deserialize)]
57enum MinimalStateEventSerdeHelper<C> {
58    /// Previous variant for a non-redacted event.
59    Original(MinimalStateEventSerdeHelperInner<C>),
60    /// Previous variant for a redacted event.
61    Redacted(MinimalStateEventSerdeHelperInner<C>),
62    /// New variant.
63    PossiblyRedacted(MinimalStateEventSerdeHelperInner<C>),
64}
65
66impl<C> From<MinimalStateEventSerdeHelper<C>> for MinimalStateEvent<C>
67where
68    C: PossiblyRedactedStateEventContent + RedactContent,
69{
70    fn from(value: MinimalStateEventSerdeHelper<C>) -> Self {
71        match value {
72            MinimalStateEventSerdeHelper::Original(event) => event,
73            MinimalStateEventSerdeHelper::Redacted(event) => event,
74            MinimalStateEventSerdeHelper::PossiblyRedacted(event) => event,
75        }
76        .into()
77    }
78}
79
80impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelper<C>
81where
82    C: PossiblyRedactedStateEventContent + RedactContent,
83{
84    fn from(value: MinimalStateEvent<C>) -> Self {
85        Self::PossiblyRedacted(value.into())
86    }
87}
88
89#[derive(Serialize, Deserialize)]
90struct MinimalStateEventSerdeHelperInner<C> {
91    content: C,
92    event_id: Option<OwnedEventId>,
93}
94
95impl<C> From<MinimalStateEventSerdeHelperInner<C>> for MinimalStateEvent<C>
96where
97    C: PossiblyRedactedStateEventContent + RedactContent,
98{
99    fn from(value: MinimalStateEventSerdeHelperInner<C>) -> Self {
100        let MinimalStateEventSerdeHelperInner { content, event_id } = value;
101        Self { content, event_id }
102    }
103}
104
105impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelperInner<C>
106where
107    C: PossiblyRedactedStateEventContent + RedactContent,
108{
109    fn from(value: MinimalStateEvent<C>) -> Self {
110        let MinimalStateEvent { content, event_id } = value;
111        Self { content, event_id }
112    }
113}
114
115/// A minimal `m.room.member` event.
116pub type MinimalRoomMemberEvent = MinimalStateEvent<PossiblyRedactedRoomMemberEventContent>;
117
118impl<C1, C2> From<SyncStateEvent<C1>> for MinimalStateEvent<C2>
119where
120    C1: StaticStateEventContent + RedactContent + Into<C2>,
121    C1::Redacted: RedactedStateEventContent + Into<C2>,
122    C2: PossiblyRedactedStateEventContent + RedactContent,
123{
124    fn from(ev: SyncStateEvent<C1>) -> Self {
125        match ev {
126            SyncStateEvent::Original(ev) => {
127                Self { content: ev.content.into(), event_id: Some(ev.event_id) }
128            }
129            SyncStateEvent::Redacted(ev) => {
130                Self { content: ev.content.into(), event_id: Some(ev.event_id) }
131            }
132        }
133    }
134}
135
136impl<C1, C2> From<&SyncStateEvent<C1>> for MinimalStateEvent<C2>
137where
138    C1: Clone + StaticStateEventContent + RedactContent + Into<C2>,
139    C1::Redacted: Clone + RedactedStateEventContent + Into<C2>,
140    C2: PossiblyRedactedStateEventContent + RedactContent,
141{
142    fn from(ev: &SyncStateEvent<C1>) -> Self {
143        match ev {
144            SyncStateEvent::Original(ev) => {
145                Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
146            }
147            SyncStateEvent::Redacted(ev) => {
148                Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
149            }
150        }
151    }
152}
153
154impl From<&SyncRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
155    fn from(ev: &SyncRoomCreateEvent) -> Self {
156        match ev {
157            SyncStateEvent::Original(ev) => Self {
158                content: RoomCreateWithCreatorEventContent::from_event_content(
159                    ev.content.clone(),
160                    ev.sender.clone(),
161                ),
162                event_id: Some(ev.event_id.clone()),
163            },
164            SyncStateEvent::Redacted(ev) => Self {
165                content: RoomCreateWithCreatorEventContent::from_event_content(
166                    ev.content.clone(),
167                    ev.sender.clone(),
168                ),
169                event_id: Some(ev.event_id.clone()),
170            },
171        }
172    }
173}
174
175impl<C> From<StrippedStateEvent<C>> for MinimalStateEvent<C>
176where
177    C: PossiblyRedactedStateEventContent + RedactContent,
178{
179    fn from(event: StrippedStateEvent<C>) -> Self {
180        Self { content: event.content, event_id: None }
181    }
182}
183
184impl<C> From<&StrippedStateEvent<C>> for MinimalStateEvent<C>
185where
186    C: Clone + PossiblyRedactedStateEventContent + RedactContent,
187{
188    fn from(event: &StrippedStateEvent<C>) -> Self {
189        Self { content: event.content.clone(), event_id: None }
190    }
191}
192
193impl From<&StrippedRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
194    fn from(event: &StrippedRoomCreateEvent) -> Self {
195        let content = RoomCreateWithCreatorEventContent::from_event_content(
196            event.content.clone(),
197            event.sender.clone(),
198        );
199        Self { content, event_id: None }
200    }
201}
202
203/// A raw state event and its `(type, state_key)` tuple that identifies it
204/// in the state map of the room.
205///
206/// This type can also cache the deserialized event lazily when using
207/// [`RawStateEventWithKeys::deserialize_as()`].
208#[derive(Debug, Clone)]
209pub struct RawStateEventWithKeys<T: AnyStateEventEnum> {
210    /// The raw state event.
211    pub raw: Raw<T>,
212    /// The type of the state event.
213    pub event_type: StateEventType,
214    /// The state key of the state event.
215    pub state_key: String,
216    /// The cached deserialized event.
217    cached_event: Option<Result<T, ()>>,
218}
219
220impl<T: AnyStateEventEnum> RawStateEventWithKeys<T> {
221    /// Try to construct a `RawStateEventWithKeys` from the given raw state
222    /// event.
223    ///
224    /// Returns `None` if extracting the `type` or `state_key` fails.
225    pub fn try_from_raw_state_event(raw: Raw<T>) -> Option<Self> {
226        let StateEventWithKeysDeHelper { event_type, state_key } =
227            match raw.deserialize_as_unchecked() {
228                Ok(fields) => fields,
229                Err(error) => {
230                    warn!(?error, "Couldn't deserialize type and state key of state event");
231                    return None;
232                }
233            };
234
235        // It should be a state event, so log if there is no state key.
236        let Some(state_key) = state_key else {
237            warn!(
238                ?event_type,
239                "Couldn't deserialize type and state key of state event: missing state key"
240            );
241            return None;
242        };
243
244        Some(Self { raw, event_type, state_key, cached_event: None })
245    }
246
247    /// Try to deserialize the raw event.
248    ///
249    /// The result of the event deserialization is cached for future calls to
250    /// this method.
251    ///
252    /// Returns `None` if the deserialization failed.
253    pub fn deserialize(&mut self) -> Option<&T> {
254        self.cached_event
255            .get_or_insert_with(|| {
256                self.raw.deserialize().map_err(|error| {
257                    warn!(?error, "Couldn't deserialize state event");
258                })
259            })
260            .as_ref()
261            .ok()
262    }
263
264    /// Try to deserialize the raw event and return it as a
265    /// [`MinimalStateEvent`] using the selected variant of
266    /// [`AnyPossiblyRedactedStateEventContent`].
267    ///
268    /// This method should only be called if the variant is already known. It is
269    /// considered a developer error for `as_variant_fn` to return `None`, but
270    /// this API was chosen to simplify closures that use the
271    /// [`as_variant!`](as_variant::as_variant) macro.
272    ///
273    /// The result of the event deserialization is cached for future calls to
274    /// this method.
275    ///
276    /// Returns `None` if the deserialization failed or if `as_variant_fn`
277    /// returns `None`.
278    pub fn deserialize_as_minimal_event<F, C>(
279        &mut self,
280        as_variant_fn: F,
281    ) -> Option<MinimalStateEvent<C>>
282    where
283        F: FnOnce(AnyPossiblyRedactedStateEventContent) -> Option<C>,
284        C: StaticEventContent + PossiblyRedactedStateEventContent + RedactContent,
285    {
286        let any_event = self.deserialize()?;
287        let any_content = any_event.get_content();
288
289        let Some(content) = as_variant_fn(any_content) else {
290            // This should be a developer error, or an upstream error.
291            error!(
292                expected_event_type = ?C::TYPE,
293                actual_event_type = ?any_event.get_event_type().to_string(),
294                "Couldn't deserialize state event content: unexpected type",
295            );
296            return None;
297        };
298
299        Some(MinimalStateEvent {
300            content,
301            event_id: any_event.get_event_id().map(ToOwned::to_owned),
302        })
303    }
304
305    /// Override the event cached by
306    /// [`RawStateEventWithKeys::deserialize_as()`].
307    ///
308    /// When validating the content of the deserialized event, this can be used
309    /// to edit the parts that fail validation and pass the edited event down
310    /// the chain.
311    pub(crate) fn set_cached_event(&mut self, event: T) {
312        self.cached_event = Some(Ok(event));
313    }
314}
315
316impl RawStateEventWithKeys<AnySyncStateEvent> {
317    /// Try to construct a `RawStateEventWithKeys` from the given raw
318    /// timeline event.
319    ///
320    /// Returns `None` if deserializing the `type` or `state_key` fails, or if
321    /// the event is not a state event.
322    pub fn try_from_raw_timeline_event(raw: &Raw<AnySyncTimelineEvent>) -> Option<Self> {
323        let StateEventWithKeysDeHelper { event_type, state_key } = match raw
324            .deserialize_as_unchecked()
325        {
326            Ok(fields) => fields,
327            Err(error) => {
328                warn!(?error, "Couldn't deserialize type and optional state key of timeline event");
329                return None;
330            }
331        };
332
333        // If the state key is missing, it is not a state event according to the spec.
334        Some(Self {
335            event_type,
336            state_key: state_key?,
337            raw: raw.clone().cast_unchecked(),
338            cached_event: None,
339        })
340    }
341
342    /// Try to deserialize the raw event and return the selected variant of
343    /// [`AnySyncStateEvent`].
344    ///
345    /// This method should only be called if the variant is already known. It is
346    /// considered a developer error for `as_variant_fn` to return `None`, but
347    /// this API was chosen to simplify closures that use the
348    /// [`as_variant!`](as_variant::as_variant) macro.
349    ///
350    /// The result of the event deserialization is cached for future calls to
351    /// this method.
352    ///
353    /// Returns `None` if the deserialization failed or if `as_variant_fn`
354    /// returns `None`.
355    pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&SyncStateEvent<C>>
356    where
357        F: FnOnce(&AnySyncStateEvent) -> Option<&SyncStateEvent<C>>,
358        C: StaticEventContent + StaticStateEventContent + RedactContent,
359        C::Redacted: RedactedStateEventContent,
360    {
361        let any_event = self.deserialize()?;
362        let event = as_variant_fn(any_event);
363
364        if event.is_none() {
365            // This should be a developer error, or an upstream error.
366            error!(
367                expected_event_type = ?C::TYPE,
368                actual_event_type = ?any_event.event_type().to_string(),
369                "Couldn't deserialize state event: unexpected type",
370            );
371        }
372
373        event
374    }
375}
376
377impl RawStateEventWithKeys<AnyStrippedStateEvent> {
378    /// Try to deserialize the raw event and return the selected variant of
379    /// [`AnyStrippedStateEvent`].
380    ///
381    /// This method should only be called if the variant is already known. It is
382    /// considered a developer error for `as_variant_fn` to return `None`, but
383    /// this API was chosen to simplify closures that use the
384    /// [`as_variant!`](as_variant::as_variant) macro.
385    ///
386    /// The result of the event deserialization is cached for future calls to
387    /// this method.
388    ///
389    /// Returns `None` if the deserialization failed or if `as_variant_fn`
390    /// returns `None`.
391    pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&StrippedStateEvent<C>>
392    where
393        F: FnOnce(&AnyStrippedStateEvent) -> Option<&StrippedStateEvent<C>>,
394        C: StaticEventContent + PossiblyRedactedStateEventContent,
395    {
396        let any_event = self.deserialize()?;
397        let event = as_variant_fn(any_event);
398
399        if event.is_none() {
400            // This should be a developer error, or an upstream error.
401            error!(
402                expected_event_type = ?C::TYPE,
403                actual_event_type = ?any_event.event_type().to_string(),
404                "Couldn't deserialize stripped state event: unexpected type",
405            );
406        }
407
408        event
409    }
410}
411
412/// Helper type to deserialize a [`RawStateEventWithKeys`].
413#[derive(Deserialize)]
414struct StateEventWithKeysDeHelper {
415    #[serde(rename = "type")]
416    event_type: StateEventType,
417    /// The state key is optional to be able to differentiate state events from
418    /// other messages in the timeline.
419    state_key: Option<String>,
420}
421
422/// Helper trait to use common methods of `Any*StateEvent` enums.
423pub trait AnyStateEventEnum: DeserializeOwned {
424    /// Get the type of the state event.
425    fn get_event_type(&self) -> StateEventType;
426
427    /// Get the content of the state event.
428    fn get_content(&self) -> AnyPossiblyRedactedStateEventContent;
429
430    /// Get the ID of the state event, if any.
431    fn get_event_id(&self) -> Option<&EventId>;
432
433    /// Get the sender of the state event.
434    fn get_sender(&self) -> &UserId;
435
436    /// Get the timestamp of the state event, if any.
437    fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch>;
438}
439
440impl AnyStateEventEnum for AnySyncStateEvent {
441    /// Get the type of the state event.
442    fn get_event_type(&self) -> StateEventType {
443        self.event_type()
444    }
445
446    fn get_content(&self) -> AnyPossiblyRedactedStateEventContent {
447        self.content()
448    }
449
450    fn get_event_id(&self) -> Option<&EventId> {
451        Some(self.event_id())
452    }
453
454    fn get_sender(&self) -> &UserId {
455        self.sender()
456    }
457
458    fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
459        Some(self.origin_server_ts())
460    }
461}
462
463impl AnyStateEventEnum for AnyStrippedStateEvent {
464    /// Get the type of the state event.
465    fn get_event_type(&self) -> StateEventType {
466        self.event_type()
467    }
468
469    fn get_content(&self) -> AnyPossiblyRedactedStateEventContent {
470        self.content()
471    }
472
473    fn get_event_id(&self) -> Option<&EventId> {
474        None
475    }
476
477    fn get_sender(&self) -> &UserId {
478        self.sender()
479    }
480
481    fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
482        None
483    }
484}
485
486#[cfg(test)]
487mod tests {
488    use ruma::{event_id, events::room::name::PossiblyRedactedRoomNameEventContent};
489
490    use super::MinimalStateEvent;
491
492    #[test]
493    fn test_backward_compatible_deserialize_minimal_state_event() {
494        let event_id = event_id!("$event");
495
496        // The old format with `Original` and `Redacted` variants works.
497        let event =
498            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
499                r#"{"Original":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
500            )
501            .unwrap();
502        assert_eq!(event.content.name.as_deref(), Some("My Room"));
503        assert_eq!(event.event_id.as_deref(), Some(event_id));
504
505        let event =
506            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
507                r#"{"Redacted":{"content":{},"event_id":"$event"}}"#,
508            )
509            .unwrap();
510        assert_eq!(event.content.name, None);
511        assert_eq!(event.event_id.as_deref(), Some(event_id));
512
513        // The new format works.
514        let event =
515            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
516                r#"{"PossiblyRedacted":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
517            )
518            .unwrap();
519        assert_eq!(event.content.name.as_deref(), Some("My Room"));
520        assert_eq!(event.event_id.as_deref(), Some(event_id));
521
522        let event =
523            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
524                r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#,
525            )
526            .unwrap();
527        assert_eq!(event.content.name, None);
528        assert_eq!(event.event_id.as_deref(), Some(event_id));
529    }
530}