matrix_sdk_base/
latest_event.rs

1//! Utilities for working with events to decide whether they are suitable for
2//! use as a [crate::Room::latest_event].
3
4use matrix_sdk_common::deserialized_responses::TimelineEvent;
5use ruma::{MilliSecondsSinceUnixEpoch, MxcUri, OwnedEventId};
6#[cfg(feature = "e2e-encryption")]
7use ruma::{
8    UserId,
9    events::{
10        AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent,
11        call::invite::SyncCallInviteEvent,
12        poll::unstable_start::SyncUnstablePollStartEvent,
13        relation::RelationType,
14        room::{
15            member::{MembershipState, SyncRoomMemberEvent},
16            message::{MessageType, SyncRoomMessageEvent},
17            power_levels::RoomPowerLevels,
18        },
19        rtc::notification::SyncRtcNotificationEvent,
20        sticker::SyncStickerEvent,
21    },
22};
23use serde::{Deserialize, Serialize};
24
25use crate::{MinimalRoomMemberEvent, store::SerializableEventContent};
26
27/// A latest event value!
28#[derive(Debug, Default, Clone, Serialize, Deserialize)]
29pub enum LatestEventValue {
30    /// No value has been computed yet, or no candidate value was found.
31    #[default]
32    None,
33
34    /// The latest event represents a remote event.
35    Remote(RemoteLatestEventValue),
36
37    /// The latest event represents a local event that is sending.
38    LocalIsSending(LocalLatestEventValue),
39
40    /// The latest event represents a local event that cannot be sent, either
41    /// because a previous local event, or this local event cannot be sent.
42    LocalCannotBeSent(LocalLatestEventValue),
43}
44
45impl LatestEventValue {
46    /// Get the timestamp of the [`LatestEventValue`].
47    ///
48    /// If it's [`None`], it returns `None`. If it's [`Remote`], it returns the
49    /// [`TimelineEvent::timestamp`]. If it's [`LocalIsSending`] or
50    /// [`LocalCannotBeSent`], it returns the
51    /// [`LocalLatestEventValue::timestamp`] value.
52    ///
53    /// [`None`]: LatestEventValue::None
54    /// [`Remote`]: LatestEventValue::Remote
55    /// [`LocalIsSending`]: LatestEventValue::LocalIsSending
56    /// [`LocalCannotBeSent`]: LatestEventValue::LocalCannotBeSent
57    pub fn timestamp(&self) -> Option<MilliSecondsSinceUnixEpoch> {
58        match self {
59            Self::None => None,
60            Self::Remote(remote_latest_event_value) => remote_latest_event_value.timestamp(),
61            Self::LocalIsSending(LocalLatestEventValue { timestamp, .. })
62            | Self::LocalCannotBeSent(LocalLatestEventValue { timestamp, .. }) => Some(*timestamp),
63        }
64    }
65
66    /// Check whether the [`LatestEventValue`] represents a local value or not,
67    /// i.e. it is [`LocalIsSending`] or [`LocalCannotBeSent`].
68    ///
69    /// [`LocalIsSending`]: LatestEventValue::LocalIsSending
70    /// [`LocalCannotBeSent`]: LatestEventValue::LocalCannotBeSent
71    pub fn is_local(&self) -> bool {
72        match self {
73            Self::LocalIsSending(_) | Self::LocalCannotBeSent(_) => true,
74            Self::None | Self::Remote(_) => false,
75        }
76    }
77}
78
79/// Represents the value for [`LatestEventValue::Remote`].
80pub type RemoteLatestEventValue = TimelineEvent;
81
82/// Represents the value for [`LatestEventValue::LocalIsSending`] and
83/// [`LatestEventValue::LocalCannotBeSent`].
84#[derive(Debug, Clone, Serialize, Deserialize)]
85pub struct LocalLatestEventValue {
86    /// The time where the event has been created (by this module).
87    pub timestamp: MilliSecondsSinceUnixEpoch,
88
89    /// The content of the local event.
90    pub content: SerializableEventContent,
91}
92
93#[cfg(test)]
94mod tests_latest_event_value {
95    use ruma::{
96        MilliSecondsSinceUnixEpoch,
97        events::{AnyMessageLikeEventContent, room::message::RoomMessageEventContent},
98        serde::Raw,
99        uint,
100    };
101    use serde_json::json;
102
103    use super::{LatestEventValue, LocalLatestEventValue, RemoteLatestEventValue};
104    use crate::store::SerializableEventContent;
105
106    #[test]
107    fn test_timestamp_with_none() {
108        let value = LatestEventValue::None;
109
110        assert_eq!(value.timestamp(), None);
111    }
112
113    #[test]
114    fn test_timestamp_with_remote() {
115        let value = LatestEventValue::Remote(RemoteLatestEventValue::from_plaintext(
116            Raw::from_json_string(
117                json!({
118                    "content": RoomMessageEventContent::text_plain("raclette"),
119                    "type": "m.room.message",
120                    "event_id": "$ev0",
121                    "room_id": "!r0",
122                    "origin_server_ts": 42,
123                    "sender": "@mnt_io:matrix.org",
124                })
125                .to_string(),
126            )
127            .unwrap(),
128        ));
129
130        assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
131    }
132
133    #[test]
134    fn test_timestamp_with_local_is_sending() {
135        let value = LatestEventValue::LocalIsSending(LocalLatestEventValue {
136            timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
137            content: SerializableEventContent::from_raw(
138                Raw::new(&AnyMessageLikeEventContent::RoomMessage(
139                    RoomMessageEventContent::text_plain("raclette"),
140                ))
141                .unwrap(),
142                "m.room.message".to_owned(),
143            ),
144        });
145
146        assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
147    }
148
149    #[test]
150    fn test_timestamp_with_local_cannot_be_sent() {
151        let value = LatestEventValue::LocalCannotBeSent(LocalLatestEventValue {
152            timestamp: MilliSecondsSinceUnixEpoch(uint!(42)),
153            content: SerializableEventContent::from_raw(
154                Raw::new(&AnyMessageLikeEventContent::RoomMessage(
155                    RoomMessageEventContent::text_plain("raclette"),
156                ))
157                .unwrap(),
158                "m.room.message".to_owned(),
159            ),
160        });
161
162        assert_eq!(value.timestamp(), Some(MilliSecondsSinceUnixEpoch(uint!(42))));
163    }
164}
165
166/// Represents a decision about whether an event could be stored as the latest
167/// event in a room. Variants starting with Yes indicate that this message could
168/// be stored, and provide the inner event information, and those starting with
169/// a No indicate that it could not, and give a reason.
170#[cfg(feature = "e2e-encryption")]
171#[derive(Debug)]
172pub enum PossibleLatestEvent<'a> {
173    /// This message is suitable - it is an m.room.message
174    YesRoomMessage(&'a SyncRoomMessageEvent),
175    /// This message is suitable - it is a sticker
176    YesSticker(&'a SyncStickerEvent),
177    /// This message is suitable - it is a poll
178    YesPoll(&'a SyncUnstablePollStartEvent),
179
180    /// This message is suitable - it is a call invite
181    YesCallInvite(&'a SyncCallInviteEvent),
182
183    /// This message is suitable - it's a call notification
184    YesRtcNotification(&'a SyncRtcNotificationEvent),
185
186    /// This state event is suitable - it's a knock membership change
187    /// that can be handled by the current user.
188    YesKnockedStateEvent(&'a SyncRoomMemberEvent),
189
190    // Later: YesState(),
191    // Later: YesReaction(),
192    /// Not suitable - it's a state event
193    NoUnsupportedEventType,
194    /// Not suitable - it's not a m.room.message or an edit/replacement
195    NoUnsupportedMessageLikeType,
196    /// Not suitable - it's encrypted
197    NoEncrypted,
198}
199
200/// Decide whether an event could be stored as the latest event in a room.
201/// Returns a LatestEvent representing our decision.
202#[cfg(feature = "e2e-encryption")]
203pub fn is_suitable_for_latest_event<'a>(
204    event: &'a AnySyncTimelineEvent,
205    power_levels_info: Option<(&'a UserId, &'a RoomPowerLevels)>,
206) -> PossibleLatestEvent<'a> {
207    match event {
208        // Suitable - we have an m.room.message that was not redacted or edited
209        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(message)) => {
210            if let Some(original_message) = message.as_original() {
211                // Don't show incoming verification requests
212                if let MessageType::VerificationRequest(_) = original_message.content.msgtype {
213                    return PossibleLatestEvent::NoUnsupportedMessageLikeType;
214                }
215
216                // Check if this is a replacement for another message. If it is, ignore it
217                let is_replacement =
218                    original_message.content.relates_to.as_ref().is_some_and(|relates_to| {
219                        if let Some(relation_type) = relates_to.rel_type() {
220                            relation_type == RelationType::Replacement
221                        } else {
222                            false
223                        }
224                    });
225
226                if is_replacement {
227                    PossibleLatestEvent::NoUnsupportedMessageLikeType
228                } else {
229                    PossibleLatestEvent::YesRoomMessage(message)
230                }
231            } else {
232                PossibleLatestEvent::YesRoomMessage(message)
233            }
234        }
235
236        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollStart(poll)) => {
237            PossibleLatestEvent::YesPoll(poll)
238        }
239
240        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(invite)) => {
241            PossibleLatestEvent::YesCallInvite(invite)
242        }
243
244        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RtcNotification(notify)) => {
245            PossibleLatestEvent::YesRtcNotification(notify)
246        }
247
248        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Sticker(sticker)) => {
249            PossibleLatestEvent::YesSticker(sticker)
250        }
251
252        // Encrypted events are not suitable
253        AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(_)) => {
254            PossibleLatestEvent::NoEncrypted
255        }
256
257        // Later, if we support reactions:
258        // AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Reaction(_))
259
260        // MessageLike, but not one of the types we want to show in message previews, so not
261        // suitable
262        AnySyncTimelineEvent::MessageLike(_) => PossibleLatestEvent::NoUnsupportedMessageLikeType,
263
264        // We don't currently support most state events
265        AnySyncTimelineEvent::State(state) => {
266            // But we make an exception for knocked state events *if* the current user
267            // can either accept or decline them
268            if let AnySyncStateEvent::RoomMember(member) = state
269                && matches!(member.membership(), MembershipState::Knock)
270            {
271                let can_accept_or_decline_knocks = match power_levels_info {
272                    Some((own_user_id, room_power_levels)) => {
273                        room_power_levels.user_can_invite(own_user_id)
274                            || room_power_levels.user_can_kick(own_user_id)
275                    }
276                    _ => false,
277                };
278
279                // The current user can act on the knock changes, so they should be
280                // displayed
281                if can_accept_or_decline_knocks {
282                    return PossibleLatestEvent::YesKnockedStateEvent(member);
283                }
284            }
285            PossibleLatestEvent::NoUnsupportedEventType
286        }
287    }
288}
289
290/// Represent all information required to represent a latest event in an
291/// efficient way.
292///
293/// ## Implementation details
294///
295/// Serialization and deserialization should be a breeze, but we introduced a
296/// change in the format without realizing, and without a migration. Ideally,
297/// this would be handled with a `serde(untagged)` enum that would be used to
298/// deserialize in either the older format, or to the new format. Unfortunately,
299/// untagged enums don't play nicely with `serde_json::value::RawValue`,
300/// so we did have to implement a custom `Deserialize` for `LatestEvent`, that
301/// first deserializes the thing as a raw JSON value, and then deserializes the
302/// JSON string as one variant or the other.
303///
304/// Because of that, `LatestEvent` should only be (de)serialized using
305/// serde_json.
306///
307/// Whenever you introduce new fields to `LatestEvent` make sure to add them to
308/// `SerializedLatestEvent` too.
309#[derive(Clone, Debug, Serialize)]
310pub struct LatestEvent {
311    /// The actual event.
312    event: TimelineEvent,
313
314    /// The member profile of the event' sender.
315    #[serde(skip_serializing_if = "Option::is_none")]
316    sender_profile: Option<MinimalRoomMemberEvent>,
317
318    /// The name of the event' sender is ambiguous.
319    #[serde(skip_serializing_if = "Option::is_none")]
320    sender_name_is_ambiguous: Option<bool>,
321}
322
323#[derive(Deserialize)]
324struct SerializedLatestEvent {
325    /// The actual event.
326    event: TimelineEvent,
327
328    /// The member profile of the event' sender.
329    #[serde(skip_serializing_if = "Option::is_none")]
330    sender_profile: Option<MinimalRoomMemberEvent>,
331
332    /// The name of the event' sender is ambiguous.
333    #[serde(skip_serializing_if = "Option::is_none")]
334    sender_name_is_ambiguous: Option<bool>,
335}
336
337// Note: this deserialize implementation for LatestEvent will *only* work with
338// serde_json.
339impl<'de> Deserialize<'de> for LatestEvent {
340    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
341    where
342        D: serde::Deserializer<'de>,
343    {
344        let raw: Box<serde_json::value::RawValue> = Box::deserialize(deserializer)?;
345
346        let mut variant_errors = Vec::new();
347
348        match serde_json::from_str::<SerializedLatestEvent>(raw.get()) {
349            Ok(value) => {
350                return Ok(LatestEvent {
351                    event: value.event,
352                    sender_profile: value.sender_profile,
353                    sender_name_is_ambiguous: value.sender_name_is_ambiguous,
354                });
355            }
356            Err(err) => variant_errors.push(err),
357        }
358
359        match serde_json::from_str::<TimelineEvent>(raw.get()) {
360            Ok(value) => {
361                return Ok(LatestEvent {
362                    event: value,
363                    sender_profile: None,
364                    sender_name_is_ambiguous: None,
365                });
366            }
367            Err(err) => variant_errors.push(err),
368        }
369
370        Err(serde::de::Error::custom(format!(
371            "data did not match any variant of serialized LatestEvent (using serde_json). \
372             Observed errors: {variant_errors:?}"
373        )))
374    }
375}
376
377impl LatestEvent {
378    /// Create a new [`LatestEvent`] without the sender's profile.
379    pub fn new(event: TimelineEvent) -> Self {
380        Self { event, sender_profile: None, sender_name_is_ambiguous: None }
381    }
382
383    /// Create a new [`LatestEvent`] with maybe the sender's profile.
384    pub fn new_with_sender_details(
385        event: TimelineEvent,
386        sender_profile: Option<MinimalRoomMemberEvent>,
387        sender_name_is_ambiguous: Option<bool>,
388    ) -> Self {
389        Self { event, sender_profile, sender_name_is_ambiguous }
390    }
391
392    /// Transform [`Self`] into an event.
393    pub fn into_event(self) -> TimelineEvent {
394        self.event
395    }
396
397    /// Get a reference to the event.
398    pub fn event(&self) -> &TimelineEvent {
399        &self.event
400    }
401
402    /// Get a mutable reference to the event.
403    pub fn event_mut(&mut self) -> &mut TimelineEvent {
404        &mut self.event
405    }
406
407    /// Get the event ID.
408    pub fn event_id(&self) -> Option<OwnedEventId> {
409        self.event.event_id()
410    }
411
412    /// Check whether [`Self`] has a sender profile.
413    pub fn has_sender_profile(&self) -> bool {
414        self.sender_profile.is_some()
415    }
416
417    /// Return the sender's display name if it was known at the time [`Self`]
418    /// was built.
419    pub fn sender_display_name(&self) -> Option<&str> {
420        self.sender_profile.as_ref().and_then(|profile| {
421            profile.as_original().and_then(|event| event.content.displayname.as_deref())
422        })
423    }
424
425    /// Return `Some(true)` if the sender's name is ambiguous, `Some(false)` if
426    /// it isn't, `None` if ambiguity detection wasn't possible at the time
427    /// [`Self`] was built.
428    pub fn sender_name_ambiguous(&self) -> Option<bool> {
429        self.sender_name_is_ambiguous
430    }
431
432    /// Return the sender's avatar URL if it was known at the time [`Self`] was
433    /// built.
434    pub fn sender_avatar_url(&self) -> Option<&MxcUri> {
435        self.sender_profile.as_ref().and_then(|profile| {
436            profile.as_original().and_then(|event| event.content.avatar_url.as_deref())
437        })
438    }
439}
440
441#[cfg(test)]
442mod tests {
443    #[cfg(feature = "e2e-encryption")]
444    use std::collections::BTreeMap;
445    use std::time::Duration;
446
447    #[cfg(feature = "e2e-encryption")]
448    use assert_matches::assert_matches;
449    #[cfg(feature = "e2e-encryption")]
450    use assert_matches2::assert_let;
451    use matrix_sdk_common::deserialized_responses::TimelineEvent;
452    #[cfg(feature = "e2e-encryption")]
453    use ruma::{
454        MilliSecondsSinceUnixEpoch, UInt, VoipVersionId,
455        events::{
456            AnySyncMessageLikeEvent, AnySyncStateEvent, AnySyncTimelineEvent, EmptyStateKey,
457            MessageLikeUnsigned, OriginalSyncMessageLikeEvent, OriginalSyncStateEvent,
458            RedactedSyncMessageLikeEvent, RedactedUnsigned, StateUnsigned, SyncMessageLikeEvent,
459            call::{
460                SessionDescription,
461                invite::{CallInviteEventContent, SyncCallInviteEvent},
462            },
463            poll::{
464                unstable_response::{
465                    SyncUnstablePollResponseEvent, UnstablePollResponseEventContent,
466                },
467                unstable_start::{
468                    NewUnstablePollStartEventContent, SyncUnstablePollStartEvent,
469                    UnstablePollAnswer, UnstablePollStartContentBlock,
470                },
471            },
472            relation::Replacement,
473            room::{
474                ImageInfo, MediaSource,
475                encrypted::{
476                    EncryptedEventScheme, OlmV1Curve25519AesSha2Content, RoomEncryptedEventContent,
477                    SyncRoomEncryptedEvent,
478                },
479                message::{
480                    ImageMessageEventContent, MessageType, RedactedRoomMessageEventContent,
481                    Relation, RoomMessageEventContent, SyncRoomMessageEvent,
482                },
483                topic::{RoomTopicEventContent, SyncRoomTopicEvent},
484            },
485            sticker::{StickerEventContent, SyncStickerEvent},
486        },
487        owned_event_id, owned_mxc_uri, owned_user_id,
488    };
489    use ruma::{
490        events::rtc::notification::{
491            NotificationType, RtcNotificationEventContent, SyncRtcNotificationEvent,
492        },
493        serde::Raw,
494    };
495    use serde_json::json;
496
497    use super::LatestEvent;
498    #[cfg(feature = "e2e-encryption")]
499    use super::{PossibleLatestEvent, is_suitable_for_latest_event};
500
501    #[cfg(feature = "e2e-encryption")]
502    #[test]
503    fn test_room_messages_are_suitable() {
504        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
505            SyncRoomMessageEvent::Original(OriginalSyncMessageLikeEvent {
506                content: RoomMessageEventContent::new(MessageType::Image(
507                    ImageMessageEventContent::new(
508                        "".to_owned(),
509                        MediaSource::Plain(owned_mxc_uri!("mxc://example.com/1")),
510                    ),
511                )),
512                event_id: owned_event_id!("$1"),
513                sender: owned_user_id!("@a:b.c"),
514                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
515                unsigned: MessageLikeUnsigned::new(),
516            }),
517        ));
518        assert_let!(
519            PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Original(m)) =
520                is_suitable_for_latest_event(&event, None)
521        );
522
523        assert_eq!(m.content.msgtype.msgtype(), "m.image");
524    }
525
526    #[cfg(feature = "e2e-encryption")]
527    #[test]
528    fn test_polls_are_suitable() {
529        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollStart(
530            SyncUnstablePollStartEvent::Original(OriginalSyncMessageLikeEvent {
531                content: NewUnstablePollStartEventContent::new(UnstablePollStartContentBlock::new(
532                    "do you like rust?",
533                    vec![UnstablePollAnswer::new("id", "yes")].try_into().unwrap(),
534                ))
535                .into(),
536                event_id: owned_event_id!("$1"),
537                sender: owned_user_id!("@a:b.c"),
538                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
539                unsigned: MessageLikeUnsigned::new(),
540            }),
541        ));
542        assert_let!(
543            PossibleLatestEvent::YesPoll(SyncMessageLikeEvent::Original(m)) =
544                is_suitable_for_latest_event(&event, None)
545        );
546
547        assert_eq!(m.content.poll_start().question.text, "do you like rust?");
548    }
549
550    #[cfg(feature = "e2e-encryption")]
551    #[test]
552    fn test_call_invites_are_suitable() {
553        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::CallInvite(
554            SyncCallInviteEvent::Original(OriginalSyncMessageLikeEvent {
555                content: CallInviteEventContent::new(
556                    "call_id".into(),
557                    UInt::new(123).unwrap(),
558                    SessionDescription::new("".into(), "".into()),
559                    VoipVersionId::V1,
560                ),
561                event_id: owned_event_id!("$1"),
562                sender: owned_user_id!("@a:b.c"),
563                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
564                unsigned: MessageLikeUnsigned::new(),
565            }),
566        ));
567        assert_let!(
568            PossibleLatestEvent::YesCallInvite(SyncMessageLikeEvent::Original(_)) =
569                is_suitable_for_latest_event(&event, None)
570        );
571    }
572
573    #[cfg(feature = "e2e-encryption")]
574    #[test]
575    fn test_call_notifications_are_suitable() {
576        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RtcNotification(
577            SyncRtcNotificationEvent::Original(OriginalSyncMessageLikeEvent {
578                content: RtcNotificationEventContent::new(
579                    MilliSecondsSinceUnixEpoch::now(),
580                    Duration::new(30, 0),
581                    NotificationType::Ring,
582                ),
583                event_id: owned_event_id!("$1"),
584                sender: owned_user_id!("@a:b.c"),
585                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
586                unsigned: MessageLikeUnsigned::new(),
587            }),
588        ));
589        assert_let!(
590            PossibleLatestEvent::YesRtcNotification(SyncMessageLikeEvent::Original(_)) =
591                is_suitable_for_latest_event(&event, None)
592        );
593    }
594
595    #[cfg(feature = "e2e-encryption")]
596    #[test]
597    fn test_stickers_are_suitable() {
598        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::Sticker(
599            SyncStickerEvent::Original(OriginalSyncMessageLikeEvent {
600                content: StickerEventContent::new(
601                    "sticker!".to_owned(),
602                    ImageInfo::new(),
603                    owned_mxc_uri!("mxc://example.com/1"),
604                ),
605                event_id: owned_event_id!("$1"),
606                sender: owned_user_id!("@a:b.c"),
607                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
608                unsigned: MessageLikeUnsigned::new(),
609            }),
610        ));
611
612        assert_matches!(
613            is_suitable_for_latest_event(&event, None),
614            PossibleLatestEvent::YesSticker(SyncStickerEvent::Original(_))
615        );
616    }
617
618    #[cfg(feature = "e2e-encryption")]
619    #[test]
620    fn test_different_types_of_messagelike_are_unsuitable() {
621        let event =
622            AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::UnstablePollResponse(
623                SyncUnstablePollResponseEvent::Original(OriginalSyncMessageLikeEvent {
624                    content: UnstablePollResponseEventContent::new(
625                        vec![String::from("option1")],
626                        owned_event_id!("$1"),
627                    ),
628                    event_id: owned_event_id!("$2"),
629                    sender: owned_user_id!("@a:b.c"),
630                    origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
631                    unsigned: MessageLikeUnsigned::new(),
632                }),
633            ));
634
635        assert_matches!(
636            is_suitable_for_latest_event(&event, None),
637            PossibleLatestEvent::NoUnsupportedMessageLikeType
638        );
639    }
640
641    #[cfg(feature = "e2e-encryption")]
642    #[test]
643    fn test_redacted_messages_are_suitable() {
644        // Ruma does not allow constructing UnsignedRoomRedactionEvent instances.
645        let room_redaction_event = serde_json::from_value(json!({
646            "content": {},
647            "event_id": "$redaction",
648            "sender": "@x:y.za",
649            "origin_server_ts": 223543,
650            "unsigned": { "reason": "foo" }
651        }))
652        .unwrap();
653
654        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
655            SyncRoomMessageEvent::Redacted(RedactedSyncMessageLikeEvent {
656                content: RedactedRoomMessageEventContent::new(),
657                event_id: owned_event_id!("$1"),
658                sender: owned_user_id!("@a:b.c"),
659                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
660                unsigned: RedactedUnsigned::new(room_redaction_event),
661            }),
662        ));
663
664        assert_matches!(
665            is_suitable_for_latest_event(&event, None),
666            PossibleLatestEvent::YesRoomMessage(SyncMessageLikeEvent::Redacted(_))
667        );
668    }
669
670    #[cfg(feature = "e2e-encryption")]
671    #[test]
672    fn test_encrypted_messages_are_unsuitable() {
673        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomEncrypted(
674            SyncRoomEncryptedEvent::Original(OriginalSyncMessageLikeEvent {
675                content: RoomEncryptedEventContent::new(
676                    EncryptedEventScheme::OlmV1Curve25519AesSha2(
677                        OlmV1Curve25519AesSha2Content::new(BTreeMap::new(), "".to_owned()),
678                    ),
679                    None,
680                ),
681                event_id: owned_event_id!("$1"),
682                sender: owned_user_id!("@a:b.c"),
683                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
684                unsigned: MessageLikeUnsigned::new(),
685            }),
686        ));
687
688        assert_matches!(
689            is_suitable_for_latest_event(&event, None),
690            PossibleLatestEvent::NoEncrypted
691        );
692    }
693
694    #[cfg(feature = "e2e-encryption")]
695    #[test]
696    fn test_state_events_are_unsuitable() {
697        let event = AnySyncTimelineEvent::State(AnySyncStateEvent::RoomTopic(
698            SyncRoomTopicEvent::Original(OriginalSyncStateEvent {
699                content: RoomTopicEventContent::new("".to_owned()),
700                event_id: owned_event_id!("$1"),
701                sender: owned_user_id!("@a:b.c"),
702                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
703                unsigned: StateUnsigned::new(),
704                state_key: EmptyStateKey,
705            }),
706        ));
707
708        assert_matches!(
709            is_suitable_for_latest_event(&event, None),
710            PossibleLatestEvent::NoUnsupportedEventType
711        );
712    }
713
714    #[cfg(feature = "e2e-encryption")]
715    #[test]
716    fn test_replacement_events_are_unsuitable() {
717        let mut event_content = RoomMessageEventContent::text_plain("Bye bye, world!");
718        event_content.relates_to = Some(Relation::Replacement(Replacement::new(
719            owned_event_id!("$1"),
720            RoomMessageEventContent::text_plain("Hello, world!").into(),
721        )));
722
723        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
724            SyncRoomMessageEvent::Original(OriginalSyncMessageLikeEvent {
725                content: event_content,
726                event_id: owned_event_id!("$2"),
727                sender: owned_user_id!("@a:b.c"),
728                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(2123).unwrap()),
729                unsigned: MessageLikeUnsigned::new(),
730            }),
731        ));
732
733        assert_matches!(
734            is_suitable_for_latest_event(&event, None),
735            PossibleLatestEvent::NoUnsupportedMessageLikeType
736        );
737    }
738
739    #[cfg(feature = "e2e-encryption")]
740    #[test]
741    fn test_verification_requests_are_unsuitable() {
742        use ruma::{device_id, events::room::message::KeyVerificationRequestEventContent, user_id};
743
744        let event = AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
745            SyncRoomMessageEvent::Original(OriginalSyncMessageLikeEvent {
746                content: RoomMessageEventContent::new(MessageType::VerificationRequest(
747                    KeyVerificationRequestEventContent::new(
748                        "body".to_owned(),
749                        vec![],
750                        device_id!("device_id").to_owned(),
751                        user_id!("@user_id:example.com").to_owned(),
752                    ),
753                )),
754                event_id: owned_event_id!("$1"),
755                sender: owned_user_id!("@a:b.c"),
756                origin_server_ts: MilliSecondsSinceUnixEpoch(UInt::new(123).unwrap()),
757                unsigned: MessageLikeUnsigned::new(),
758            }),
759        ));
760
761        assert_let!(
762            PossibleLatestEvent::NoUnsupportedMessageLikeType =
763                is_suitable_for_latest_event(&event, None)
764        );
765    }
766
767    #[test]
768    fn test_deserialize_latest_event() {
769        #[derive(Debug, serde::Serialize, serde::Deserialize)]
770        struct TestStruct {
771            latest_event: LatestEvent,
772        }
773
774        let event = TimelineEvent::from_plaintext(
775            Raw::from_json_string(json!({ "event_id": "$1" }).to_string()).unwrap(),
776        );
777
778        let initial = TestStruct {
779            latest_event: LatestEvent {
780                event: event.clone(),
781                sender_profile: None,
782                sender_name_is_ambiguous: None,
783            },
784        };
785
786        // When serialized, LatestEvent always uses the new format.
787        let serialized = serde_json::to_value(&initial).unwrap();
788        assert_eq!(
789            serialized,
790            json!({
791                "latest_event": {
792                    "event": {
793                        "kind": {
794                            "PlainText": {
795                                "event": {
796                                    "event_id": "$1"
797                                }
798                            }
799                        },
800                        "thread_summary": "None",
801                        "timestamp": null,
802                    }
803                }
804            })
805        );
806
807        // And it can be properly deserialized from the new format.
808        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
809        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
810        assert!(deserialized.latest_event.sender_profile.is_none());
811        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
812
813        // The previous format can also be deserialized.
814        let serialized = json!({
815                "latest_event": {
816                    "event": {
817                        "encryption_info": null,
818                        "event": {
819                            "event_id": "$1"
820                        }
821                    },
822                }
823        });
824
825        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
826        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
827        assert!(deserialized.latest_event.sender_profile.is_none());
828        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
829
830        // The even older format can also be deserialized.
831        let serialized = json!({
832            "latest_event": event
833        });
834
835        let deserialized: TestStruct = serde_json::from_value(serialized).unwrap();
836        assert_eq!(deserialized.latest_event.event().event_id().unwrap(), "$1");
837        assert!(deserialized.latest_event.sender_profile.is_none());
838        assert!(deserialized.latest_event.sender_name_is_ambiguous.is_none());
839    }
840}