1use 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#[derive(Clone, Default)]
45pub struct SyncResponse {
46 pub rooms: RoomUpdates,
48 pub presence: Vec<Raw<PresenceEvent>>,
50 pub account_data: Vec<Raw<AnyGlobalAccountDataEvent>>,
52 pub to_device: Vec<Raw<AnyToDeviceEvent>>,
54 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#[derive(Clone, Default)]
72pub struct RoomUpdates {
73 pub left: BTreeMap<OwnedRoomId, LeftRoomUpdate>,
75 pub joined: BTreeMap<OwnedRoomId, JoinedRoomUpdate>,
77 pub invited: BTreeMap<OwnedRoomId, InvitedRoomUpdate>,
79 pub knocked: BTreeMap<OwnedRoomId, KnockedRoomUpdate>,
81}
82
83impl RoomUpdates {
84 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#[derive(Clone, Default)]
171pub struct JoinedRoomUpdate {
172 pub unread_notifications: UnreadNotificationsCount,
174 pub timeline: Timeline,
176 pub state: Vec<Raw<AnySyncStateEvent>>,
181 pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
183 pub ephemeral: Vec<Raw<AnySyncEphemeralRoomEvent>>,
186 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#[derive(Copy, Clone, Debug, Default, Deserialize, Serialize, PartialEq)]
222pub struct UnreadNotificationsCount {
223 pub highlight_count: u64,
226 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#[derive(Clone, Default)]
241pub struct LeftRoomUpdate {
242 pub timeline: Timeline,
245 pub state: Vec<Raw<AnySyncStateEvent>>,
250 pub account_data: Vec<Raw<AnyRoomAccountDataEvent>>,
252 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#[derive(Clone, Debug, Default)]
284pub struct Timeline {
285 pub limited: bool,
288
289 pub prev_batch: Option<String>,
292
293 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#[derive(Clone)]
323pub struct Notification {
324 pub actions: Vec<Action>,
326
327 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}