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