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::{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("org.matrix.simplified_msc3575") => "/_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        /// Filters to apply to the list before sorting.
115        #[serde(skip_serializing_if = "Option::is_none")]
116        pub filters: Option<ListFilters>,
117    }
118
119    /// A sliding sync list request filters (see [`List::filters`]).
120    ///
121    /// All fields are applied with _AND_ operators. The absence of fields
122    /// implies no filter on that criteria: it does NOT imply `false`.
123    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
124    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
125    pub struct ListFilters {
126        /// Whether to return only DM rooms (as determined by the `m.direct` account data event),
127        /// only non-DM rooms, or both.
128        #[serde(skip_serializing_if = "Option::is_none")]
129        pub is_dm: Option<bool>,
130
131        /// Whether to return only encrypted rooms (as determined by the existence of an
132        /// `m.room.encryption` state event), only unencrypted rooms, or both.
133        #[serde(skip_serializing_if = "Option::is_none")]
134        pub is_encrypted: Option<bool>,
135
136        /// Whether to return only invited rooms, only joined rooms, or both.
137        #[serde(skip_serializing_if = "Option::is_none")]
138        pub is_invite: Option<bool>,
139
140        /// Only list rooms with these create-types, or all.
141        ///
142        /// If a room type is specified in both `room_types` and `not_room_types`,
143        /// `not_room_types` wins and the corresponding rooms are not included.
144        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
145        pub room_types: Vec<RoomTypeFilter>,
146
147        /// Only list rooms that are not of these create-types, or all.
148        ///
149        /// If a room type is specified in both `room_types` and `not_room_types`,
150        /// `not_room_types` wins and the corresponding rooms are not included.
151        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
152        pub not_room_types: Vec<RoomTypeFilter>,
153    }
154
155    /// Sliding sync request room subscription (see [`super::Request::room_subscriptions`]).
156    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
157    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
158    pub struct RoomSubscription {
159        /// Required state for each returned room. An array of event type and
160        /// state key tuples.
161        #[serde(default, skip_serializing_if = "Vec::is_empty")]
162        pub required_state: Vec<(StateEventType, String)>,
163
164        /// The maximum number of timeline events to return per room.
165        pub timeline_limit: UInt,
166    }
167
168    /// Sliding sync request room details (see [`List::room_details`]).
169    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
170    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
171    pub struct RoomDetails {
172        /// Required state for each returned room. An array of event type and state key tuples.
173        #[serde(default, skip_serializing_if = "Vec::is_empty")]
174        pub required_state: Vec<(StateEventType, String)>,
175
176        /// The maximum number of timeline events to return per room.
177        pub timeline_limit: UInt,
178    }
179
180    /// Sliding sync request extensions (see [`super::Request::extensions`]).
181    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
182    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
183    pub struct Extensions {
184        /// Configure the to-device extension.
185        #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
186        pub to_device: ToDevice,
187
188        /// Configure the E2EE extension.
189        #[serde(default, skip_serializing_if = "E2EE::is_empty")]
190        pub e2ee: E2EE,
191
192        /// Configure the account data extension.
193        #[serde(default, skip_serializing_if = "AccountData::is_empty")]
194        pub account_data: AccountData,
195
196        /// Configure the receipts extension.
197        #[serde(default, skip_serializing_if = "Receipts::is_empty")]
198        pub receipts: Receipts,
199
200        /// Configure the typing extension.
201        #[serde(default, skip_serializing_if = "Typing::is_empty")]
202        pub typing: Typing,
203
204        /// Extensions may add further fields to the list.
205        #[serde(flatten)]
206        other: BTreeMap<String, serde_json::Value>,
207    }
208
209    impl Extensions {
210        /// Whether all fields are empty or `None`.
211        pub fn is_empty(&self) -> bool {
212            self.to_device.is_empty()
213                && self.e2ee.is_empty()
214                && self.account_data.is_empty()
215                && self.receipts.is_empty()
216                && self.typing.is_empty()
217                && self.other.is_empty()
218        }
219    }
220
221    /// Single entry for a room subscription configuration in an extension request.
222    #[derive(Clone, Debug, PartialEq)]
223    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
224    pub enum ExtensionRoomConfig {
225        /// Apply extension to all global room subscriptions.
226        AllSubscribed,
227
228        /// Additionally apply extension to this specific room.
229        Room(OwnedRoomId),
230    }
231
232    impl Serialize for ExtensionRoomConfig {
233        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
234        where
235            S: serde::Serializer,
236        {
237            match self {
238                Self::AllSubscribed => serializer.serialize_str("*"),
239                Self::Room(r) => r.serialize(serializer),
240            }
241        }
242    }
243
244    impl<'de> Deserialize<'de> for ExtensionRoomConfig {
245        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246        where
247            D: serde::de::Deserializer<'de>,
248        {
249            match deserialize_cow_str(deserializer)?.as_ref() {
250                "*" => Ok(Self::AllSubscribed),
251                other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
252            }
253        }
254    }
255
256    /// To-device messages extension.
257    ///
258    /// According to [MSC3885](https://github.com/matrix-org/matrix-spec-proposals/pull/3885).
259    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
260    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
261    pub struct ToDevice {
262        /// Activate or deactivate this extension.
263        #[serde(skip_serializing_if = "Option::is_none")]
264        pub enabled: Option<bool>,
265
266        /// Maximum number of to-device messages per response.
267        #[serde(skip_serializing_if = "Option::is_none")]
268        pub limit: Option<UInt>,
269
270        /// Give messages since this token only.
271        #[serde(skip_serializing_if = "Option::is_none")]
272        pub since: Option<String>,
273    }
274
275    impl ToDevice {
276        /// Whether all fields are empty or `None`.
277        pub fn is_empty(&self) -> bool {
278            self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
279        }
280    }
281
282    /// E2EE extension configuration.
283    ///
284    /// According to [MSC3884](https://github.com/matrix-org/matrix-spec-proposals/pull/3884).
285    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
286    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
287    pub struct E2EE {
288        /// Activate or deactivate this extension.
289        #[serde(skip_serializing_if = "Option::is_none")]
290        pub enabled: Option<bool>,
291    }
292
293    impl E2EE {
294        /// Whether all fields are empty or `None`.
295        pub fn is_empty(&self) -> bool {
296            self.enabled.is_none()
297        }
298    }
299
300    /// Account-data extension .
301    ///
302    /// Not yet part of the spec proposal. Taken from the reference implementation
303    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/account_data.go>
304    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
305    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
306    pub struct AccountData {
307        /// Activate or deactivate this extension.
308        #[serde(skip_serializing_if = "Option::is_none")]
309        pub enabled: Option<bool>,
310
311        /// List of list names for which account data should be enabled.
312        ///
313        /// This is specific to room account data (e.g. user-defined room tags).
314        ///
315        /// If not defined, will be enabled for *all* the lists appearing in the
316        /// request. If defined and empty, will be disabled for all the lists.
317        #[serde(skip_serializing_if = "Option::is_none")]
318        pub lists: Option<Vec<String>>,
319
320        /// List of room names for which account data should be enabled.
321        ///
322        /// This is specific to room account data (e.g. user-defined room tags).
323        ///
324        /// If not defined, will be enabled for *all* the rooms appearing in the
325        /// room subscriptions. If defined and empty, will be disabled for all
326        /// the rooms.
327        #[serde(skip_serializing_if = "Option::is_none")]
328        pub rooms: Option<Vec<ExtensionRoomConfig>>,
329    }
330
331    impl AccountData {
332        /// Whether all fields are empty or `None`.
333        pub fn is_empty(&self) -> bool {
334            self.enabled.is_none()
335        }
336    }
337
338    /// Receipt extension.
339    ///
340    /// According to [MSC3960](https://github.com/matrix-org/matrix-spec-proposals/pull/3960)
341    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
342    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
343    pub struct Receipts {
344        /// Activate or deactivate this extension.
345        #[serde(skip_serializing_if = "Option::is_none")]
346        pub enabled: Option<bool>,
347
348        /// List of list names for which receipts should be enabled.
349        ///
350        /// If not defined, will be enabled for *all* the lists appearing in the
351        /// request. If defined and empty, will be disabled for all the lists.
352        #[serde(skip_serializing_if = "Option::is_none")]
353        pub lists: Option<Vec<String>>,
354
355        /// List of room names for which receipts should be enabled.
356        ///
357        /// If not defined, will be enabled for *all* the rooms appearing in the
358        /// room subscriptions. If defined and empty, will be disabled for all
359        /// the rooms.
360        #[serde(skip_serializing_if = "Option::is_none")]
361        pub rooms: Option<Vec<ExtensionRoomConfig>>,
362    }
363
364    impl Receipts {
365        /// Whether all fields are empty or `None`.
366        pub fn is_empty(&self) -> bool {
367            self.enabled.is_none()
368        }
369    }
370
371    /// Typing extension configuration.
372    ///
373    /// Not yet part of the spec proposal. Taken from the reference implementation
374    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/typing.go>
375    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
376    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
377    pub struct Typing {
378        /// Activate or deactivate this extension.
379        #[serde(skip_serializing_if = "Option::is_none")]
380        pub enabled: Option<bool>,
381
382        /// List of list names for which typing notifications should be enabled.
383        ///
384        /// If not defined, will be enabled for *all* the lists appearing in the
385        /// request. If defined and empty, will be disabled for all the lists.
386        #[serde(skip_serializing_if = "Option::is_none")]
387        pub lists: Option<Vec<String>>,
388
389        /// List of room names for which typing notifications should be enabled.
390        ///
391        /// If not defined, will be enabled for *all* the rooms appearing in the
392        /// room subscriptions. If defined and empty, will be disabled for all
393        /// the rooms.
394        #[serde(skip_serializing_if = "Option::is_none")]
395        pub rooms: Option<Vec<ExtensionRoomConfig>>,
396    }
397
398    impl Typing {
399        /// Whether all fields are empty or `None`.
400        pub fn is_empty(&self) -> bool {
401            self.enabled.is_none()
402        }
403    }
404}
405
406/// Response type for the `/sync` endpoint.
407#[response(error = crate::Error)]
408pub struct Response {
409    /// Matches the `txn_id` sent by the request (see [`Request::txn_id`]).
410    #[serde(skip_serializing_if = "Option::is_none")]
411    pub txn_id: Option<String>,
412
413    /// The token to supply in the `pos` parameter of the next `/sync` request
414    /// (see [`Request::pos`]).
415    pub pos: String,
416
417    /// Resulting details of the lists.
418    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
419    pub lists: BTreeMap<String, response::List>,
420
421    /// The updated rooms.
422    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
423    pub rooms: BTreeMap<OwnedRoomId, response::Room>,
424
425    /// Extensions.
426    #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
427    pub extensions: response::Extensions,
428}
429
430impl Response {
431    /// Creates a new `Response` with the given `pos`.
432    pub fn new(pos: String) -> Self {
433        Self {
434            txn_id: None,
435            pos,
436            lists: Default::default(),
437            rooms: Default::default(),
438            extensions: Default::default(),
439        }
440    }
441}
442
443/// HTTP types related to a [`Response`].
444pub mod response {
445    use ruma_common::OneTimeKeyAlgorithm;
446    use ruma_events::{
447        receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
448        AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnyToDeviceEvent,
449    };
450
451    use super::{
452        super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
453        JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
454        UnreadNotificationsCount,
455    };
456
457    /// A sliding sync response updates to joiend rooms (see
458    /// [`super::Response::lists`]).
459    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
460    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
461    pub struct List {
462        /// The total number of rooms found for this list.
463        pub count: UInt,
464    }
465
466    /// A slising sync response updated room (see [`super::Response::rooms`]).
467    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
468    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
469    pub struct Room {
470        /// The name as calculated by the server.
471        #[serde(skip_serializing_if = "Option::is_none")]
472        pub name: Option<String>,
473
474        /// The avatar.
475        #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
476        pub avatar: JsOption<OwnedMxcUri>,
477
478        /// Whether it is an initial response.
479        #[serde(skip_serializing_if = "Option::is_none")]
480        pub initial: Option<bool>,
481
482        /// Whether it is a direct room.
483        #[serde(skip_serializing_if = "Option::is_none")]
484        pub is_dm: Option<bool>,
485
486        /// If this is `Some(_)`, this is a not-yet-accepted invite containing
487        /// the given stripped state events.
488        #[serde(skip_serializing_if = "Option::is_none")]
489        pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
490
491        /// Number of unread notifications.
492        #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
493        pub unread_notifications: UnreadNotificationsCount,
494
495        /// Message-like events and live state events.
496        #[serde(default, skip_serializing_if = "Vec::is_empty")]
497        pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
498
499        /// State events as configured by the request.
500        #[serde(default, skip_serializing_if = "Vec::is_empty")]
501        pub required_state: Vec<Raw<AnySyncStateEvent>>,
502
503        /// The `prev_batch` allowing you to paginate through the messages
504        /// before the given ones.
505        #[serde(skip_serializing_if = "Option::is_none")]
506        pub prev_batch: Option<String>,
507
508        /// True if the number of events returned was limited by the limit on
509        /// the filter.
510        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
511        pub limited: bool,
512
513        /// The number of users with membership of `join`, including the
514        /// client’s own user ID.
515        #[serde(skip_serializing_if = "Option::is_none")]
516        pub joined_count: Option<UInt>,
517
518        /// The number of users with membership of `invite`.
519        #[serde(skip_serializing_if = "Option::is_none")]
520        pub invited_count: Option<UInt>,
521
522        /// The number of timeline events which have just occurred and are not
523        /// historical.
524        #[serde(skip_serializing_if = "Option::is_none")]
525        pub num_live: Option<UInt>,
526
527        /// The bump stamp of the room.
528        ///
529        /// It can be interpreted as a “recency stamp” or “streaming order
530        /// index”. For example, consider `roomA` with `bump_stamp = 2`, `roomB`
531        /// with `bump_stamp = 1` and `roomC` with `bump_stamp = 0`. If `roomC`
532        /// receives an update, its `bump_stamp` will be 3.
533        #[serde(skip_serializing_if = "Option::is_none")]
534        pub bump_stamp: Option<UInt>,
535
536        /// Heroes of the room.
537        #[serde(skip_serializing_if = "Option::is_none")]
538        pub heroes: Option<Vec<Hero>>,
539    }
540
541    impl Room {
542        /// Creates an empty `Room`.
543        pub fn new() -> Self {
544            Default::default()
545        }
546    }
547
548    /// A sliding sync response room hero (see [`Room::heroes`]).
549    #[derive(Clone, Debug, Deserialize, Serialize)]
550    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
551    pub struct Hero {
552        /// The user ID.
553        pub user_id: OwnedUserId,
554
555        /// The name.
556        #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
557        pub name: Option<String>,
558
559        /// The avatar.
560        #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
561        pub avatar: Option<OwnedMxcUri>,
562    }
563
564    impl Hero {
565        /// Creates a new `Hero` with the given user ID.
566        pub fn new(user_id: OwnedUserId) -> Self {
567            Self { user_id, name: None, avatar: None }
568        }
569    }
570
571    /// Extensions responses.
572    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
573    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
574    pub struct Extensions {
575        /// To-device extension response.
576        #[serde(skip_serializing_if = "Option::is_none")]
577        pub to_device: Option<ToDevice>,
578
579        /// E2EE extension response.
580        #[serde(default, skip_serializing_if = "E2EE::is_empty")]
581        pub e2ee: E2EE,
582
583        /// Account data extension response.
584        #[serde(default, skip_serializing_if = "AccountData::is_empty")]
585        pub account_data: AccountData,
586
587        /// Receipts extension response.
588        #[serde(default, skip_serializing_if = "Receipts::is_empty")]
589        pub receipts: Receipts,
590
591        /// Typing extension response.
592        #[serde(default, skip_serializing_if = "Typing::is_empty")]
593        pub typing: Typing,
594    }
595
596    impl Extensions {
597        /// Whether the extension data is empty.
598        ///
599        /// True if neither to-device, e2ee nor account data are to be found.
600        pub fn is_empty(&self) -> bool {
601            self.to_device.is_none()
602                && self.e2ee.is_empty()
603                && self.account_data.is_empty()
604                && self.receipts.is_empty()
605                && self.typing.is_empty()
606        }
607    }
608
609    /// To-device extension response.
610    ///
611    /// According to [MSC3885](https://github.com/matrix-org/matrix-spec-proposals/pull/3885).
612    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
613    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
614    pub struct ToDevice {
615        /// Fetch the next batch from this entry.
616        pub next_batch: String,
617
618        /// The to-device events.
619        #[serde(default, skip_serializing_if = "Vec::is_empty")]
620        pub events: Vec<Raw<AnyToDeviceEvent>>,
621    }
622
623    /// E2EE extension response.
624    ///
625    /// According to [MSC3884](https://github.com/matrix-org/matrix-spec-proposals/pull/3884).
626    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
627    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
628    pub struct E2EE {
629        /// Information on E2EE device updates.
630        #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
631        pub device_lists: DeviceLists,
632
633        /// For each key algorithm, the number of unclaimed one-time keys
634        /// currently held on the server for a device.
635        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
636        pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
637
638        /// The unused fallback key algorithms.
639        ///
640        /// The presence of this field indicates that the server supports
641        /// fallback keys.
642        #[serde(skip_serializing_if = "Option::is_none")]
643        pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
644    }
645
646    impl E2EE {
647        /// Whether all fields are empty or `None`.
648        pub fn is_empty(&self) -> bool {
649            self.device_lists.is_empty()
650                && self.device_one_time_keys_count.is_empty()
651                && self.device_unused_fallback_key_types.is_none()
652        }
653    }
654
655    /// Account-data extension response .
656    ///
657    /// Not yet part of the spec proposal. Taken from the reference implementation
658    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/account_data.go>
659    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
660    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
661    pub struct AccountData {
662        /// The global private data created by this user.
663        #[serde(default, skip_serializing_if = "Vec::is_empty")]
664        pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
665
666        /// The private data that this user has attached to each room.
667        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
668        pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
669    }
670
671    impl AccountData {
672        /// Whether all fields are empty or `None`.
673        pub fn is_empty(&self) -> bool {
674            self.global.is_empty() && self.rooms.is_empty()
675        }
676    }
677
678    /// Receipt extension response.
679    ///
680    /// According to [MSC3960](https://github.com/matrix-org/matrix-spec-proposals/pull/3960)
681    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
682    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
683    pub struct Receipts {
684        /// The ephemeral receipt room event for each room.
685        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
686        pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
687    }
688
689    impl Receipts {
690        /// Whether all fields are empty or `None`.
691        pub fn is_empty(&self) -> bool {
692            self.rooms.is_empty()
693        }
694    }
695
696    /// Typing extension response.
697    ///
698    /// Not yet part of the spec proposal. Taken from the reference implementation
699    /// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/typing.go>
700    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
701    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
702    pub struct Typing {
703        /// The ephemeral typing event for each room.
704        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
705        pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
706    }
707
708    impl Typing {
709        /// Whether all fields are empty or `None`.
710        pub fn is_empty(&self) -> bool {
711            self.rooms.is_empty()
712        }
713    }
714}
715
716#[cfg(test)]
717mod tests {
718    use ruma_common::owned_room_id;
719
720    use super::request::ExtensionRoomConfig;
721
722    #[test]
723    fn serialize_request_extension_room_config() {
724        let entry = ExtensionRoomConfig::AllSubscribed;
725        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
726
727        let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
728        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
729    }
730
731    #[test]
732    fn deserialize_request_extension_room_config() {
733        assert_eq!(
734            serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
735            ExtensionRoomConfig::AllSubscribed
736        );
737
738        assert_eq!(
739            serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
740            ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
741        );
742    }
743}