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 event
24/// ID. The event ID is optional so this type can also hold events from invited
25/// 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 in
204/// 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 timeline
318    /// 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
334        // spec.
335        Some(Self {
336            event_type,
337            state_key: state_key?,
338            raw: raw.clone().cast_unchecked(),
339            cached_event: None,
340        })
341    }
342
343    /// Try to deserialize the raw event and return the selected variant of
344    /// [`AnySyncStateEvent`].
345    ///
346    /// This method should only be called if the variant is already known. It is
347    /// considered a developer error for `as_variant_fn` to return `None`, but
348    /// this API was chosen to simplify closures that use the
349    /// [`as_variant!`](as_variant::as_variant) macro.
350    ///
351    /// The result of the event deserialization is cached for future calls to
352    /// this method.
353    ///
354    /// Returns `None` if the deserialization failed or if `as_variant_fn`
355    /// returns `None`.
356    pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&SyncStateEvent<C>>
357    where
358        F: FnOnce(&AnySyncStateEvent) -> Option<&SyncStateEvent<C>>,
359        C: StaticEventContent + StaticStateEventContent + RedactContent,
360        C::Redacted: RedactedStateEventContent,
361    {
362        let any_event = self.deserialize()?;
363        let event = as_variant_fn(any_event);
364
365        if event.is_none() {
366            // This should be a developer error, or an upstream error.
367            error!(
368                expected_event_type = ?C::TYPE,
369                actual_event_type = ?any_event.event_type().to_string(),
370                "Couldn't deserialize state event: unexpected type",
371            );
372        }
373
374        event
375    }
376}
377
378impl RawStateEventWithKeys<AnyStrippedStateEvent> {
379    /// Try to deserialize the raw event and return the selected variant of
380    /// [`AnyStrippedStateEvent`].
381    ///
382    /// This method should only be called if the variant is already known. It is
383    /// considered a developer error for `as_variant_fn` to return `None`, but
384    /// this API was chosen to simplify closures that use the
385    /// [`as_variant!`](as_variant::as_variant) macro.
386    ///
387    /// The result of the event deserialization is cached for future calls to
388    /// this method.
389    ///
390    /// Returns `None` if the deserialization failed or if `as_variant_fn`
391    /// returns `None`.
392    pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&StrippedStateEvent<C>>
393    where
394        F: FnOnce(&AnyStrippedStateEvent) -> Option<&StrippedStateEvent<C>>,
395        C: StaticEventContent + PossiblyRedactedStateEventContent,
396    {
397        let any_event = self.deserialize()?;
398        let event = as_variant_fn(any_event);
399
400        if event.is_none() {
401            // This should be a developer error, or an upstream error.
402            error!(
403                expected_event_type = ?C::TYPE,
404                actual_event_type = ?any_event.event_type().to_string(),
405                "Couldn't deserialize stripped state event: unexpected type",
406            );
407        }
408
409        event
410    }
411}
412
413/// Helper type to deserialize a [`RawStateEventWithKeys`].
414#[derive(Deserialize)]
415struct StateEventWithKeysDeHelper {
416    #[serde(rename = "type")]
417    event_type: StateEventType,
418    /// The state key is optional to be able to differentiate state events from
419    /// other messages in the timeline.
420    state_key: Option<String>,
421}
422
423/// Helper trait to use common methods of `Any*StateEvent` enums.
424pub trait AnyStateEventEnum: DeserializeOwned {
425    /// Get the type of the state event.
426    fn get_event_type(&self) -> StateEventType;
427
428    /// Get the content of the state event.
429    fn get_content(&self) -> AnyPossiblyRedactedStateEventContent;
430
431    /// Get the ID of the state event, if any.
432    fn get_event_id(&self) -> Option<&EventId>;
433
434    /// Get the sender of the state event.
435    fn get_sender(&self) -> &UserId;
436
437    /// Get the timestamp of the state event, if any.
438    fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch>;
439}
440
441impl AnyStateEventEnum for AnySyncStateEvent {
442    /// Get the type of the state event.
443    fn get_event_type(&self) -> StateEventType {
444        self.event_type()
445    }
446
447    fn get_content(&self) -> AnyPossiblyRedactedStateEventContent {
448        self.content()
449    }
450
451    fn get_event_id(&self) -> Option<&EventId> {
452        Some(self.event_id())
453    }
454
455    fn get_sender(&self) -> &UserId {
456        self.sender()
457    }
458
459    fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
460        Some(self.origin_server_ts())
461    }
462}
463
464impl AnyStateEventEnum for AnyStrippedStateEvent {
465    /// Get the type of the state event.
466    fn get_event_type(&self) -> StateEventType {
467        self.event_type()
468    }
469
470    fn get_content(&self) -> AnyPossiblyRedactedStateEventContent {
471        self.content()
472    }
473
474    fn get_event_id(&self) -> Option<&EventId> {
475        None
476    }
477
478    fn get_sender(&self) -> &UserId {
479        self.sender()
480    }
481
482    fn get_origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
483        None
484    }
485}
486
487#[cfg(test)]
488mod tests {
489    use ruma::{event_id, events::room::name::PossiblyRedactedRoomNameEventContent};
490
491    use super::MinimalStateEvent;
492
493    #[test]
494    fn test_backward_compatible_deserialize_minimal_state_event() {
495        let event_id = event_id!("$event");
496
497        // The old format with `Original` and `Redacted` variants works.
498        let event =
499            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
500                r#"{"Original":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
501            )
502            .unwrap();
503        assert_eq!(event.content.name.as_deref(), Some("My Room"));
504        assert_eq!(event.event_id.as_deref(), Some(event_id));
505
506        let event =
507            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
508                r#"{"Redacted":{"content":{},"event_id":"$event"}}"#,
509            )
510            .unwrap();
511        assert_eq!(event.content.name, None);
512        assert_eq!(event.event_id.as_deref(), Some(event_id));
513
514        // The new format works.
515        let event =
516            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
517                r#"{"PossiblyRedacted":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
518            )
519            .unwrap();
520        assert_eq!(event.content.name.as_deref(), Some("My Room"));
521        assert_eq!(event.event_id.as_deref(), Some(event_id));
522
523        let event =
524            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
525                r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#,
526            )
527            .unwrap();
528        assert_eq!(event.content.name, None);
529        assert_eq!(event.event_id.as_deref(), Some(event_id));
530    }
531}