matrix_sdk_base/
sync.rs

1// Copyright 2022 The Matrix.org Foundation C.I.C.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! The SDK's representation of the result of a `/sync` request.
16
17use std::{collections::BTreeMap, fmt};
18
19use matrix_sdk_common::{debug::DebugRawEvent, deserialized_responses::TimelineEvent};
20pub use ruma::api::client::sync::sync_events::v3::{
21    InvitedRoom as InvitedRoomUpdate, KnockedRoom as KnockedRoomUpdate,
22};
23use ruma::{
24    api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
25    events::{
26        presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
27        AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnyToDeviceEvent,
28    },
29    push::Action,
30    serde::Raw,
31    OwnedEventId, OwnedRoomId,
32};
33use serde::{Deserialize, Serialize};
34
35use crate::{
36    debug::{DebugInvitedRoom, DebugKnockedRoom, DebugListOfRawEvents, DebugListOfRawEventsNoId},
37    deserialized_responses::{AmbiguityChange, RawAnySyncOrStrippedTimelineEvent},
38};
39
40/// Generalized representation of a `/sync` response.
41///
42/// This type is intended to be applicable regardless of the endpoint used for
43/// syncing.
44#[derive(Clone, Default)]
45pub struct SyncResponse {
46    /// Updates to rooms.
47    pub rooms: RoomUpdates,
48    /// Updates to the presence status of other users.
49    pub presence: Vec<Raw<PresenceEvent>>,
50    /// The global private data created by this user.
51    pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
52    /// Messages sent directly between devices.
53    pub to_device: Vec<Raw<AnyToDeviceEvent>>,
54    /// New notifications per room.
55    pub notifications: BTreeMap<OwnedRoomId, Vec<Notification>>,
56}
57
58#[cfg(not(tarpaulin_include))]
59impl fmt::Debug for SyncResponse {
60    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61        f.debug_struct("SyncResponse")
62            .field("rooms", &self.rooms)
63            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
64            .field("to_device", &DebugListOfRawEventsNoId(&self.to_device))
65            .field("notifications", &self.notifications)
66            .finish_non_exhaustive()
67    }
68}
69
70/// Updates to rooms in a [`SyncResponse`].
71#[derive(Clone, Default)]
72pub struct RoomUpdates {
73    /// The rooms that the user has left or been banned from.
74    pub left: BTreeMap<OwnedRoomId, LeftRoomUpdate>,
75    /// The rooms that the user has joined.
76    pub joined: BTreeMap<OwnedRoomId, JoinedRoomUpdate>,
77    /// The rooms that the user has been invited to.
78    pub invited: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
79    /// The rooms that the user has knocked on.
80    pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
81}
82
83impl RoomUpdates {
84    /// Iterate over all room IDs, from [`RoomUpdates::left`],
85    /// [`RoomUpdates::joined`], [`RoomUpdates::invited`] and
86    /// [`RoomUpdates::knocked`].
87    pub(crate) fn iter_all_room_ids(&self) -> impl Iterator<Item = &OwnedRoomId> {
88        self.left
89            .keys()
90            .chain(self.joined.keys())
91            .chain(self.invited.keys())
92            .chain(self.knocked.keys())
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use std::collections::BTreeMap;
99
100    use assert_matches::assert_matches;
101    use ruma::room_id;
102
103    use super::{
104        InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, RoomUpdates,
105    };
106
107    #[test]
108    fn test_room_updates_iter_all_room_ids() {
109        let room_id_0 = room_id!("!r0");
110        let room_id_1 = room_id!("!r1");
111        let room_id_2 = room_id!("!r2");
112        let room_id_3 = room_id!("!r3");
113        let room_id_4 = room_id!("!r4");
114        let room_id_5 = room_id!("!r5");
115        let room_id_6 = room_id!("!r6");
116        let room_id_7 = room_id!("!r7");
117        let room_updates = RoomUpdates {
118            left: {
119                let mut left = BTreeMap::new();
120                left.insert(room_id_0.to_owned(), LeftRoomUpdate::default());
121                left.insert(room_id_1.to_owned(), LeftRoomUpdate::default());
122                left
123            },
124            joined: {
125                let mut joined = BTreeMap::new();
126                joined.insert(room_id_2.to_owned(), JoinedRoomUpdate::default());
127                joined.insert(room_id_3.to_owned(), JoinedRoomUpdate::default());
128                joined
129            },
130            invited: {
131                let mut invited = BTreeMap::new();
132                invited.insert(room_id_4.to_owned(), InvitedRoomUpdate::default());
133                invited.insert(room_id_5.to_owned(), InvitedRoomUpdate::default());
134                invited
135            },
136            knocked: {
137                let mut knocked = BTreeMap::new();
138                knocked.insert(room_id_6.to_owned(), KnockedRoomUpdate::default());
139                knocked.insert(room_id_7.to_owned(), KnockedRoomUpdate::default());
140                knocked
141            },
142        };
143
144        let mut iter = room_updates.iter_all_room_ids();
145        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_0));
146        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_1));
147        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_2));
148        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_3));
149        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_4));
150        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_5));
151        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_6));
152        assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_7));
153        assert!(iter.next().is_none());
154    }
155}
156
157#[cfg(not(tarpaulin_include))]
158impl fmt::Debug for RoomUpdates {
159    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
160        f.debug_struct("RoomUpdates")
161            .field("left", &self.left)
162            .field("joined", &self.joined)
163            .field("invited", &DebugInvitedRoomUpdates(&self.invited))
164            .field("knocked", &DebugKnockedRoomUpdates(&self.knocked))
165            .finish()
166    }
167}
168
169/// Updates to joined rooms.
170#[derive(Clone, Default)]
171pub struct JoinedRoomUpdate {
172    /// Counts of unread notifications for this room.
173    pub unread_notifications: UnreadNotificationsCount,
174    /// The timeline of messages and state changes in the room.
175    pub timeline: Timeline,
176    /// Updates to the state, between the time indicated by the `since`
177    /// parameter, and the start of the `timeline` (or all state up to the
178    /// start of the `timeline`, if `since` is not given, or `full_state` is
179    /// true).
180    pub state: Vec<Raw<AnySyncStateEvent>>,
181    /// The private data that this user has attached to this room.
182    pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
183    /// The ephemeral events in the room that aren't recorded in the timeline or
184    /// state of the room. e.g. typing.
185    pub ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
186    /// Collection of ambiguity changes that room member events trigger.
187    ///
188    /// This is a map of event ID of the `m.room.member` event to the
189    /// details of the ambiguity change.
190    pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
191}
192
193#[cfg(not(tarpaulin_include))]
194impl fmt::Debug for JoinedRoomUpdate {
195    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
196        f.debug_struct("JoinedRoomUpdate")
197            .field("unread_notifications", &self.unread_notifications)
198            .field("timeline", &self.timeline)
199            .field("state", &DebugListOfRawEvents(&self.state))
200            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
201            .field("ephemeral", &self.ephemeral)
202            .field("ambiguity_changes", &self.ambiguity_changes)
203            .finish()
204    }
205}
206
207impl JoinedRoomUpdate {
208    pub(crate) fn new(
209        timeline: Timeline,
210        state: Vec<Raw<AnySyncStateEvent>>,
211        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
212        ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
213        unread_notifications: UnreadNotificationsCount,
214        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
215    ) -> Self {
216        Self { unread_notifications, timeline, state, account_data, ephemeral, ambiguity_changes }
217    }
218}
219
220/// Counts of unread notifications for a room.
221#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
222pub struct UnreadNotificationsCount {
223    /// The number of unread notifications for this room with the highlight flag
224    /// set.
225    pub highlight_count: u64,
226    /// The total number of unread notifications for this room.
227    pub notification_count: u64,
228}
229
230impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
231    fn from(notifications: RumaUnreadNotificationsCount) -> Self {
232        Self {
233            highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0),
234            notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0),
235        }
236    }
237}
238
239/// Updates to left rooms.
240#[derive(Clone, Default)]
241pub struct LeftRoomUpdate {
242    /// The timeline of messages and state changes in the room up to the point
243    /// when the user left.
244    pub timeline: Timeline,
245    /// Updates to the state, between the time indicated by the `since`
246    /// parameter, and the start of the `timeline` (or all state up to the
247    /// start of the `timeline`, if `since` is not given, or `full_state` is
248    /// true).
249    pub state: Vec<Raw<AnySyncStateEvent>>,
250    /// The private data that this user has attached to this room.
251    pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
252    /// Collection of ambiguity changes that room member events trigger.
253    ///
254    /// This is a map of event ID of the `m.room.member` event to the
255    /// details of the ambiguity change.
256    pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
257}
258
259impl LeftRoomUpdate {
260    pub(crate) fn new(
261        timeline: Timeline,
262        state: Vec<Raw<AnySyncStateEvent>>,
263        account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
264        ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
265    ) -> Self {
266        Self { timeline, state, account_data, ambiguity_changes }
267    }
268}
269
270#[cfg(not(tarpaulin_include))]
271impl fmt::Debug for LeftRoomUpdate {
272    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
273        f.debug_struct("LeftRoomUpdate")
274            .field("timeline", &self.timeline)
275            .field("state", &DebugListOfRawEvents(&self.state))
276            .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
277            .field("ambiguity_changes", &self.ambiguity_changes)
278            .finish()
279    }
280}
281
282/// Events in the room.
283#[derive(Clone, Debug, Default)]
284pub struct Timeline {
285    /// True if the number of events returned was limited by the `limit` on the
286    /// filter.
287    pub limited: bool,
288
289    /// A token that can be supplied to to the `from` parameter of the
290    /// `/rooms/{roomId}/messages` endpoint.
291    pub prev_batch: Option<String>,
292
293    /// A list of events.
294    pub events: Vec<TimelineEvent>,
295}
296
297impl Timeline {
298    pub(crate) fn new(limited: bool, prev_batch: Option<String>) -> Self {
299        Self { limited, prev_batch, ..Default::default() }
300    }
301}
302
303struct DebugInvitedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, InvitedRoomUpdate>);
304
305#[cfg(not(tarpaulin_include))]
306impl fmt::Debug for DebugInvitedRoomUpdates<'_> {
307    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308        f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugInvitedRoom(v)))).finish()
309    }
310}
311
312struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
313
314#[cfg(not(tarpaulin_include))]
315impl fmt::Debug for DebugKnockedRoomUpdates<'_> {
316    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
317        f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugKnockedRoom(v)))).finish()
318    }
319}
320
321/// A notification triggered by a sync response.
322#[derive(Clone)]
323pub struct Notification {
324    /// The actions to perform when the conditions for this rule are met.
325    pub actions: Vec<Action>,
326
327    /// The event that triggered the notification.
328    pub event: RawAnySyncOrStrippedTimelineEvent,
329}
330
331#[cfg(not(tarpaulin_include))]
332impl fmt::Debug for Notification {
333    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334        let event_debug = match &self.event {
335            RawAnySyncOrStrippedTimelineEvent::Sync(ev) => DebugRawEvent(ev),
336            RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => DebugRawEvent(ev.cast_ref()),
337        };
338
339        f.debug_struct("Notification")
340            .field("actions", &self.actions)
341            .field("event", &event_debug)
342            .finish()
343    }
344}