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