ruma_client_api/sync/
sync_events.rs

1//! `GET /_matrix/client/*/sync`
2//!
3//! Get all new events from all rooms since the last sync or a given point in time.
4
5use js_int::UInt;
6use ruma_common::{
7    serde::{from_raw_json_value, JsonCastable, JsonObject},
8    EventId, MilliSecondsSinceUnixEpoch, OwnedUserId, UserId,
9};
10use ruma_events::{
11    AnyStateEvent, AnyStrippedStateEvent, AnySyncStateEvent, OriginalStateEvent,
12    OriginalSyncStateEvent, PossiblyRedactedStateEventContent, RedactContent, RedactedStateEvent,
13    RedactedStateEventContent, RedactedSyncStateEvent, StateEvent, StateEventType,
14    StaticStateEventContent, StrippedStateEvent, SyncStateEvent,
15};
16use serde::{Deserialize, Serialize};
17use serde_json::value::RawValue as RawJsonValue;
18
19pub mod v3;
20
21#[cfg(feature = "unstable-msc4186")]
22pub mod v5;
23
24/// Unread notifications count.
25#[derive(Clone, Debug, Default, Deserialize, Serialize)]
26#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
27pub struct UnreadNotificationsCount {
28    /// The number of unread notifications with the highlight flag set.
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub highlight_count: Option<UInt>,
31
32    /// The total number of unread notifications.
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub notification_count: Option<UInt>,
35}
36
37impl UnreadNotificationsCount {
38    /// Creates an empty `UnreadNotificationsCount`.
39    pub fn new() -> Self {
40        Default::default()
41    }
42
43    /// Returns true if there are no notification count updates.
44    pub fn is_empty(&self) -> bool {
45        self.highlight_count.is_none() && self.notification_count.is_none()
46    }
47}
48
49/// Information on E2E device updates.
50#[derive(Clone, Debug, Default, Deserialize, Serialize)]
51#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
52pub struct DeviceLists {
53    /// List of users who have updated their device identity keys or who now
54    /// share an encrypted room with the client since the previous sync.
55    #[serde(default, skip_serializing_if = "Vec::is_empty")]
56    pub changed: Vec<OwnedUserId>,
57
58    /// List of users who no longer share encrypted rooms since the previous sync
59    /// response.
60    #[serde(default, skip_serializing_if = "Vec::is_empty")]
61    pub left: Vec<OwnedUserId>,
62}
63
64impl DeviceLists {
65    /// Creates an empty `DeviceLists`.
66    pub fn new() -> Self {
67        Default::default()
68    }
69
70    /// Returns true if there are no device list updates.
71    pub fn is_empty(&self) -> bool {
72        self.changed.is_empty() && self.left.is_empty()
73    }
74}
75
76/// Possible event formats that may appear in stripped state.
77#[derive(Debug, Clone)]
78#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
79#[allow(clippy::large_enum_variant)]
80pub enum StrippedState {
81    /// A stripped state event.
82    Stripped(AnyStrippedStateEvent),
83
84    /// An event using the sync format.
85    #[cfg(feature = "unstable-msc4319")]
86    Sync(AnySyncStateEvent),
87
88    /// A full state event.
89    #[cfg(feature = "unstable-msc4311")]
90    Full(AnyStateEvent),
91}
92
93impl StrippedState {
94    /// Returns the `type` of this event.
95    pub fn event_type(&self) -> StateEventType {
96        match self {
97            Self::Stripped(event) => event.event_type(),
98            #[cfg(feature = "unstable-msc4319")]
99            Self::Sync(event) => event.event_type(),
100            #[cfg(feature = "unstable-msc4311")]
101            Self::Full(event) => event.event_type(),
102        }
103    }
104
105    /// Returns this event's `sender` field.
106    pub fn sender(&self) -> &UserId {
107        match self {
108            Self::Stripped(event) => event.sender(),
109            #[cfg(feature = "unstable-msc4319")]
110            Self::Sync(event) => event.sender(),
111            #[cfg(feature = "unstable-msc4311")]
112            Self::Full(event) => event.sender(),
113        }
114    }
115
116    /// Returns this event's `state_key` field.
117    pub fn state_key(&self) -> &str {
118        match self {
119            Self::Stripped(event) => event.state_key(),
120            #[cfg(feature = "unstable-msc4319")]
121            Self::Sync(event) => event.state_key(),
122            #[cfg(feature = "unstable-msc4311")]
123            Self::Full(event) => event.state_key(),
124        }
125    }
126
127    /// Returns this event's `event_id` field, if there is one.
128    pub fn event_id(&self) -> Option<&EventId> {
129        match self {
130            Self::Stripped(_) => None,
131            #[cfg(feature = "unstable-msc4319")]
132            Self::Sync(event) => Some(event.event_id()),
133            #[cfg(feature = "unstable-msc4311")]
134            Self::Full(event) => Some(event.event_id()),
135        }
136    }
137
138    /// Returns this event's `origin_server_ts` field, if there is one.
139    pub fn origin_server_ts(&self) -> Option<MilliSecondsSinceUnixEpoch> {
140        match self {
141            Self::Stripped(_) => None,
142            #[cfg(feature = "unstable-msc4319")]
143            Self::Sync(event) => Some(event.origin_server_ts()),
144            #[cfg(feature = "unstable-msc4311")]
145            Self::Full(event) => Some(event.origin_server_ts()),
146        }
147    }
148}
149
150impl<'de> Deserialize<'de> for StrippedState {
151    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152    where
153        D: serde::Deserializer<'de>,
154    {
155        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
156
157        #[cfg(any(feature = "unstable-msc4311", feature = "unstable-msc4319"))]
158        {
159            use serde::de;
160
161            #[derive(Deserialize)]
162            struct StrippedStateDeHelper {
163                event_id: Option<de::IgnoredAny>,
164                origin_server_ts: Option<de::IgnoredAny>,
165                #[cfg(feature = "unstable-msc4311")]
166                room_id: Option<de::IgnoredAny>,
167            }
168
169            let StrippedStateDeHelper {
170                event_id,
171                origin_server_ts,
172                #[cfg(feature = "unstable-msc4311")]
173                room_id,
174            } = from_raw_json_value(&json)?;
175
176            if event_id.is_some() && origin_server_ts.is_some() {
177                #[cfg(feature = "unstable-msc4311")]
178                if room_id.is_some() {
179                    return from_raw_json_value(&json).map(Self::Full);
180                }
181
182                #[cfg(feature = "unstable-msc4319")]
183                return from_raw_json_value(&json).map(Self::Sync);
184            }
185        }
186
187        from_raw_json_value(&json).map(Self::Stripped)
188    }
189}
190
191impl JsonCastable<StrippedState> for AnySyncStateEvent {}
192
193impl JsonCastable<StrippedState> for AnyStrippedStateEvent {}
194
195impl JsonCastable<AnyStrippedStateEvent> for StrippedState {}
196
197impl JsonCastable<StrippedState> for AnyStateEvent {}
198
199impl<C> JsonCastable<StrippedState> for OriginalStateEvent<C> where C: StaticStateEventContent {}
200
201impl<C> JsonCastable<StrippedState> for OriginalSyncStateEvent<C> where C: StaticStateEventContent {}
202
203impl<C> JsonCastable<StrippedState> for RedactedStateEvent<C> where C: RedactedStateEventContent {}
204
205impl<C> JsonCastable<StrippedState> for RedactedSyncStateEvent<C> where C: RedactedStateEventContent {}
206
207impl<C> JsonCastable<StrippedState> for StateEvent<C>
208where
209    C: StaticStateEventContent + RedactContent,
210    <C as RedactContent>::Redacted: RedactedStateEventContent,
211{
212}
213
214impl<C> JsonCastable<StrippedState> for SyncStateEvent<C>
215where
216    C: StaticStateEventContent + RedactContent,
217    <C as RedactContent>::Redacted: RedactedStateEventContent,
218{
219}
220
221impl<C> JsonCastable<StrippedState> for StrippedStateEvent<C> where
222    C: PossiblyRedactedStateEventContent
223{
224}
225
226impl JsonCastable<JsonObject> for StrippedState {}
227
228#[cfg(test)]
229mod tests {
230    use assert_matches2::assert_matches;
231    use ruma_common::user_id;
232    use ruma_events::{room::member::MembershipState, AnyStrippedStateEvent};
233    use serde_json::{from_value as from_json_value, json};
234
235    use crate::sync::sync_events::StrippedState;
236
237    #[test]
238    fn deserialize_stripped_state() {
239        let user_id = user_id!("@patrick:localhost");
240        let content = json!({
241            "membership": "join",
242        });
243
244        // Stripped format.
245        let stripped_event_json = json!({
246            "content": content,
247            "type": "m.room.member",
248            "state_key": user_id,
249            "sender": user_id,
250        });
251        assert_matches!(
252            from_json_value::<StrippedState>(stripped_event_json).unwrap(),
253            StrippedState::Stripped(AnyStrippedStateEvent::RoomMember(stripped_member_event))
254        );
255        assert_eq!(stripped_member_event.sender, user_id);
256        assert_eq!(stripped_member_event.state_key, user_id);
257        assert_eq!(stripped_member_event.content.membership, MembershipState::Join);
258
259        #[cfg(feature = "unstable-msc4319")]
260        {
261            use js_int::uint;
262            use ruma_common::event_id;
263            use ruma_events::{AnySyncStateEvent, SyncStateEvent};
264
265            let event_id = event_id!("$abcdefgh");
266
267            // Sync format.
268            let sync_event_json = json!({
269                "content": content,
270                "event_id": event_id,
271                "origin_server_ts": 1_000_000,
272                "sender": user_id,
273                "state_key": user_id,
274                "type": "m.room.member",
275            });
276            assert_matches!(
277                from_json_value::<StrippedState>(sync_event_json).unwrap(),
278                StrippedState::Sync(AnySyncStateEvent::RoomMember(SyncStateEvent::Original(
279                    sync_member_event
280                )))
281            );
282            assert_eq!(sync_member_event.content.membership, MembershipState::Join);
283            assert_eq!(sync_member_event.event_id, event_id);
284            assert_eq!(sync_member_event.origin_server_ts.0, uint!(1_000_000));
285            assert_eq!(sync_member_event.sender, user_id);
286            assert_eq!(sync_member_event.state_key, user_id);
287        }
288
289        #[cfg(feature = "unstable-msc4311")]
290        {
291            use js_int::uint;
292            use ruma_common::{event_id, room_id};
293            use ruma_events::{AnyStateEvent, StateEvent};
294
295            let event_id = event_id!("$abcdefgh");
296            let room_id = room_id!("!room:localhost");
297
298            // Timeline format.
299            let timeline_event_json = json!({
300                "content": content,
301                "event_id": event_id,
302                "origin_server_ts": 1_000_000,
303                "room_id": room_id,
304                "sender": user_id,
305                "state_key": user_id,
306                "type": "m.room.member",
307            });
308            assert_matches!(
309                from_json_value::<StrippedState>(timeline_event_json).unwrap(),
310                StrippedState::Full(AnyStateEvent::RoomMember(StateEvent::Original(
311                    timeline_member_event
312                )))
313            );
314            assert_eq!(timeline_member_event.content.membership, MembershipState::Join);
315            assert_eq!(timeline_member_event.event_id, event_id);
316            assert_eq!(timeline_member_event.origin_server_ts.0, uint!(1_000_000));
317            assert_eq!(timeline_member_event.room_id, room_id);
318            assert_eq!(timeline_member_event.sender, user_id);
319            assert_eq!(timeline_member_event.state_key, user_id);
320        }
321    }
322}