ruma_client_api/sync/sync_events/
v5.rs

1//! `POST /_matrix/client/unstable/org.matrix.simplified_msc3575/sync` ([MSC4186])
2//!
3//! A simplified version of sliding sync ([MSC3575]).
4//!
5//! Get all new events in a sliding window of rooms since the last sync or a given point in time.
6//!
7//! [MSC3575]: https://github.com/matrix-org/matrix-spec-proposals/pull/3575
8//! [MSC4186]: https://github.com/matrix-org/matrix-spec-proposals/pull/4186
9
10use std::{collections::BTreeMap, time::Duration};
11
12use js_int::UInt;
13use js_option::JsOption;
14use ruma_common::{
15    api::{request, response, Metadata},
16    metadata,
17    serde::{duration::opt_ms, Raw},
18    OwnedMxcUri, OwnedRoomId, OwnedUserId,
19};
20use ruma_events::{AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, StateEventType};
21use serde::{Deserialize, Serialize};
22
23use super::UnreadNotificationsCount;
24
25const METADATA: Metadata = metadata! {
26    method: POST,
27    rate_limited: false,
28    authentication: AccessToken,
29    history: {
30        unstable => "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync",
31        // 1.4 => "/_matrix/client/v5/sync",
32    }
33};
34
35/// Request type for the `/sync` endpoint.
36#[request(error = crate::Error)]
37#[derive(Default)]
38pub struct Request {
39    /// A point in time to continue a sync from.
40    ///
41    /// This is an opaque value taken from the `pos` field of a previous `/sync`
42    /// response. A `None` value asks the server to start a new _session_ (mind
43    /// it can be costly)
44    #[serde(skip_serializing_if = "Option::is_none")]
45    #[ruma_api(query)]
46    pub pos: Option<String>,
47
48    /// A unique string identifier for this connection to the server.
49    ///
50    /// If this is missing, only one sliding sync connection can be made to
51    /// the server at any one time. Clients need to set this to allow more
52    /// than one connection concurrently, so the server can distinguish between
53    /// connections. This must be provided with every request, if your client
54    /// needs more than one concurrent connection.
55    ///
56    /// Limitation: it must not contain more than 16 chars, due to it being
57    /// required with every request.
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub conn_id: Option<String>,
60
61    /// Allows clients to know what request params reached the server,
62    /// functionally similar to txn IDs on `/send` for events.
63    #[serde(skip_serializing_if = "Option::is_none")]
64    pub txn_id: Option<String>,
65
66    /// The maximum time to poll before responding to this request.
67    ///
68    /// `None` means no timeout, so virtually an infinite wait from the server.
69    #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")]
70    #[ruma_api(query)]
71    pub timeout: Option<Duration>,
72
73    /// Lists of rooms we are interested by, represented by ranges.
74    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
75    pub lists: BTreeMap<String, request::List>,
76
77    /// Specific rooms we are interested by.
78    ///
79    /// It is useful to receive updates from rooms that are possibly
80    /// out-of-range of all the lists (see [`Self::lists`]).
81    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
82    pub room_subscriptions: BTreeMap<OwnedRoomId, request::RoomSubscription>,
83
84    /// Extensions.
85    #[serde(default, skip_serializing_if = "request::Extensions::is_empty")]
86    pub extensions: request::Extensions,
87}
88
89impl Request {
90    /// Creates an empty `Request`.
91    pub fn new() -> Self {
92        Default::default()
93    }
94}
95
96/// HTTP types related to a [`Request`].
97pub mod request {
98    use ruma_common::{directory::RoomTypeFilter, serde::deserialize_cow_str, RoomId};
99    use serde::de::Error as _;
100
101    use super::{BTreeMap, Deserialize, OwnedRoomId, Serialize, StateEventType, UInt};
102
103    /// A sliding sync list request (see [`super::Request::lists`]).
104    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
105    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
106    pub struct List {
107        /// The ranges of rooms we're interested in.
108        pub ranges: Vec<(UInt, UInt)>,
109
110        /// The details to be included per room.
111        #[serde(flatten)]
112        pub room_details: RoomDetails,
113
114        /// Request a stripped variant of membership events for the users used
115        /// to calculate the room name.
116        #[serde(skip_serializing_if = "Option::is_none")]
117        pub include_heroes: Option<bool>,
118
119        /// Filters to apply to the list before sorting.
120        #[serde(skip_serializing_if = "Option::is_none")]
121        pub filters: Option<ListFilters>,
122    }
123
124    /// A sliding sync list request filters (see [`List::filters`]).
125    ///
126    /// All fields are applied with _AND_ operators. The absence of fields
127    /// implies no filter on that criteria: it does NOT imply `false`.
128    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
129    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
130    pub struct ListFilters {
131        /// Whether to return invited rooms, only joined rooms or both.
132        ///
133        /// Flag which only returns rooms the user is currently invited to.
134        /// If unset, both invited and joined rooms are returned. If false,
135        /// no invited rooms are returned. If true, only invited rooms are
136        /// returned.
137        #[serde(skip_serializing_if = "Option::is_none")]
138        pub is_invite: Option<bool>,
139
140        /// Only list rooms that are not of these create-types, or all.
141        ///
142        /// This can be used to filter out spaces from the room list.
143        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
144        pub not_room_types: Vec<RoomTypeFilter>,
145    }
146
147    /// Sliding sync request room subscription (see [`super::Request::room_subscriptions`]).
148    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
149    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
150    pub struct RoomSubscription {
151        /// Required state for each returned room. An array of event type and
152        /// state key tuples.
153        #[serde(default, skip_serializing_if = "Vec::is_empty")]
154        pub required_state: Vec<(StateEventType, String)>,
155
156        /// The maximum number of timeline events to return per room.
157        pub timeline_limit: UInt,
158
159        /// Include the room heroes.
160        #[serde(skip_serializing_if = "Option::is_none")]
161        pub include_heroes: Option<bool>,
162    }
163
164    /// Sliding sync request room details (see [`List::room_details`]).
165    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
166    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
167    pub struct RoomDetails {
168        /// Required state for each returned room. An array of event type and state key tuples.
169        #[serde(default, skip_serializing_if = "Vec::is_empty")]
170        pub required_state: Vec<(StateEventType, String)>,
171
172        /// The maximum number of timeline events to return per room.
173        pub timeline_limit: UInt,
174    }
175
176    /// Sliding sync request extensions (see [`super::Request::extensions`]).
177    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
178    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
179    pub struct Extensions {
180        /// Configure the to-device extension.
181        #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
182        pub to_device: ToDevice,
183
184        /// Configure the E2EE extension.
185        #[serde(default, skip_serializing_if = "E2EE::is_empty")]
186        pub e2ee: E2EE,
187
188        /// Configure the account data extension.
189        #[serde(default, skip_serializing_if = "AccountData::is_empty")]
190        pub account_data: AccountData,
191
192        /// Configure the receipts extension.
193        #[serde(default, skip_serializing_if = "Receipts::is_empty")]
194        pub receipts: Receipts,
195
196        /// Configure the typing extension.
197        #[serde(default, skip_serializing_if = "Typing::is_empty")]
198        pub typing: Typing,
199
200        /// Extensions may add further fields to the list.
201        #[serde(flatten)]
202        other: BTreeMap<String, serde_json::Value>,
203    }
204
205    impl Extensions {
206        /// Whether all fields are empty or `None`.
207        pub fn is_empty(&self) -> bool {
208            self.to_device.is_empty()
209                && self.e2ee.is_empty()
210                && self.account_data.is_empty()
211                && self.receipts.is_empty()
212                && self.typing.is_empty()
213                && self.other.is_empty()
214        }
215    }
216
217    /// To-device messages extension.
218    ///
219    /// According to [MSC3885](https://github.com/matrix-org/matrix-spec-proposals/pull/3885).
220    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
221    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
222    pub struct ToDevice {
223        /// Activate or deactivate this extension.
224        #[serde(skip_serializing_if = "Option::is_none")]
225        pub enabled: Option<bool>,
226
227        /// Maximum number of to-device messages per response.
228        #[serde(skip_serializing_if = "Option::is_none")]
229        pub limit: Option<UInt>,
230
231        /// Give messages since this token only.
232        #[serde(skip_serializing_if = "Option::is_none")]
233        pub since: Option<String>,
234
235        /// List of list names for which to-device events should be enabled.
236        ///
237        /// If not defined, will be enabled for *all* the lists appearing in the
238        /// request. If defined and empty, will be disabled for all the lists.
239        #[serde(skip_serializing_if = "Option::is_none")]
240        pub lists: Option<Vec<String>>,
241
242        /// List of room names for which to-device events should be enabled.
243        ///
244        /// If not defined, will be enabled for *all* the rooms appearing in the
245        /// room subscriptions. If defined and empty, will be disabled for all
246        /// the rooms.
247        #[serde(skip_serializing_if = "Option::is_none")]
248        pub rooms: Option<Vec<OwnedRoomId>>,
249    }
250
251    impl ToDevice {
252        /// Whether all fields are empty or `None`.
253        pub fn is_empty(&self) -> bool {
254            self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
255        }
256    }
257
258    /// E2EE extension configuration.
259    ///
260    /// According to [MSC3884](https://github.com/matrix-org/matrix-spec-proposals/pull/3884).
261    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
262    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
263    pub struct E2EE {
264        /// Activate or deactivate this extension.
265        #[serde(skip_serializing_if = "Option::is_none")]
266        pub enabled: Option<bool>,
267    }
268
269    impl E2EE {
270        /// Whether all fields are empty or `None`.
271        pub fn is_empty(&self) -> bool {
272            self.enabled.is_none()
273        }
274    }
275
276    /// Account-data extension .
277    ///
278    /// Not yet part of the spec proposal. Taken from the reference implementation
279    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/account_data.go>
280    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
281    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
282    pub struct AccountData {
283        /// Activate or deactivate this extension.
284        #[serde(skip_serializing_if = "Option::is_none")]
285        pub enabled: Option<bool>,
286
287        /// List of list names for which account data should be enabled.
288        ///
289        /// This is specific to room account data (e.g. user-defined room tags).
290        ///
291        /// If not defined, will be enabled for *all* the lists appearing in the
292        /// request. If defined and empty, will be disabled for all the lists.
293        #[serde(skip_serializing_if = "Option::is_none")]
294        pub lists: Option<Vec<String>>,
295
296        /// List of room names for which account data should be enabled.
297        ///
298        /// This is specific to room account data (e.g. user-defined room tags).
299        ///
300        /// If not defined, will be enabled for *all* the rooms appearing in the
301        /// room subscriptions. If defined and empty, will be disabled for all
302        /// the rooms.
303        #[serde(skip_serializing_if = "Option::is_none")]
304        pub rooms: Option<Vec<OwnedRoomId>>,
305    }
306
307    impl AccountData {
308        /// Whether all fields are empty or `None`.
309        pub fn is_empty(&self) -> bool {
310            self.enabled.is_none()
311        }
312    }
313
314    /// Receipt extension.
315    ///
316    /// According to [MSC3960](https://github.com/matrix-org/matrix-spec-proposals/pull/3960)
317    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
318    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
319    pub struct Receipts {
320        /// Activate or deactivate this extension.
321        #[serde(skip_serializing_if = "Option::is_none")]
322        pub enabled: Option<bool>,
323
324        /// List of list names for which receipts should be enabled.
325        ///
326        /// If not defined, will be enabled for *all* the lists appearing in the
327        /// request. If defined and empty, will be disabled for all the lists.
328        #[serde(skip_serializing_if = "Option::is_none")]
329        pub lists: Option<Vec<String>>,
330
331        /// List of room names for which receipts should be enabled.
332        ///
333        /// If not defined, will be enabled for *all* the rooms appearing in the
334        /// room subscriptions. If defined and empty, will be disabled for all
335        /// the rooms.
336        #[serde(skip_serializing_if = "Option::is_none")]
337        pub rooms: Option<Vec<ReceiptsRoom>>,
338    }
339
340    impl Receipts {
341        /// Whether all fields are empty or `None`.
342        pub fn is_empty(&self) -> bool {
343            self.enabled.is_none()
344        }
345    }
346
347    /// Single entry for a room-related read receipt configuration in
348    /// [`Receipts`].
349    #[derive(Clone, Debug, PartialEq)]
350    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
351    pub enum ReceiptsRoom {
352        /// Get read receipts for all the subscribed rooms.
353        AllSubscribed,
354
355        /// Get read receipts for this particular room.
356        Room(OwnedRoomId),
357    }
358
359    impl Serialize for ReceiptsRoom {
360        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
361        where
362            S: serde::Serializer,
363        {
364            match self {
365                Self::AllSubscribed => serializer.serialize_str("*"),
366                Self::Room(r) => r.serialize(serializer),
367            }
368        }
369    }
370
371    impl<'de> Deserialize<'de> for ReceiptsRoom {
372        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
373        where
374            D: serde::de::Deserializer<'de>,
375        {
376            match deserialize_cow_str(deserializer)?.as_ref() {
377                "*" => Ok(Self::AllSubscribed),
378                other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
379            }
380        }
381    }
382
383    /// Typing extension configuration.
384    ///
385    /// Not yet part of the spec proposal. Taken from the reference implementation
386    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/typing.go>
387    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
388    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
389    pub struct Typing {
390        /// Activate or deactivate this extension.
391        #[serde(skip_serializing_if = "Option::is_none")]
392        pub enabled: Option<bool>,
393
394        /// List of list names for which typing notifications should be enabled.
395        ///
396        /// If not defined, will be enabled for *all* the lists appearing in the
397        /// request. If defined and empty, will be disabled for all the lists.
398        #[serde(skip_serializing_if = "Option::is_none")]
399        pub lists: Option<Vec<String>>,
400
401        /// List of room names for which typing notifications should be enabled.
402        ///
403        /// If not defined, will be enabled for *all* the rooms appearing in the
404        /// room subscriptions. If defined and empty, will be disabled for all
405        /// the rooms.
406        #[serde(skip_serializing_if = "Option::is_none")]
407        pub rooms: Option<Vec<OwnedRoomId>>,
408    }
409
410    impl Typing {
411        /// Whether all fields are empty or `None`.
412        pub fn is_empty(&self) -> bool {
413            self.enabled.is_none()
414        }
415    }
416}
417
418/// Response type for the `/sync` endpoint.
419#[response(error = crate::Error)]
420pub struct Response {
421    /// Matches the `txn_id` sent by the request (see [`Request::txn_id`]).
422    #[serde(skip_serializing_if = "Option::is_none")]
423    pub txn_id: Option<String>,
424
425    /// The token to supply in the `pos` parameter of the next `/sync` request
426    /// (see [`Request::pos`]).
427    pub pos: String,
428
429    /// Resulting details of the lists.
430    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
431    pub lists: BTreeMap<String, response::List>,
432
433    /// The updated rooms.
434    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
435    pub rooms: BTreeMap<OwnedRoomId, response::Room>,
436
437    /// Extensions.
438    #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
439    pub extensions: response::Extensions,
440}
441
442impl Response {
443    /// Creates a new `Response` with the given `pos`.
444    pub fn new(pos: String) -> Self {
445        Self {
446            txn_id: None,
447            pos,
448            lists: Default::default(),
449            rooms: Default::default(),
450            extensions: Default::default(),
451        }
452    }
453}
454
455/// HTTP types related to a [`Response`].
456pub mod response {
457    use ruma_common::OneTimeKeyAlgorithm;
458    use ruma_events::{
459        receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
460        AnyRoomAccountDataEvent, AnyToDeviceEvent,
461    };
462
463    use super::{
464        super::DeviceLists, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
465        BTreeMap, Deserialize, JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize,
466        UInt, UnreadNotificationsCount,
467    };
468
469    /// A sliding sync response updates to joiend rooms (see
470    /// [`super::Response::lists`]).
471    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
472    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
473    pub struct List {
474        /// The total number of rooms found for this list.
475        pub count: UInt,
476    }
477
478    /// A slising sync response updated room (see [`super::Response::rooms`]).
479    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
480    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
481    pub struct Room {
482        /// The name as calculated by the server.
483        #[serde(skip_serializing_if = "Option::is_none")]
484        pub name: Option<String>,
485
486        /// The avatar.
487        #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
488        pub avatar: JsOption<OwnedMxcUri>,
489
490        /// Whether it is an initial response.
491        #[serde(skip_serializing_if = "Option::is_none")]
492        pub initial: Option<bool>,
493
494        /// Whether it is a direct room.
495        #[serde(skip_serializing_if = "Option::is_none")]
496        pub is_dm: Option<bool>,
497
498        /// If this is `Some(_)`, this is a not-yet-accepted invite containing
499        /// the given stripped state events.
500        #[serde(skip_serializing_if = "Option::is_none")]
501        pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
502
503        /// Number of unread notifications.
504        #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
505        pub unread_notifications: UnreadNotificationsCount,
506
507        /// Message-like events and live state events.
508        #[serde(default, skip_serializing_if = "Vec::is_empty")]
509        pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
510
511        /// State events as configured by the request.
512        #[serde(default, skip_serializing_if = "Vec::is_empty")]
513        pub required_state: Vec<Raw<AnySyncStateEvent>>,
514
515        /// The `prev_batch` allowing you to paginate through the messages
516        /// before the given ones.
517        #[serde(skip_serializing_if = "Option::is_none")]
518        pub prev_batch: Option<String>,
519
520        /// True if the number of events returned was limited by the limit on
521        /// the filter.
522        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
523        pub limited: bool,
524
525        /// The number of users with membership of `join`, including the
526        /// client’s own user ID.
527        #[serde(skip_serializing_if = "Option::is_none")]
528        pub joined_count: Option<UInt>,
529
530        /// The number of users with membership of `invite`.
531        #[serde(skip_serializing_if = "Option::is_none")]
532        pub invited_count: Option<UInt>,
533
534        /// The number of timeline events which have just occurred and are not
535        /// historical.
536        #[serde(skip_serializing_if = "Option::is_none")]
537        pub num_live: Option<UInt>,
538
539        /// The bump stamp of the room.
540        ///
541        /// It can be interpreted as a “recency stamp” or “streaming order
542        /// index”. For example, consider `roomA` with `bump_stamp = 2`, `roomB`
543        /// with `bump_stamp = 1` and `roomC` with `bump_stamp = 0`. If `roomC`
544        /// receives an update, its `bump_stamp` will be 3.
545        #[serde(skip_serializing_if = "Option::is_none")]
546        pub bump_stamp: Option<UInt>,
547
548        /// Heroes of the room, if requested.
549        #[serde(skip_serializing_if = "Option::is_none")]
550        pub heroes: Option<Vec<Hero>>,
551    }
552
553    impl Room {
554        /// Creates an empty `Room`.
555        pub fn new() -> Self {
556            Default::default()
557        }
558    }
559
560    /// A sliding sync response room hero (see [`Room::heroes`]).
561    #[derive(Clone, Debug, Deserialize, Serialize)]
562    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
563    pub struct Hero {
564        /// The user ID.
565        pub user_id: OwnedUserId,
566
567        /// The name.
568        #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
569        pub name: Option<String>,
570
571        /// The avatar.
572        #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
573        pub avatar: Option<OwnedMxcUri>,
574    }
575
576    impl Hero {
577        /// Creates a new `Hero` with the given user ID.
578        pub fn new(user_id: OwnedUserId) -> Self {
579            Self { user_id, name: None, avatar: None }
580        }
581    }
582
583    /// Extensions responses.
584    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
585    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
586    pub struct Extensions {
587        /// To-device extension response.
588        #[serde(skip_serializing_if = "Option::is_none")]
589        pub to_device: Option<ToDevice>,
590
591        /// E2EE extension response.
592        #[serde(default, skip_serializing_if = "E2EE::is_empty")]
593        pub e2ee: E2EE,
594
595        /// Account data extension response.
596        #[serde(default, skip_serializing_if = "AccountData::is_empty")]
597        pub account_data: AccountData,
598
599        /// Receipts extension response.
600        #[serde(default, skip_serializing_if = "Receipts::is_empty")]
601        pub receipts: Receipts,
602
603        /// Typing extension response.
604        #[serde(default, skip_serializing_if = "Typing::is_empty")]
605        pub typing: Typing,
606    }
607
608    impl Extensions {
609        /// Whether the extension data is empty.
610        ///
611        /// True if neither to-device, e2ee nor account data are to be found.
612        pub fn is_empty(&self) -> bool {
613            self.to_device.is_none()
614                && self.e2ee.is_empty()
615                && self.account_data.is_empty()
616                && self.receipts.is_empty()
617                && self.typing.is_empty()
618        }
619    }
620
621    /// To-device extension response.
622    ///
623    /// According to [MSC3885](https://github.com/matrix-org/matrix-spec-proposals/pull/3885).
624    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
625    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
626    pub struct ToDevice {
627        /// Fetch the next batch from this entry.
628        pub next_batch: String,
629
630        /// The to-device events.
631        #[serde(default, skip_serializing_if = "Vec::is_empty")]
632        pub events: Vec<Raw<AnyToDeviceEvent>>,
633    }
634
635    /// E2EE extension response.
636    ///
637    /// According to [MSC3884](https://github.com/matrix-org/matrix-spec-proposals/pull/3884).
638    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
639    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
640    pub struct E2EE {
641        /// Information on E2EE device updates.
642        #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
643        pub device_lists: DeviceLists,
644
645        /// For each key algorithm, the number of unclaimed one-time keys
646        /// currently held on the server for a device.
647        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
648        pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
649
650        /// The unused fallback key algorithms.
651        ///
652        /// The presence of this field indicates that the server supports
653        /// fallback keys.
654        #[serde(skip_serializing_if = "Option::is_none")]
655        pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
656    }
657
658    impl E2EE {
659        /// Whether all fields are empty or `None`.
660        pub fn is_empty(&self) -> bool {
661            self.device_lists.is_empty()
662                && self.device_one_time_keys_count.is_empty()
663                && self.device_unused_fallback_key_types.is_none()
664        }
665    }
666
667    /// Account-data extension response .
668    ///
669    /// Not yet part of the spec proposal. Taken from the reference implementation
670    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/account_data.go>
671    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
672    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
673    pub struct AccountData {
674        /// The global private data created by this user.
675        #[serde(default, skip_serializing_if = "Vec::is_empty")]
676        pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
677
678        /// The private data that this user has attached to each room.
679        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
680        pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
681    }
682
683    impl AccountData {
684        /// Whether all fields are empty or `None`.
685        pub fn is_empty(&self) -> bool {
686            self.global.is_empty() && self.rooms.is_empty()
687        }
688    }
689
690    /// Receipt extension response.
691    ///
692    /// According to [MSC3960](https://github.com/matrix-org/matrix-spec-proposals/pull/3960)
693    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
694    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
695    pub struct Receipts {
696        /// The ephemeral receipt room event for each room.
697        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
698        pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
699    }
700
701    impl Receipts {
702        /// Whether all fields are empty or `None`.
703        pub fn is_empty(&self) -> bool {
704            self.rooms.is_empty()
705        }
706    }
707
708    /// Typing extension response.
709    ///
710    /// Not yet part of the spec proposal. Taken from the reference implementation
711    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/typing.go>
712    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
713    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
714    pub struct Typing {
715        /// The ephemeral typing event for each room.
716        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
717        pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
718    }
719
720    impl Typing {
721        /// Whether all fields are empty or `None`.
722        pub fn is_empty(&self) -> bool {
723            self.rooms.is_empty()
724        }
725    }
726}
727
728#[cfg(test)]
729mod tests {
730    use ruma_common::owned_room_id;
731
732    use super::request::ReceiptsRoom;
733
734    #[test]
735    fn serialize_request_receipts_room() {
736        let entry = ReceiptsRoom::AllSubscribed;
737        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
738
739        let entry = ReceiptsRoom::Room(owned_room_id!("!foo:bar.baz"));
740        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
741    }
742
743    #[test]
744    fn deserialize_request_receipts_room() {
745        assert_eq!(
746            serde_json::from_str::<ReceiptsRoom>(r#""*""#).unwrap(),
747            ReceiptsRoom::AllSubscribed
748        );
749
750        assert_eq!(
751            serde_json::from_str::<ReceiptsRoom>(r#""!foo:bar.baz""#).unwrap(),
752            ReceiptsRoom::Room(owned_room_id!("!foo:bar.baz"))
753        );
754    }
755}