ruma_client_api/sync/sync_events/
v4.rs

1//! `POST /_matrix/client/unstable/org.matrix.msc3575/sync` ([MSC])
2//!
3//! Get all new events in a sliding window of rooms since the last sync or a given point in time.
4//!
5//! [MSC]: https://github.com/matrix-org/matrix-doc/blob/kegan/sync-v3/proposals/3575-sync.md
6
7use std::{collections::BTreeMap, time::Duration};
8
9use js_int::UInt;
10use js_option::JsOption;
11use ruma_common::{
12    api::{request, response, Metadata},
13    directory::RoomTypeFilter,
14    metadata,
15    serde::{deserialize_cow_str, duration::opt_ms, Raw},
16    MilliSecondsSinceUnixEpoch, OneTimeKeyAlgorithm, OwnedMxcUri, OwnedRoomId, OwnedUserId, RoomId,
17};
18use ruma_events::{
19    receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
20    AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
21    AnyToDeviceEvent, StateEventType, TimelineEventType,
22};
23use serde::{de::Error as _, Deserialize, Serialize};
24
25#[cfg(feature = "unstable-msc4186")]
26use super::v5;
27use super::{DeviceLists, UnreadNotificationsCount};
28
29const METADATA: Metadata = metadata! {
30    method: POST,
31    rate_limited: false,
32    authentication: AccessToken,
33    history: {
34        unstable => "/_matrix/client/unstable/org.matrix.msc3575/sync",
35        // 1.4 => "/_matrix/client/v4/sync",
36    }
37};
38
39/// Request type for the `sync` endpoint.
40#[request(error = crate::Error)]
41#[derive(Default)]
42pub struct Request {
43    /// A point in time to continue a sync from.
44    ///
45    /// Should be a token from the `pos` field of a previous `/sync`
46    /// response.
47    #[serde(skip_serializing_if = "Option::is_none")]
48    #[ruma_api(query)]
49    pub pos: Option<String>,
50
51    /// The delta token to store for session recovery.
52    ///
53    /// The delta token is a future bandwidth optimisation to resume from an
54    /// earlier session. If you received a delta token in your last response
55    /// you can persist and it when establishing a new sessions to "resume"
56    /// from the last state and not resend information you had stored. If you
57    /// send a delta token, the server expects you to have stored the last
58    /// state, if there is no delta token present the server will resend all
59    /// information necessary to calculate the state.
60    ///
61    /// Please consult ["Bandwidth optimisations for persistent clients" of the MSC][MSC]
62    /// for further details, expectations of the implementation and limitations
63    /// to consider before implementing this.
64    ///
65    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#bandwidth-optimisations-for-persistent-clients
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub delta_token: Option<String>,
68
69    /// A unique string identifier for this connection to the server.
70    ///
71    /// Optional. If this is missing, only one sliding sync connection can be made to the server at
72    /// any one time. Clients need to set this to allow more than one connection concurrently,
73    /// so the server can distinguish between connections. This is NOT STICKY and must be
74    /// provided with every request, if your client needs more than one concurrent connection.
75    ///
76    /// Limitation: it must not contain more than 16 chars, due to it being required with every
77    /// request.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    pub conn_id: Option<String>,
80
81    /// Allows clients to know what request params reached the server,
82    /// functionally similar to txn IDs on /send for events.
83    #[serde(skip_serializing_if = "Option::is_none")]
84    pub txn_id: Option<String>,
85
86    /// The maximum time to poll before responding to this request.
87    #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")]
88    #[ruma_api(query)]
89    pub timeout: Option<Duration>,
90
91    /// The list configurations of rooms we are interested in mapped by
92    /// name.
93    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
94    pub lists: BTreeMap<String, SyncRequestList>,
95
96    /// Specific rooms and event types that we want to receive events from.
97    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
98    pub room_subscriptions: BTreeMap<OwnedRoomId, RoomSubscription>,
99
100    /// Specific rooms we no longer want to receive events from.
101    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
102    pub unsubscribe_rooms: Vec<OwnedRoomId>,
103
104    /// Extensions API.
105    #[serde(default, skip_serializing_if = "ExtensionsConfig::is_empty")]
106    pub extensions: ExtensionsConfig,
107}
108
109/// Response type for the `sync` endpoint.
110#[response(error = crate::Error)]
111pub struct Response {
112    /// Whether this response describes an initial sync (i.e. after the `pos` token has been
113    /// discard by the server?).
114    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
115    pub initial: bool,
116
117    /// Matches the `txn_id` sent by the request. Please see [`Request::txn_id`].
118    #[serde(skip_serializing_if = "Option::is_none")]
119    pub txn_id: Option<String>,
120
121    /// The token to supply in the `pos` param of the next `/sync` request.
122    pub pos: String,
123
124    /// Updates on the order of rooms, mapped by the names we asked for.
125    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
126    pub lists: BTreeMap<String, SyncList>,
127
128    /// The updates on rooms.
129    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
130    pub rooms: BTreeMap<OwnedRoomId, SlidingSyncRoom>,
131
132    /// Extensions API.
133    #[serde(default, skip_serializing_if = "Extensions::is_empty")]
134    pub extensions: Extensions,
135
136    /// The delta token to store for session recovery.
137    ///
138    /// The delta token is a future bandwidth optimisation to resume from an
139    /// earlier session. If you received a delta token in your last response
140    /// you can persist and it when establishing a new sessions to "resume"
141    /// from the last state and not resend information you had stored. If you
142    /// send a delta token, the server expects you to have stored the last
143    /// state, if there is no delta token present the server will resend all
144    /// information necessary to calculate the state.
145    ///
146    /// Please consult ["Bandwidth optimisations for persistent clients" of the MSC][MSC]
147    /// for further details, expectations of the implementation and limitations
148    /// to consider before implementing this.
149    ///
150    /// [MSC]: https://github.com/matrix-org/matrix-spec-proposals/blob/kegan/sync-v3/proposals/3575-sync.md#bandwidth-optimisations-for-persistent-clients
151    pub delta_token: Option<String>,
152}
153
154impl Request {
155    /// Creates an empty `Request`.
156    pub fn new() -> Self {
157        Default::default()
158    }
159}
160
161impl Response {
162    /// Creates a new `Response` with the given pos.
163    pub fn new(pos: String) -> Self {
164        Self {
165            initial: Default::default(),
166            txn_id: None,
167            pos,
168            delta_token: Default::default(),
169            lists: Default::default(),
170            rooms: Default::default(),
171            extensions: Default::default(),
172        }
173    }
174}
175
176/// Filter for a sliding sync list, set at request.
177///
178/// All fields are applied with AND operators, hence if `is_dm`  is `true` and `is_encrypted` is
179/// `true` then only encrypted DM rooms will be returned. The absence of fields implies no filter
180/// on that criteria: it does NOT imply `false`.
181///
182/// Filters are considered _sticky_, meaning that the filter only has to be provided once and their
183/// parameters 'sticks' for future requests until a new filter overwrites them.
184#[derive(Clone, Debug, Default, Serialize, Deserialize)]
185#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
186pub struct SyncRequestListFilters {
187    /// Whether to return DMs, non-DM rooms or both.
188    ///
189    /// Flag which only returns rooms present (or not) in the DM section of account data.
190    /// If unset, both DM rooms and non-DM rooms are returned. If false, only non-DM rooms
191    /// are returned. If true, only DM rooms are returned.
192    #[serde(skip_serializing_if = "Option::is_none")]
193    pub is_dm: Option<bool>,
194
195    /// Only list rooms that are spaces of these or all.
196    ///
197    /// A list of spaces which target rooms must be a part of. For every invited/joined
198    /// room for this user, ensure that there is a parent space event which is in this list. If
199    /// unset, all rooms are included. Servers MUST NOT navigate subspaces. It is up to the
200    /// client to give a complete list of spaces to navigate. Only rooms directly in these
201    /// spaces will be returned.
202    #[serde(default, skip_serializing_if = "Vec::is_empty")]
203    pub spaces: Vec<String>,
204
205    /// Whether to return encrypted, non-encrypted rooms or both.
206    ///
207    /// Flag which only returns rooms which have an `m.room.encryption` state event. If
208    /// unset, both encrypted and unencrypted rooms are returned. If false, only unencrypted
209    /// rooms are returned. If true, only encrypted rooms are returned.
210    #[serde(skip_serializing_if = "Option::is_none")]
211    pub is_encrypted: Option<bool>,
212
213    /// Whether to return invited Rooms, only joined rooms or both.
214    ///
215    /// Flag which only returns rooms the user is currently invited to. If unset, both
216    /// invited and joined rooms are returned. If false, no invited rooms are returned. If
217    /// true, only invited rooms are returned.
218    #[serde(skip_serializing_if = "Option::is_none")]
219    pub is_invite: Option<bool>,
220
221    /// Whether to return Rooms with tombstones, only rooms without tombstones or both.
222    ///
223    /// Flag which only returns rooms which have an `m.room.tombstone` state event. If unset,
224    /// both tombstoned and un-tombstoned rooms are returned. If false, only un-tombstoned rooms
225    /// are returned. If true, only tombstoned rooms are returned.
226    #[serde(skip_serializing_if = "Option::is_none")]
227    pub is_tombstoned: Option<bool>,
228
229    /// Only list rooms of given create-types or all.
230    ///
231    /// If specified, only rooms where the `m.room.create` event has a `type` matching one
232    /// of the strings in this array will be returned. If this field is unset, all rooms are
233    /// returned regardless of type. This can be used to get the initial set of spaces for an
234    /// account.
235    #[serde(default, skip_serializing_if = "Vec::is_empty")]
236    pub room_types: Vec<RoomTypeFilter>,
237
238    /// Only list rooms that are not of these create-types, or all.
239    ///
240    /// Same as "room_types" but inverted. This can be used to filter out spaces from the room
241    /// list.
242    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
243    pub not_room_types: Vec<RoomTypeFilter>,
244
245    /// Only list rooms matching the given string, or all.
246    ///
247    /// Filter the room name. Case-insensitive partial matching e.g 'foo' matches 'abFooab'.
248    /// The term 'like' is inspired by SQL 'LIKE', and the text here is similar to '%foo%'.
249    #[serde(skip_serializing_if = "Option::is_none")]
250    pub room_name_like: Option<String>,
251
252    /// Filter the room based on its room tags.
253    ///
254    /// If multiple tags are present, a room can have
255    /// any one of the listed tags (OR'd).
256    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
257    pub tags: Vec<String>,
258
259    /// Filter the room based on its room tags.
260    ///
261    /// Takes priority over `tags`. For example, a room
262    /// with tags A and B with filters `tags:[A]` `not_tags:[B]` would NOT be included because
263    /// `not_tags` takes priority over `tags`. This filter is useful if your Rooms list does
264    /// NOT include the list of favourite rooms again.
265    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
266    pub not_tags: Vec<String>,
267
268    /// Extensions may add further fields to the filters.
269    #[serde(flatten, default, skip_serializing_if = "BTreeMap::is_empty")]
270    pub extensions: BTreeMap<String, serde_json::Value>,
271}
272
273/// Sliding Sync Request for each list.
274#[derive(Clone, Debug, Default, Serialize, Deserialize)]
275#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
276pub struct SyncRequestList {
277    /// Put this list into the all-rooms-mode.
278    ///
279    /// Settings this to true will inform the server that, no matter how slow
280    /// that might be, the clients wants all rooms the filters apply to. When operating
281    /// in this mode, `ranges` and  `sort` will be ignored  there will be no movement operations
282    /// (`DELETE` followed by `INSERT`) as the client has the entire list and can work out whatever
283    /// sort order they wish. There will still be `DELETE` and `INSERT` operations when rooms are
284    /// left or joined respectively. In addition, there will be an initial `SYNC` operation to let
285    /// the client know which rooms in the rooms object were from this list.
286    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
287    pub slow_get_all_rooms: bool,
288
289    /// The ranges of rooms we're interested in.
290    pub ranges: Vec<(UInt, UInt)>,
291
292    /// The sort ordering applied to this list of rooms. Sticky.
293    #[serde(default, skip_serializing_if = "Vec::is_empty")]
294    pub sort: Vec<String>,
295
296    /// The details to be included per room
297    #[serde(flatten)]
298    pub room_details: RoomDetailsConfig,
299
300    /// If tombstoned rooms should be returned and if so, with what information attached.
301    #[serde(skip_serializing_if = "Option::is_none")]
302    pub include_old_rooms: Option<IncludeOldRooms>,
303
304    /// Request a stripped variant of membership events for the users used to calculate the room
305    /// name.
306    ///
307    /// Sticky.
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub include_heroes: Option<bool>,
310
311    /// Filters to apply to the list before sorting. Sticky.
312    #[serde(skip_serializing_if = "Option::is_none")]
313    pub filters: Option<SyncRequestListFilters>,
314
315    /// An allow-list of event types which should be considered recent activity when sorting
316    /// `by_recency`. By omitting event types from this field, clients can ensure that
317    /// uninteresting events (e.g. a profil rename) do not cause a room to jump to the top of its
318    /// list(s). Empty or omitted `bump_event_types` have no effect; all events in a room will be
319    /// considered recent activity.
320    ///
321    /// NB. Changes to bump_event_types will NOT cause the room list to be reordered;
322    /// it will only affect the ordering of rooms due to future updates.
323    ///
324    /// Sticky.
325    #[serde(default, skip_serializing_if = "Vec::is_empty")]
326    pub bump_event_types: Vec<TimelineEventType>,
327}
328
329/// Configuration for requesting room details.
330#[derive(Clone, Debug, Default, Serialize, Deserialize)]
331#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
332pub struct RoomDetailsConfig {
333    /// Required state for each room returned. An array of event type and state key tuples.
334    ///
335    /// Note that elements of this array are NOT sticky so they must be specified in full when they
336    /// are changed. Sticky.
337    #[serde(default, skip_serializing_if = "Vec::is_empty")]
338    pub required_state: Vec<(StateEventType, String)>,
339
340    /// The maximum number of timeline events to return per room. Sticky.
341    #[serde(skip_serializing_if = "Option::is_none")]
342    pub timeline_limit: Option<UInt>,
343}
344
345/// Configuration for old rooms to include
346#[derive(Clone, Debug, Default, Serialize, Deserialize)]
347#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
348pub struct IncludeOldRooms {
349    /// Required state for each room returned. An array of event type and state key tuples.
350    ///
351    /// Note that elements of this array are NOT sticky so they must be specified in full when they
352    /// are changed. Sticky.
353    #[serde(default, skip_serializing_if = "Vec::is_empty")]
354    pub required_state: Vec<(StateEventType, String)>,
355
356    /// The maximum number of timeline events to return per room. Sticky.
357    #[serde(skip_serializing_if = "Option::is_none")]
358    pub timeline_limit: Option<UInt>,
359}
360
361/// Configuration for room subscription
362#[derive(Clone, Debug, Default, Serialize, Deserialize)]
363#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
364pub struct RoomSubscription {
365    /// Required state for each room returned. An array of event type and state key tuples.
366    ///
367    /// Note that elements of this array are NOT sticky so they must be specified in full when they
368    /// are changed. Sticky.
369    #[serde(default, skip_serializing_if = "Vec::is_empty")]
370    pub required_state: Vec<(StateEventType, String)>,
371
372    /// The maximum number of timeline events to return per room. Sticky.
373    #[serde(skip_serializing_if = "Option::is_none")]
374    pub timeline_limit: Option<UInt>,
375
376    /// Include the room heroes. Sticky.
377    #[serde(skip_serializing_if = "Option::is_none")]
378    pub include_heroes: Option<bool>,
379}
380
381/// Operation applied to the specific SlidingSyncList
382#[derive(Clone, Debug, Deserialize, Serialize)]
383#[serde(rename_all = "UPPERCASE")]
384#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
385pub enum SlidingOp {
386    /// Full reset of the given window.
387    Sync,
388    /// Insert an item at the given point, moves all following entry by
389    /// one to the next Empty or Invalid field.
390    Insert,
391    /// Drop this entry, moves all following entry up by one.
392    Delete,
393    /// Mark these as invaldiated.
394    Invalidate,
395}
396
397/// Updates to joined rooms.
398#[derive(Clone, Debug, Default, Deserialize, Serialize)]
399#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
400pub struct SyncList {
401    /// The sync operation to apply, if any.
402    #[serde(default, skip_serializing_if = "Vec::is_empty")]
403    pub ops: Vec<SyncOp>,
404
405    /// The total number of rooms found for this filter.
406    pub count: UInt,
407}
408
409/// Updates to joined rooms.
410#[derive(Clone, Debug, Deserialize, Serialize)]
411#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
412pub struct SyncOp {
413    /// The sync operation to apply.
414    pub op: SlidingOp,
415
416    /// The range this list update applies to.
417    pub range: Option<(UInt, UInt)>,
418
419    /// Or the specific index the update applies to.
420    pub index: Option<UInt>,
421
422    /// The list of room_ids updates to apply.
423    #[serde(default, skip_serializing_if = "Vec::is_empty")]
424    pub room_ids: Vec<OwnedRoomId>,
425
426    /// On insert and delete we are only receiving exactly one room_id.
427    pub room_id: Option<OwnedRoomId>,
428}
429
430/// Updates to joined rooms.
431#[derive(Clone, Debug, Default, Deserialize, Serialize)]
432#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
433pub struct SlidingSyncRoom {
434    /// The name of the room as calculated by the server.
435    #[serde(skip_serializing_if = "Option::is_none")]
436    pub name: Option<String>,
437
438    /// The avatar of the room.
439    #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
440    pub avatar: JsOption<OwnedMxcUri>,
441
442    /// Was this an initial response.
443    #[serde(skip_serializing_if = "Option::is_none")]
444    pub initial: Option<bool>,
445
446    /// This is a direct message.
447    #[serde(skip_serializing_if = "Option::is_none")]
448    pub is_dm: Option<bool>,
449
450    /// If this is `Some(_)`, this is a not-yet-accepted invite containing the given stripped state
451    /// events.
452    #[serde(skip_serializing_if = "Option::is_none")]
453    pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
454
455    /// Counts of unread notifications for this room.
456    #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
457    pub unread_notifications: UnreadNotificationsCount,
458
459    /// The timeline of messages and state changes in the room.
460    #[serde(default, skip_serializing_if = "Vec::is_empty")]
461    pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
462
463    /// Updates to the state at the beginning of the `timeline`.
464    /// A list of state events.
465    #[serde(default, skip_serializing_if = "Vec::is_empty")]
466    pub required_state: Vec<Raw<AnySyncStateEvent>>,
467
468    /// The prev_batch allowing you to paginate through the messages before the given ones.
469    #[serde(skip_serializing_if = "Option::is_none")]
470    pub prev_batch: Option<String>,
471
472    /// True if the number of events returned was limited by the limit on the filter.
473    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
474    pub limited: bool,
475
476    /// The number of users with membership of `join`, including the client’s own user ID.
477    #[serde(skip_serializing_if = "Option::is_none")]
478    pub joined_count: Option<UInt>,
479
480    /// The number of users with membership of `invite`.
481    #[serde(skip_serializing_if = "Option::is_none")]
482    pub invited_count: Option<UInt>,
483
484    /// The number of timeline events which have just occurred and are not historical.
485    #[serde(skip_serializing_if = "Option::is_none")]
486    pub num_live: Option<UInt>,
487
488    /// The timestamp of the room.
489    ///
490    /// It's not to be confused with `origin_server_ts` of the latest event in the
491    /// timeline. `bump_event_types` might "ignore” some events when computing the
492    /// timestamp of the room. Thus, using this `timestamp` value is more accurate than
493    /// relying on the latest event.
494    #[serde(skip_serializing_if = "Option::is_none")]
495    pub timestamp: Option<MilliSecondsSinceUnixEpoch>,
496
497    /// Heroes of the room, if requested by a room subscription.
498    #[serde(skip_serializing_if = "Option::is_none")]
499    pub heroes: Option<Vec<SlidingSyncRoomHero>>,
500}
501
502impl SlidingSyncRoom {
503    /// Creates an empty `Room`.
504    pub fn new() -> Self {
505        Default::default()
506    }
507}
508
509/// A sliding sync room hero.
510#[derive(Clone, Debug, Deserialize, Serialize)]
511#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
512pub struct SlidingSyncRoomHero {
513    /// The user ID of the hero.
514    pub user_id: OwnedUserId,
515
516    /// The name of the hero.
517    #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
518    pub name: Option<String>,
519
520    /// The avatar of the hero.
521    #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
522    pub avatar: Option<OwnedMxcUri>,
523}
524
525impl SlidingSyncRoomHero {
526    /// Creates a new `SlidingSyncRoomHero` with the given user id.
527    pub fn new(user_id: OwnedUserId) -> Self {
528        Self { user_id, name: None, avatar: None }
529    }
530}
531
532/// Sliding-Sync extension configuration.
533#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
534#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
535pub struct ExtensionsConfig {
536    /// Request to devices messages with the given config.
537    #[serde(default, skip_serializing_if = "ToDeviceConfig::is_empty")]
538    pub to_device: ToDeviceConfig,
539
540    /// Configure the end-to-end-encryption extension.
541    #[serde(default, skip_serializing_if = "E2EEConfig::is_empty")]
542    pub e2ee: E2EEConfig,
543
544    /// Configure the account data extension.
545    #[serde(default, skip_serializing_if = "AccountDataConfig::is_empty")]
546    pub account_data: AccountDataConfig,
547
548    /// Request to receipt information with the given config.
549    #[serde(default, skip_serializing_if = "ReceiptsConfig::is_empty")]
550    pub receipts: ReceiptsConfig,
551
552    /// Request to typing information with the given config.
553    #[serde(default, skip_serializing_if = "TypingConfig::is_empty")]
554    pub typing: TypingConfig,
555
556    /// Extensions may add further fields to the list.
557    #[serde(flatten)]
558    other: BTreeMap<String, serde_json::Value>,
559}
560
561impl ExtensionsConfig {
562    /// Whether all fields are empty or `None`.
563    pub fn is_empty(&self) -> bool {
564        self.to_device.is_empty()
565            && self.e2ee.is_empty()
566            && self.account_data.is_empty()
567            && self.receipts.is_empty()
568            && self.typing.is_empty()
569            && self.other.is_empty()
570    }
571}
572
573/// Extensions specific response data.
574#[derive(Clone, Debug, Default, Serialize, Deserialize)]
575#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
576pub struct Extensions {
577    /// To-device extension in response.
578    #[serde(skip_serializing_if = "Option::is_none")]
579    pub to_device: Option<ToDevice>,
580
581    /// E2EE extension in response.
582    #[serde(default, skip_serializing_if = "E2EE::is_empty")]
583    pub e2ee: E2EE,
584
585    /// Account data extension in response.
586    #[serde(default, skip_serializing_if = "AccountData::is_empty")]
587    pub account_data: AccountData,
588
589    /// Receipt data extension in response.
590    #[serde(default, skip_serializing_if = "Receipts::is_empty")]
591    pub receipts: Receipts,
592
593    /// Typing data extension in response.
594    #[serde(default, skip_serializing_if = "Typing::is_empty")]
595    pub typing: Typing,
596}
597
598impl Extensions {
599    /// Whether the extension data is empty.
600    ///
601    /// True if neither to-device, e2ee nor account data are to be found.
602    pub fn is_empty(&self) -> bool {
603        self.to_device.is_none()
604            && self.e2ee.is_empty()
605            && self.account_data.is_empty()
606            && self.receipts.is_empty()
607            && self.typing.is_empty()
608    }
609}
610
611/// To-device messages extension configuration.
612///
613/// According to [MSC3885](https://github.com/matrix-org/matrix-spec-proposals/pull/3885).
614#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
615#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
616pub struct ToDeviceConfig {
617    /// Activate or deactivate this extension. Sticky.
618    #[serde(skip_serializing_if = "Option::is_none")]
619    pub enabled: Option<bool>,
620
621    /// Max number of to-device messages per response.
622    #[serde(skip_serializing_if = "Option::is_none")]
623    pub limit: Option<UInt>,
624
625    /// Give messages since this token only.
626    #[serde(skip_serializing_if = "Option::is_none")]
627    pub since: Option<String>,
628
629    /// List of list names for which to-device events should be enabled.
630    ///
631    /// If not defined, will be enabled for *all* the lists appearing in the request.
632    /// If defined and empty, will be disabled for all the lists.
633    ///
634    /// Sticky.
635    #[serde(skip_serializing_if = "Option::is_none")]
636    pub lists: Option<Vec<String>>,
637
638    /// List of room names for which to-device events should be enabled.
639    ///
640    /// If not defined, will be enabled for *all* the rooms appearing in the `room_subscriptions`.
641    /// If defined and empty, will be disabled for all the rooms.
642    ///
643    /// Sticky.
644    #[serde(skip_serializing_if = "Option::is_none")]
645    pub rooms: Option<Vec<OwnedRoomId>>,
646}
647
648impl ToDeviceConfig {
649    /// Whether all fields are empty or `None`.
650    pub fn is_empty(&self) -> bool {
651        self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
652    }
653}
654
655/// To-device messages extension response.
656///
657/// According to [MSC3885](https://github.com/matrix-org/matrix-spec-proposals/pull/3885).
658#[derive(Clone, Debug, Default, Serialize, Deserialize)]
659#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
660pub struct ToDevice {
661    /// Fetch the next batch from this entry.
662    pub next_batch: String,
663
664    /// The to-device Events.
665    #[serde(default, skip_serializing_if = "Vec::is_empty")]
666    pub events: Vec<Raw<AnyToDeviceEvent>>,
667}
668
669/// E2EE extension configuration.
670///
671/// According to [MSC3884](https://github.com/matrix-org/matrix-spec-proposals/pull/3884).
672#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
673#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
674pub struct E2EEConfig {
675    /// Activate or deactivate this extension. Sticky.
676    #[serde(skip_serializing_if = "Option::is_none")]
677    pub enabled: Option<bool>,
678}
679
680impl E2EEConfig {
681    /// Whether all fields are empty or `None`.
682    pub fn is_empty(&self) -> bool {
683        self.enabled.is_none()
684    }
685}
686
687/// E2EE extension response data.
688///
689/// According to [MSC3884](https://github.com/matrix-org/matrix-spec-proposals/pull/3884).
690#[derive(Clone, Debug, Default, Serialize, Deserialize)]
691#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
692pub struct E2EE {
693    /// Information on E2EE device updates.
694    ///
695    /// Only present on an incremental sync.
696    #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
697    pub device_lists: DeviceLists,
698
699    /// For each key algorithm, the number of unclaimed one-time keys
700    /// currently held on the server for a device.
701    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
702    pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
703
704    /// The unused fallback key algorithms.
705    ///
706    /// The presence of this field indicates that the server supports
707    /// fallback keys.
708    #[serde(skip_serializing_if = "Option::is_none")]
709    pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
710}
711
712impl E2EE {
713    /// Whether all fields are empty or `None`.
714    pub fn is_empty(&self) -> bool {
715        self.device_lists.is_empty()
716            && self.device_one_time_keys_count.is_empty()
717            && self.device_unused_fallback_key_types.is_none()
718    }
719}
720
721/// Account-data extension configuration.
722///
723/// Not yet part of the spec proposal. Taken from the reference implementation
724/// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/account_data.go>
725#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
726#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
727pub struct AccountDataConfig {
728    /// Activate or deactivate this extension. Sticky.
729    #[serde(skip_serializing_if = "Option::is_none")]
730    pub enabled: Option<bool>,
731
732    /// List of list names for which account data should be enabled.
733    ///
734    /// This is specific to room account data (e.g. user-defined room tags).
735    ///
736    /// If not defined, will be enabled for *all* the lists appearing in the request.
737    /// If defined and empty, will be disabled for all the lists.
738    ///
739    /// Sticky.
740    #[serde(skip_serializing_if = "Option::is_none")]
741    pub lists: Option<Vec<String>>,
742
743    /// List of room names for which account data should be enabled.
744    ///
745    /// This is specific to room account data (e.g. user-defined room tags).
746    ///
747    /// If not defined, will be enabled for *all* the rooms appearing in the `room_subscriptions`.
748    /// If defined and empty, will be disabled for all the rooms.
749    ///
750    /// Sticky.
751    #[serde(skip_serializing_if = "Option::is_none")]
752    pub rooms: Option<Vec<OwnedRoomId>>,
753}
754
755impl AccountDataConfig {
756    /// Whether all fields are empty or `None`.
757    pub fn is_empty(&self) -> bool {
758        self.enabled.is_none()
759    }
760}
761
762/// Account-data extension response data.
763///
764/// Not yet part of the spec proposal. Taken from the reference implementation
765/// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/account_data.go>
766#[derive(Clone, Debug, Default, Serialize, Deserialize)]
767#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
768pub struct AccountData {
769    /// The global private data created by this user.
770    #[serde(default, skip_serializing_if = "Vec::is_empty")]
771    pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
772
773    /// The private data that this user has attached to each room.
774    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
775    pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
776}
777
778impl AccountData {
779    /// Whether all fields are empty or `None`.
780    pub fn is_empty(&self) -> bool {
781        self.global.is_empty() && self.rooms.is_empty()
782    }
783}
784
785/// Single entry for a room-related read receipt configuration in `ReceiptsConfig`.
786#[derive(Clone, Debug, PartialEq)]
787#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
788pub enum RoomReceiptConfig {
789    /// Get read receipts for all the subscribed rooms.
790    AllSubscribed,
791    /// Get read receipts for this particular room.
792    Room(OwnedRoomId),
793}
794
795impl Serialize for RoomReceiptConfig {
796    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
797    where
798        S: serde::Serializer,
799    {
800        match self {
801            RoomReceiptConfig::AllSubscribed => serializer.serialize_str("*"),
802            RoomReceiptConfig::Room(r) => r.serialize(serializer),
803        }
804    }
805}
806
807impl<'de> Deserialize<'de> for RoomReceiptConfig {
808    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
809    where
810        D: serde::de::Deserializer<'de>,
811    {
812        match deserialize_cow_str(deserializer)?.as_ref() {
813            "*" => Ok(RoomReceiptConfig::AllSubscribed),
814            other => Ok(RoomReceiptConfig::Room(
815                RoomId::parse(other).map_err(D::Error::custom)?.to_owned(),
816            )),
817        }
818    }
819}
820
821/// Receipt extension configuration.
822///
823/// According to [MSC3960](https://github.com/matrix-org/matrix-spec-proposals/pull/3960)
824#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
825#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
826pub struct ReceiptsConfig {
827    /// Activate or deactivate this extension. Sticky.
828    #[serde(skip_serializing_if = "Option::is_none")]
829    pub enabled: Option<bool>,
830
831    /// List of list names for which receipts should be enabled.
832    ///
833    /// If not defined, will be enabled for *all* the lists appearing in the request.
834    /// If defined and empty, will be disabled for all the lists.
835    ///
836    /// Sticky.
837    #[serde(skip_serializing_if = "Option::is_none")]
838    pub lists: Option<Vec<String>>,
839
840    /// List of room names for which receipts should be enabled.
841    ///
842    /// If not defined, will be enabled for *all* the rooms appearing in the `room_subscriptions`.
843    /// If defined and empty, will be disabled for all the rooms.
844    ///
845    /// Sticky.
846    #[serde(skip_serializing_if = "Option::is_none")]
847    pub rooms: Option<Vec<RoomReceiptConfig>>,
848}
849
850impl ReceiptsConfig {
851    /// Whether all fields are empty or `None`.
852    pub fn is_empty(&self) -> bool {
853        self.enabled.is_none()
854    }
855}
856
857/// Receipt extension response data.
858///
859/// According to [MSC3960](https://github.com/matrix-org/matrix-spec-proposals/pull/3960)
860#[derive(Clone, Debug, Default, Serialize, Deserialize)]
861#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
862pub struct Receipts {
863    /// The ephemeral receipt room event for each room
864    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
865    pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
866}
867
868impl Receipts {
869    /// Whether all fields are empty or `None`.
870    pub fn is_empty(&self) -> bool {
871        self.rooms.is_empty()
872    }
873}
874
875/// Typing extension configuration.
876///
877/// Not yet part of the spec proposal. Taken from the reference implementation
878/// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/typing.go>
879#[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
880#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
881pub struct TypingConfig {
882    /// Activate or deactivate this extension. Sticky.
883    #[serde(skip_serializing_if = "Option::is_none")]
884    pub enabled: Option<bool>,
885
886    /// List of list names for which typing notifications should be enabled.
887    ///
888    /// If not defined, will be enabled for *all* the lists appearing in the request.
889    /// If defined and empty, will be disabled for all the lists.
890    ///
891    /// Sticky.
892    #[serde(skip_serializing_if = "Option::is_none")]
893    pub lists: Option<Vec<String>>,
894
895    /// List of room names for which typing notifications should be enabled.
896    ///
897    /// If not defined, will be enabled for *all* the rooms appearing in the `room_subscriptions`.
898    /// If defined and empty, will be disabled for all the rooms.
899    ///
900    /// Sticky.
901    #[serde(skip_serializing_if = "Option::is_none")]
902    pub rooms: Option<Vec<OwnedRoomId>>,
903}
904
905impl TypingConfig {
906    /// Whether all fields are empty or `None`.
907    pub fn is_empty(&self) -> bool {
908        self.enabled.is_none()
909    }
910}
911
912/// Typing extension response data.
913///
914/// Not yet part of the spec proposal. Taken from the reference implementation
915/// <https://github.com/matrix-org/sliding-sync/blob/main/sync3/extensions/typing.go>
916#[derive(Clone, Debug, Default, Serialize, Deserialize)]
917#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
918pub struct Typing {
919    /// The ephemeral typing event for each room
920    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
921    pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
922}
923
924impl Typing {
925    /// Whether all fields are empty or `None`.
926    pub fn is_empty(&self) -> bool {
927        self.rooms.is_empty()
928    }
929}
930
931#[cfg(feature = "unstable-msc4186")]
932impl From<v5::Request> for Request {
933    fn from(value: v5::Request) -> Self {
934        Self {
935            pos: value.pos,
936            conn_id: value.conn_id,
937            txn_id: value.txn_id,
938            timeout: value.timeout,
939            lists: value
940                .lists
941                .into_iter()
942                .map(|(list_name, list)| (list_name, list.into()))
943                .collect(),
944            room_subscriptions: value
945                .room_subscriptions
946                .into_iter()
947                .map(|(room_id, room_subscription)| (room_id, room_subscription.into()))
948                .collect(),
949            extensions: value.extensions.into(),
950
951            ..Default::default()
952        }
953    }
954}
955
956#[cfg(feature = "unstable-msc4186")]
957impl From<v5::request::List> for SyncRequestList {
958    fn from(value: v5::request::List) -> Self {
959        Self {
960            ranges: value.ranges,
961            room_details: value.room_details.into(),
962            include_heroes: value.include_heroes,
963            filters: value.filters.map(Into::into),
964
965            // Defaults from MSC4186.
966            sort: vec!["by_recency".to_owned(), "by_name".to_owned()],
967            bump_event_types: vec![
968                TimelineEventType::RoomMessage,
969                TimelineEventType::RoomEncrypted,
970                TimelineEventType::RoomCreate,
971                TimelineEventType::Sticker,
972            ],
973
974            ..Default::default()
975        }
976    }
977}
978
979#[cfg(feature = "unstable-msc4186")]
980impl From<v5::request::RoomDetails> for RoomDetailsConfig {
981    fn from(value: v5::request::RoomDetails) -> Self {
982        Self { required_state: value.required_state, timeline_limit: Some(value.timeline_limit) }
983    }
984}
985
986#[cfg(feature = "unstable-msc4186")]
987impl From<v5::request::ListFilters> for SyncRequestListFilters {
988    fn from(value: v5::request::ListFilters) -> Self {
989        Self {
990            is_invite: value.is_invite,
991            not_room_types: value.not_room_types,
992            ..Default::default()
993        }
994    }
995}
996
997#[cfg(feature = "unstable-msc4186")]
998impl From<v5::request::RoomSubscription> for RoomSubscription {
999    fn from(value: v5::request::RoomSubscription) -> Self {
1000        Self {
1001            required_state: value.required_state,
1002            timeline_limit: Some(value.timeline_limit),
1003            include_heroes: value.include_heroes,
1004        }
1005    }
1006}
1007
1008#[cfg(feature = "unstable-msc4186")]
1009impl From<v5::request::Extensions> for ExtensionsConfig {
1010    fn from(value: v5::request::Extensions) -> Self {
1011        Self {
1012            to_device: value.to_device.into(),
1013            e2ee: value.e2ee.into(),
1014            account_data: value.account_data.into(),
1015            receipts: value.receipts.into(),
1016            typing: value.typing.into(),
1017
1018            ..Default::default()
1019        }
1020    }
1021}
1022
1023#[cfg(feature = "unstable-msc4186")]
1024impl From<v5::request::ToDevice> for ToDeviceConfig {
1025    fn from(value: v5::request::ToDevice) -> Self {
1026        Self {
1027            enabled: value.enabled,
1028            limit: value.limit,
1029            since: value.since,
1030            lists: value.lists,
1031            rooms: value.rooms,
1032        }
1033    }
1034}
1035
1036#[cfg(feature = "unstable-msc4186")]
1037impl From<v5::request::E2EE> for E2EEConfig {
1038    fn from(value: v5::request::E2EE) -> Self {
1039        Self { enabled: value.enabled }
1040    }
1041}
1042
1043#[cfg(feature = "unstable-msc4186")]
1044impl From<v5::request::AccountData> for AccountDataConfig {
1045    fn from(value: v5::request::AccountData) -> Self {
1046        Self { enabled: value.enabled, lists: value.lists, rooms: value.rooms }
1047    }
1048}
1049
1050#[cfg(feature = "unstable-msc4186")]
1051impl From<v5::request::Receipts> for ReceiptsConfig {
1052    fn from(value: v5::request::Receipts) -> Self {
1053        Self {
1054            enabled: value.enabled,
1055            lists: value.lists,
1056            rooms: value.rooms.map(|rooms| rooms.into_iter().map(Into::into).collect()),
1057        }
1058    }
1059}
1060
1061#[cfg(feature = "unstable-msc4186")]
1062impl From<v5::request::ReceiptsRoom> for RoomReceiptConfig {
1063    fn from(value: v5::request::ReceiptsRoom) -> Self {
1064        match value {
1065            v5::request::ReceiptsRoom::Room(room_id) => Self::Room(room_id),
1066            _ => Self::AllSubscribed,
1067        }
1068    }
1069}
1070
1071#[cfg(feature = "unstable-msc4186")]
1072impl From<v5::request::Typing> for TypingConfig {
1073    fn from(value: v5::request::Typing) -> Self {
1074        Self { enabled: value.enabled, lists: value.lists, rooms: value.rooms }
1075    }
1076}
1077
1078#[cfg(test)]
1079mod tests {
1080    use ruma_common::owned_room_id;
1081
1082    use crate::sync::sync_events::v4::RoomReceiptConfig;
1083
1084    #[test]
1085    fn serialize_room_receipt_config() {
1086        let entry = RoomReceiptConfig::AllSubscribed;
1087        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
1088
1089        let entry = RoomReceiptConfig::Room(owned_room_id!("!n8f893n9:example.com"));
1090        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!n8f893n9:example.com""#);
1091    }
1092
1093    #[test]
1094    fn deserialize_room_receipt_config() {
1095        assert_eq!(
1096            serde_json::from_str::<RoomReceiptConfig>(r#""*""#).unwrap(),
1097            RoomReceiptConfig::AllSubscribed
1098        );
1099
1100        assert_eq!(
1101            serde_json::from_str::<RoomReceiptConfig>(r#""!n8f893n9:example.com""#).unwrap(),
1102            RoomReceiptConfig::Room(owned_room_id!("!n8f893n9:example.com"))
1103        );
1104    }
1105}