1use 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 OwnedEventId, OwnedRoomId,
28 api::client::sync::sync_events::UnreadNotificationsCount as RumaUnreadNotificationsCount,
29 events::{
30 AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncEphemeralRoomEvent,
31 AnySyncStateEvent, presence::PresenceEvent,
32 },
33 push::Action,
34 serde::Raw,
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#[derive(Clone, Default)]
51pub struct SyncResponse {
52 pub rooms: RoomUpdates,
54 pub presence: Vec<Raw<PresenceEvent>>,
56 pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
58 pub to_device: Vec<ProcessedToDeviceEvent>,
60 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#[derive(Clone, Default)]
78pub struct RoomUpdates {
79 pub left: BTreeMap<OwnedRoomId, LeftRoomUpdate>,
81 pub joined: BTreeMap<OwnedRoomId, JoinedRoomUpdate>,
83 pub invited: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
85 pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
87}
88
89impl RoomUpdates {
90 pub 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 pub fn is_empty(&self) -> bool {
104 self.invited.is_empty()
105 && self.joined.is_empty()
106 && self.knocked.is_empty()
107 && self.left.is_empty()
108 }
109}
110
111#[cfg(test)]
112mod tests {
113 use std::collections::BTreeMap;
114
115 use assert_matches::assert_matches;
116 use ruma::room_id;
117
118 use super::{
119 InvitedRoomUpdate, JoinedRoomUpdate, KnockedRoomUpdate, LeftRoomUpdate, RoomUpdates,
120 };
121
122 #[test]
123 fn test_room_updates_iter_all_room_ids() {
124 let room_id_0 = room_id!("!r0");
125 let room_id_1 = room_id!("!r1");
126 let room_id_2 = room_id!("!r2");
127 let room_id_3 = room_id!("!r3");
128 let room_id_4 = room_id!("!r4");
129 let room_id_5 = room_id!("!r5");
130 let room_id_6 = room_id!("!r6");
131 let room_id_7 = room_id!("!r7");
132 let room_updates = RoomUpdates {
133 left: {
134 let mut left = BTreeMap::new();
135 left.insert(room_id_0.to_owned(), LeftRoomUpdate::default());
136 left.insert(room_id_1.to_owned(), LeftRoomUpdate::default());
137 left
138 },
139 joined: {
140 let mut joined = BTreeMap::new();
141 joined.insert(room_id_2.to_owned(), JoinedRoomUpdate::default());
142 joined.insert(room_id_3.to_owned(), JoinedRoomUpdate::default());
143 joined
144 },
145 invited: {
146 let mut invited = BTreeMap::new();
147 invited.insert(room_id_4.to_owned(), InvitedRoomUpdate::default());
148 invited.insert(room_id_5.to_owned(), InvitedRoomUpdate::default());
149 invited
150 },
151 knocked: {
152 let mut knocked = BTreeMap::new();
153 knocked.insert(room_id_6.to_owned(), KnockedRoomUpdate::default());
154 knocked.insert(room_id_7.to_owned(), KnockedRoomUpdate::default());
155 knocked
156 },
157 };
158
159 let mut iter = room_updates.iter_all_room_ids();
160 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_0));
161 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_1));
162 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_2));
163 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_3));
164 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_4));
165 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_5));
166 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_6));
167 assert_matches!(iter.next(), Some(room_id) => assert_eq!(room_id, room_id_7));
168 assert!(iter.next().is_none());
169 }
170
171 #[test]
172 fn test_empty_room_updates() {
173 let room_updates = RoomUpdates::default();
174
175 let mut iter = room_updates.iter_all_room_ids();
176 assert!(iter.next().is_none());
177
178 assert!(room_updates.is_empty());
179 }
180}
181
182#[cfg(not(tarpaulin_include))]
183impl fmt::Debug for RoomUpdates {
184 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
185 f.debug_struct("RoomUpdates")
186 .field("left", &self.left)
187 .field("joined", &self.joined)
188 .field("invited", &DebugInvitedRoomUpdates(&self.invited))
189 .field("knocked", &DebugKnockedRoomUpdates(&self.knocked))
190 .finish()
191 }
192}
193
194#[derive(Clone, Default)]
196pub struct JoinedRoomUpdate {
197 pub unread_notifications: UnreadNotificationsCount,
199 pub timeline: Timeline,
201 pub state: State,
212 pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
214 pub ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
217 pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
222}
223
224#[cfg(not(tarpaulin_include))]
225impl fmt::Debug for JoinedRoomUpdate {
226 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
227 f.debug_struct("JoinedRoomUpdate")
228 .field("unread_notifications", &self.unread_notifications)
229 .field("timeline", &self.timeline)
230 .field("state", &self.state)
231 .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
232 .field("ephemeral", &self.ephemeral)
233 .field("ambiguity_changes", &self.ambiguity_changes)
234 .finish()
235 }
236}
237
238impl JoinedRoomUpdate {
239 pub(crate) fn new(
240 timeline: Timeline,
241 state: State,
242 account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
243 ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
244 unread_notifications: UnreadNotificationsCount,
245 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
246 ) -> Self {
247 Self { unread_notifications, timeline, state, account_data, ephemeral, ambiguity_changes }
248 }
249}
250
251#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
253pub struct UnreadNotificationsCount {
254 pub highlight_count: u64,
257 pub notification_count: u64,
259}
260
261impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
262 fn from(notifications: RumaUnreadNotificationsCount) -> Self {
263 Self {
264 highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0),
265 notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0),
266 }
267 }
268}
269
270#[derive(Clone, Default)]
272pub struct LeftRoomUpdate {
273 pub timeline: Timeline,
276 pub state: State,
287 pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
289 pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
294}
295
296impl LeftRoomUpdate {
297 pub(crate) fn new(
298 timeline: Timeline,
299 state: State,
300 account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
301 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
302 ) -> Self {
303 Self { timeline, state, account_data, ambiguity_changes }
304 }
305}
306
307#[cfg(not(tarpaulin_include))]
308impl fmt::Debug for LeftRoomUpdate {
309 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
310 f.debug_struct("LeftRoomUpdate")
311 .field("timeline", &self.timeline)
312 .field("state", &self.state)
313 .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
314 .field("ambiguity_changes", &self.ambiguity_changes)
315 .finish()
316 }
317}
318
319#[derive(Clone, Debug, Default)]
321pub struct Timeline {
322 pub limited: bool,
325
326 pub prev_batch: Option<String>,
329
330 pub events: Vec<TimelineEvent>,
332}
333
334impl Timeline {
335 pub(crate) fn new(limited: bool, prev_batch: Option<String>) -> Self {
336 Self { limited, prev_batch, ..Default::default() }
337 }
338}
339
340#[derive(Clone)]
342pub enum State {
343 Before(Vec<Raw<AnySyncStateEvent>>),
350
351 After(Vec<Raw<AnySyncStateEvent>>),
356}
357
358impl Default for State {
359 fn default() -> Self {
360 Self::Before(vec![])
361 }
362}
363
364#[cfg(not(tarpaulin_include))]
365impl fmt::Debug for State {
366 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
367 match self {
368 Self::Before(events) => {
369 f.debug_tuple("Before").field(&DebugListOfRawEvents(events)).finish()
370 }
371 Self::After(events) => {
372 f.debug_tuple("After").field(&DebugListOfRawEvents(events)).finish()
373 }
374 }
375 }
376}
377
378struct DebugInvitedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, InvitedRoomUpdate>);
379
380#[cfg(not(tarpaulin_include))]
381impl fmt::Debug for DebugInvitedRoomUpdates<'_> {
382 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383 f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugInvitedRoom(v)))).finish()
384 }
385}
386
387struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
388
389#[cfg(not(tarpaulin_include))]
390impl fmt::Debug for DebugKnockedRoomUpdates<'_> {
391 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
392 f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugKnockedRoom(v)))).finish()
393 }
394}
395
396#[derive(Clone)]
398pub struct Notification {
399 pub actions: Vec<Action>,
401
402 pub event: RawAnySyncOrStrippedTimelineEvent,
404}
405
406#[cfg(not(tarpaulin_include))]
407impl fmt::Debug for Notification {
408 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
409 let event_debug = match &self.event {
410 RawAnySyncOrStrippedTimelineEvent::Sync(ev) => DebugRawEvent(ev),
411 RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => {
412 DebugRawEvent(ev.cast_ref_unchecked())
413 }
414 };
415
416 f.debug_struct("Notification")
417 .field("actions", &self.actions)
418 .field("event", &event_debug)
419 .finish()
420 }
421}