Skip to main content

matrix_sdk_base/
utils.rs

1use ruma::{
2    OwnedEventId,
3    events::{
4        AnySyncStateEvent, AnySyncTimelineEvent, PossiblyRedactedStateEventContent, RedactContent,
5        RedactedStateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
6        StrippedStateEvent, SyncStateEvent,
7        room::{
8            create::{StrippedRoomCreateEvent, SyncRoomCreateEvent},
9            member::PossiblyRedactedRoomMemberEventContent,
10        },
11    },
12    room_version_rules::RedactionRules,
13    serde::Raw,
14};
15use serde::{Deserialize, Serialize};
16use tracing::{error, warn};
17
18use crate::room::RoomCreateWithCreatorEventContent;
19
20/// A minimal state event.
21///
22/// This type can hold a possibly-redacted state event with an optional
23/// event ID. The event ID is optional so this type can also hold events from
24/// invited rooms, where event IDs are not available.
25#[derive(Clone, Debug, Deserialize, Serialize)]
26#[serde(
27    bound(serialize = "C: Serialize + Clone"),
28    from = "MinimalStateEventSerdeHelper<C>",
29    into = "MinimalStateEventSerdeHelper<C>"
30)]
31pub struct MinimalStateEvent<C: PossiblyRedactedStateEventContent + RedactContent> {
32    /// The event's content.
33    pub content: C,
34    /// The event's ID, if known.
35    pub event_id: Option<OwnedEventId>,
36}
37
38impl<C> MinimalStateEvent<C>
39where
40    C: PossiblyRedactedStateEventContent + RedactContent,
41    C::Redacted: Into<C>,
42{
43    /// Redacts this event.
44    ///
45    /// Does nothing if it is already redacted.
46    pub fn redact(&mut self, rules: &RedactionRules)
47    where
48        C: Clone,
49    {
50        self.content = self.content.clone().redact(rules).into()
51    }
52}
53
54/// Helper type to (de)serialize [`MinimalStateEvent`].
55#[derive(Serialize, Deserialize)]
56enum MinimalStateEventSerdeHelper<C> {
57    /// Previous variant for a non-redacted event.
58    Original(MinimalStateEventSerdeHelperInner<C>),
59    /// Previous variant for a redacted event.
60    Redacted(MinimalStateEventSerdeHelperInner<C>),
61    /// New variant.
62    PossiblyRedacted(MinimalStateEventSerdeHelperInner<C>),
63}
64
65impl<C> From<MinimalStateEventSerdeHelper<C>> for MinimalStateEvent<C>
66where
67    C: PossiblyRedactedStateEventContent + RedactContent,
68{
69    fn from(value: MinimalStateEventSerdeHelper<C>) -> Self {
70        match value {
71            MinimalStateEventSerdeHelper::Original(event) => event,
72            MinimalStateEventSerdeHelper::Redacted(event) => event,
73            MinimalStateEventSerdeHelper::PossiblyRedacted(event) => event,
74        }
75        .into()
76    }
77}
78
79impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelper<C>
80where
81    C: PossiblyRedactedStateEventContent + RedactContent,
82{
83    fn from(value: MinimalStateEvent<C>) -> Self {
84        Self::PossiblyRedacted(value.into())
85    }
86}
87
88#[derive(Serialize, Deserialize)]
89struct MinimalStateEventSerdeHelperInner<C> {
90    content: C,
91    event_id: Option<OwnedEventId>,
92}
93
94impl<C> From<MinimalStateEventSerdeHelperInner<C>> for MinimalStateEvent<C>
95where
96    C: PossiblyRedactedStateEventContent + RedactContent,
97{
98    fn from(value: MinimalStateEventSerdeHelperInner<C>) -> Self {
99        let MinimalStateEventSerdeHelperInner { content, event_id } = value;
100        Self { content, event_id }
101    }
102}
103
104impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelperInner<C>
105where
106    C: PossiblyRedactedStateEventContent + RedactContent,
107{
108    fn from(value: MinimalStateEvent<C>) -> Self {
109        let MinimalStateEvent { content, event_id } = value;
110        Self { content, event_id }
111    }
112}
113
114/// A minimal `m.room.member` event.
115pub type MinimalRoomMemberEvent = MinimalStateEvent<PossiblyRedactedRoomMemberEventContent>;
116
117impl<C1, C2> From<SyncStateEvent<C1>> for MinimalStateEvent<C2>
118where
119    C1: StaticStateEventContent + RedactContent + Into<C2>,
120    C1::Redacted: RedactedStateEventContent + Into<C2>,
121    C2: PossiblyRedactedStateEventContent + RedactContent,
122{
123    fn from(ev: SyncStateEvent<C1>) -> Self {
124        match ev {
125            SyncStateEvent::Original(ev) => {
126                Self { content: ev.content.into(), event_id: Some(ev.event_id) }
127            }
128            SyncStateEvent::Redacted(ev) => {
129                Self { content: ev.content.into(), event_id: Some(ev.event_id) }
130            }
131        }
132    }
133}
134
135impl<C1, C2> From<&SyncStateEvent<C1>> for MinimalStateEvent<C2>
136where
137    C1: Clone + StaticStateEventContent + RedactContent + Into<C2>,
138    C1::Redacted: Clone + RedactedStateEventContent + Into<C2>,
139    C2: PossiblyRedactedStateEventContent + RedactContent,
140{
141    fn from(ev: &SyncStateEvent<C1>) -> Self {
142        match ev {
143            SyncStateEvent::Original(ev) => {
144                Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
145            }
146            SyncStateEvent::Redacted(ev) => {
147                Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
148            }
149        }
150    }
151}
152
153impl From<&SyncRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
154    fn from(ev: &SyncRoomCreateEvent) -> Self {
155        match ev {
156            SyncStateEvent::Original(ev) => Self {
157                content: RoomCreateWithCreatorEventContent::from_event_content(
158                    ev.content.clone(),
159                    ev.sender.clone(),
160                ),
161                event_id: Some(ev.event_id.clone()),
162            },
163            SyncStateEvent::Redacted(ev) => Self {
164                content: RoomCreateWithCreatorEventContent::from_event_content(
165                    ev.content.clone(),
166                    ev.sender.clone(),
167                ),
168                event_id: Some(ev.event_id.clone()),
169            },
170        }
171    }
172}
173
174impl<C> From<StrippedStateEvent<C>> for MinimalStateEvent<C>
175where
176    C: PossiblyRedactedStateEventContent + RedactContent,
177{
178    fn from(event: StrippedStateEvent<C>) -> Self {
179        Self { content: event.content, event_id: None }
180    }
181}
182
183impl<C> From<&StrippedStateEvent<C>> for MinimalStateEvent<C>
184where
185    C: Clone + PossiblyRedactedStateEventContent + RedactContent,
186{
187    fn from(event: &StrippedStateEvent<C>) -> Self {
188        Self { content: event.content.clone(), event_id: None }
189    }
190}
191
192impl From<&StrippedRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
193    fn from(event: &StrippedRoomCreateEvent) -> Self {
194        let content = RoomCreateWithCreatorEventContent::from_event_content(
195            event.content.clone(),
196            event.sender.clone(),
197        );
198        Self { content, event_id: None }
199    }
200}
201
202/// A raw sync state event and its `(type, state_key)` tuple that identifies it
203/// in the state map of the room.
204///
205/// This type can also cache the deserialized event lazily when using
206/// [`RawSyncStateEventWithKeys::deserialize_as()`].
207#[derive(Debug, Clone)]
208pub struct RawSyncStateEventWithKeys {
209    /// The raw state event.
210    pub raw: Raw<AnySyncStateEvent>,
211    /// The type of the state event.
212    pub event_type: StateEventType,
213    /// The state key of the state event.
214    pub state_key: String,
215    /// The cached deserialized event.
216    cached_event: Option<Result<AnySyncStateEvent, ()>>,
217}
218
219impl RawSyncStateEventWithKeys {
220    /// Try to construct a `RawSyncStateEventWithKeys` from the given raw state
221    /// event.
222    ///
223    /// Returns `None` if extracting the `type` or `state_key` fails.
224    pub fn try_from_raw_state_event(raw: Raw<AnySyncStateEvent>) -> Option<Self> {
225        let StateEventWithKeysDeHelper { event_type, state_key } =
226            match raw.deserialize_as_unchecked() {
227                Ok(fields) => fields,
228                Err(error) => {
229                    warn!(?error, "Couldn't deserialize type and state key of state event");
230                    return None;
231                }
232            };
233
234        // It should be a state event, so log if there is no state key.
235        let Some(state_key) = state_key else {
236            warn!(
237                ?event_type,
238                "Couldn't deserialize type and state key of state event: missing state key"
239            );
240            return None;
241        };
242
243        Some(Self { raw, event_type, state_key, cached_event: None })
244    }
245
246    /// Try to construct a `RawSyncStateEventWithKeys` from the given raw
247    /// timeline event.
248    ///
249    /// Returns `None` if deserializing the `type` or `state_key` fails, or if
250    /// the event is not a state event.
251    pub fn try_from_raw_timeline_event(raw: &Raw<AnySyncTimelineEvent>) -> Option<Self> {
252        let StateEventWithKeysDeHelper { event_type, state_key } = match raw
253            .deserialize_as_unchecked()
254        {
255            Ok(fields) => fields,
256            Err(error) => {
257                warn!(?error, "Couldn't deserialize type and optional state key of timeline event");
258                return None;
259            }
260        };
261
262        // If the state key is missing, it is not a state event according to the spec.
263        Some(Self {
264            event_type,
265            state_key: state_key?,
266            raw: raw.clone().cast_unchecked(),
267            cached_event: None,
268        })
269    }
270
271    /// Try to deserialize the raw event and return the selected variant of
272    /// `AnySyncStateEvent`.
273    ///
274    /// This method should only be called if the variant is already known. It is
275    /// considered a developer error for `as_variant_fn` to return `None`, but
276    /// this API was chosen to simplify closures that use the
277    /// [`as_variant!`](as_variant::as_variant) macro.
278    ///
279    /// The result of the event deserialization is cached for future calls to
280    /// this method.
281    ///
282    /// Returns `None` if the deserialization failed or if `as_variant_fn`
283    /// returns `None`.
284    pub fn deserialize_as<F, C>(&mut self, as_variant_fn: F) -> Option<&SyncStateEvent<C>>
285    where
286        F: FnOnce(&AnySyncStateEvent) -> Option<&SyncStateEvent<C>>,
287        C: StaticEventContent + StaticStateEventContent + RedactContent,
288        C::Redacted: RedactedStateEventContent,
289    {
290        let any_event = self
291            .cached_event
292            .get_or_insert_with(|| {
293                self.raw.deserialize().map_err(|error| {
294                    warn!(event_type = ?C::TYPE, ?error, "Couldn't deserialize state event");
295                })
296            })
297            .as_ref()
298            .ok()?;
299
300        let event = as_variant_fn(any_event);
301
302        if event.is_none() {
303            // This should be a developer error, or an upstream error.
304            error!(
305                expected_event_type = ?C::TYPE,
306                actual_event_type = ?any_event.event_type().to_string(),
307                "Couldn't deserialize state event: unexpected type",
308            );
309        }
310
311        event
312    }
313
314    /// Override the event cached by
315    /// [`RawSyncStateEventWithKeys::deserialize_as()`].
316    ///
317    /// When validating the content of the deserialized event, this can be used
318    /// to edit the parts that fail validation and pass the edited event down
319    /// the chain.
320    pub(crate) fn set_cached_event(&mut self, event: AnySyncStateEvent) {
321        self.cached_event = Some(Ok(event));
322    }
323}
324
325/// Helper type to deserialize a [`RawSyncStateEventWithKeys`].
326#[derive(Deserialize)]
327struct StateEventWithKeysDeHelper {
328    #[serde(rename = "type")]
329    event_type: StateEventType,
330    /// The state key is optional to be able to differentiate state events from
331    /// other messages in the timeline.
332    state_key: Option<String>,
333}
334
335#[cfg(test)]
336mod tests {
337    use ruma::{event_id, events::room::name::PossiblyRedactedRoomNameEventContent};
338
339    use super::MinimalStateEvent;
340
341    #[test]
342    fn test_backward_compatible_deserialize_minimal_state_event() {
343        let event_id = event_id!("$event");
344
345        // The old format with `Original` and `Redacted` variants works.
346        let event =
347            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
348                r#"{"Original":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
349            )
350            .unwrap();
351        assert_eq!(event.content.name.as_deref(), Some("My Room"));
352        assert_eq!(event.event_id.as_deref(), Some(event_id));
353
354        let event =
355            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
356                r#"{"Redacted":{"content":{},"event_id":"$event"}}"#,
357            )
358            .unwrap();
359        assert_eq!(event.content.name, None);
360        assert_eq!(event.event_id.as_deref(), Some(event_id));
361
362        // The new format works.
363        let event =
364            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
365                r#"{"PossiblyRedacted":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
366            )
367            .unwrap();
368        assert_eq!(event.content.name.as_deref(), Some("My Room"));
369        assert_eq!(event.event_id.as_deref(), Some(event_id));
370
371        let event =
372            serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
373                r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#,
374            )
375            .unwrap();
376        assert_eq!(event.content.name, None);
377        assert_eq!(event.event_id.as_deref(), Some(event_id));
378    }
379}