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, OwnedMxcUri, OwnedRoomId, OwnedUserId,
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 pub avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
224}
225
226#[cfg(not(tarpaulin_include))]
227impl fmt::Debug for JoinedRoomUpdate {
228 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
229 f.debug_struct("JoinedRoomUpdate")
230 .field("unread_notifications", &self.unread_notifications)
231 .field("timeline", &self.timeline)
232 .field("state", &self.state)
233 .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
234 .field("ephemeral", &self.ephemeral)
235 .field("ambiguity_changes", &self.ambiguity_changes)
236 .finish()
237 }
238}
239
240impl JoinedRoomUpdate {
241 pub(crate) fn new(
242 timeline: Timeline,
243 state: State,
244 account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
245 ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
246 unread_notifications: UnreadNotificationsCount,
247 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
248 avatar_changes: Option<BTreeMap<OwnedUserId, Option<OwnedMxcUri>>>,
249 ) -> Self {
250 Self {
251 unread_notifications,
252 timeline,
253 state,
254 account_data,
255 ephemeral,
256 ambiguity_changes,
257 avatar_changes,
258 }
259 }
260}
261
262#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
264pub struct UnreadNotificationsCount {
265 pub highlight_count: u64,
268 pub notification_count: u64,
270}
271
272impl From<RumaUnreadNotificationsCount> for UnreadNotificationsCount {
273 fn from(notifications: RumaUnreadNotificationsCount) -> Self {
274 Self {
275 highlight_count: notifications.highlight_count.map(|c| c.into()).unwrap_or(0),
276 notification_count: notifications.notification_count.map(|c| c.into()).unwrap_or(0),
277 }
278 }
279}
280
281#[derive(Clone, Default)]
283pub struct LeftRoomUpdate {
284 pub timeline: Timeline,
287 pub state: State,
298 pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
300 pub ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
305}
306
307impl LeftRoomUpdate {
308 pub(crate) fn new(
309 timeline: Timeline,
310 state: State,
311 account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
312 ambiguity_changes: BTreeMap<OwnedEventId, AmbiguityChange>,
313 ) -> Self {
314 Self { timeline, state, account_data, ambiguity_changes }
315 }
316}
317
318#[cfg(not(tarpaulin_include))]
319impl fmt::Debug for LeftRoomUpdate {
320 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
321 f.debug_struct("LeftRoomUpdate")
322 .field("timeline", &self.timeline)
323 .field("state", &self.state)
324 .field("account_data", &DebugListOfRawEventsNoId(&self.account_data))
325 .field("ambiguity_changes", &self.ambiguity_changes)
326 .finish()
327 }
328}
329
330#[derive(Clone, Debug, Default)]
332pub struct Timeline {
333 pub limited: bool,
336
337 pub prev_batch: Option<String>,
340
341 pub events: Vec<TimelineEvent>,
343}
344
345impl Timeline {
346 pub(crate) fn new(limited: bool, prev_batch: Option<String>) -> Self {
347 Self { limited, prev_batch, ..Default::default() }
348 }
349}
350
351#[derive(Clone)]
353pub enum State {
354 Before(Vec<Raw<AnySyncStateEvent>>),
361
362 After(Vec<Raw<AnySyncStateEvent>>),
367}
368
369impl Default for State {
370 fn default() -> Self {
371 Self::Before(vec![])
372 }
373}
374
375#[cfg(not(tarpaulin_include))]
376impl fmt::Debug for State {
377 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
378 match self {
379 Self::Before(events) => {
380 f.debug_tuple("Before").field(&DebugListOfRawEvents(events)).finish()
381 }
382 Self::After(events) => {
383 f.debug_tuple("After").field(&DebugListOfRawEvents(events)).finish()
384 }
385 }
386 }
387}
388
389struct DebugInvitedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, InvitedRoomUpdate>);
390
391#[cfg(not(tarpaulin_include))]
392impl fmt::Debug for DebugInvitedRoomUpdates<'_> {
393 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
394 f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugInvitedRoom(v)))).finish()
395 }
396}
397
398struct DebugKnockedRoomUpdates<'a>(&'a BTreeMap<OwnedRoomId, KnockedRoomUpdate>);
399
400#[cfg(not(tarpaulin_include))]
401impl fmt::Debug for DebugKnockedRoomUpdates<'_> {
402 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
403 f.debug_map().entries(self.0.iter().map(|(k, v)| (k, DebugKnockedRoom(v)))).finish()
404 }
405}
406
407#[derive(Clone)]
409pub struct Notification {
410 pub actions: Vec<Action>,
412
413 pub event: RawAnySyncOrStrippedTimelineEvent,
415}
416
417#[cfg(not(tarpaulin_include))]
418impl fmt::Debug for Notification {
419 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
420 let event_debug = match &self.event {
421 RawAnySyncOrStrippedTimelineEvent::Sync(ev) => DebugRawEvent(ev),
422 RawAnySyncOrStrippedTimelineEvent::Stripped(ev) => {
423 DebugRawEvent(ev.cast_ref_unchecked())
424 }
425 };
426
427 f.debug_struct("Notification")
428 .field("actions", &self.actions)
429 .field("event", &event_debug)
430 .finish()
431 }
432}