ruma_client_api/sync/sync_events/
v3.rs

1//! `/v3/` ([spec])
2//!
3//! [spec]: https://spec.matrix.org/latest/client-server-api/#get_matrixclientv3sync
4
5use std::{collections::BTreeMap, time::Duration};
6
7use as_variant::as_variant;
8use js_int::UInt;
9use ruma_common::{
10    api::{request, response, Metadata},
11    metadata,
12    presence::PresenceState,
13    serde::Raw,
14    OneTimeKeyAlgorithm, OwnedEventId, OwnedRoomId, OwnedUserId,
15};
16use ruma_events::{
17    presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
18    AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyToDeviceEvent,
19};
20use serde::{Deserialize, Serialize};
21
22mod response_serde;
23
24use super::{DeviceLists, StrippedState, UnreadNotificationsCount};
25use crate::filter::FilterDefinition;
26
27const METADATA: Metadata = metadata! {
28    method: GET,
29    rate_limited: false,
30    authentication: AccessToken,
31    history: {
32        1.0 => "/_matrix/client/r0/sync",
33        1.1 => "/_matrix/client/v3/sync",
34    }
35};
36
37/// Request type for the `sync` endpoint.
38#[request(error = crate::Error)]
39#[derive(Default)]
40pub struct Request {
41    /// A filter represented either as its full JSON definition or the ID of a saved filter.
42    #[serde(skip_serializing_if = "Option::is_none")]
43    #[ruma_api(query)]
44    pub filter: Option<Filter>,
45
46    /// A point in time to continue a sync from.
47    ///
48    /// Should be a token from the `next_batch` field of a previous `/sync`
49    /// request.
50    #[serde(skip_serializing_if = "Option::is_none")]
51    #[ruma_api(query)]
52    pub since: Option<String>,
53
54    /// Controls whether to include the full state for all rooms the user is a member of.
55    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
56    #[ruma_api(query)]
57    pub full_state: bool,
58
59    /// Controls whether the client is automatically marked as online by polling this API.
60    ///
61    /// Defaults to `PresenceState::Online`.
62    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
63    #[ruma_api(query)]
64    pub set_presence: PresenceState,
65
66    /// The maximum time to poll in milliseconds before returning this request.
67    #[serde(
68        with = "ruma_common::serde::duration::opt_ms",
69        default,
70        skip_serializing_if = "Option::is_none"
71    )]
72    #[ruma_api(query)]
73    pub timeout: Option<Duration>,
74
75    /// Controls whether to receive state changes between the previous sync and the **start** of
76    /// the timeline, or between the previous sync and the **end** of the timeline.
77    #[cfg(feature = "unstable-msc4222")]
78    #[serde(
79        default,
80        skip_serializing_if = "ruma_common::serde::is_default",
81        rename = "org.matrix.msc4222.use_state_after"
82    )]
83    #[ruma_api(query)]
84    pub use_state_after: bool,
85}
86
87/// Response type for the `sync` endpoint.
88#[response(error = crate::Error)]
89pub struct Response {
90    /// The batch token to supply in the `since` param of the next `/sync` request.
91    pub next_batch: String,
92
93    /// Updates to rooms.
94    #[serde(default, skip_serializing_if = "Rooms::is_empty")]
95    pub rooms: Rooms,
96
97    /// Updates to the presence status of other users.
98    #[serde(default, skip_serializing_if = "Presence::is_empty")]
99    pub presence: Presence,
100
101    /// The global private data created by this user.
102    #[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
103    pub account_data: GlobalAccountData,
104
105    /// Messages sent directly between devices.
106    #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
107    pub to_device: ToDevice,
108
109    /// Information on E2E device updates.
110    ///
111    /// Only present on an incremental sync.
112    #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
113    pub device_lists: DeviceLists,
114
115    /// For each key algorithm, the number of unclaimed one-time keys
116    /// currently held on the server for a device.
117    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
118    pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
119
120    /// The unused fallback key algorithms.
121    ///
122    /// The presence of this field indicates that the server supports
123    /// fallback keys.
124    #[serde(skip_serializing_if = "Option::is_none")]
125    pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
126}
127
128impl Request {
129    /// Creates an empty `Request`.
130    pub fn new() -> Self {
131        Default::default()
132    }
133}
134
135impl Response {
136    /// Creates a new `Response` with the given batch token.
137    pub fn new(next_batch: String) -> Self {
138        Self {
139            next_batch,
140            rooms: Default::default(),
141            presence: Default::default(),
142            account_data: Default::default(),
143            to_device: Default::default(),
144            device_lists: Default::default(),
145            device_one_time_keys_count: BTreeMap::new(),
146            device_unused_fallback_key_types: None,
147        }
148    }
149}
150
151/// A filter represented either as its full JSON definition or the ID of a saved filter.
152#[derive(Clone, Debug, Deserialize, Serialize)]
153#[allow(clippy::large_enum_variant)]
154#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
155#[serde(untagged)]
156pub enum Filter {
157    // The filter definition needs to be (de)serialized twice because it is a URL-encoded JSON
158    // string. Since #[ruma_api(query)] only does the latter and this is a very uncommon
159    // setup, we implement it through custom serde logic for this specific enum variant rather
160    // than adding another ruma_api attribute.
161    //
162    // On the deserialization side, because this is an enum with #[serde(untagged)], serde
163    // will try the variants in order (https://serde.rs/enum-representations.html). That means because
164    // FilterDefinition is the first variant, JSON decoding is attempted first which is almost
165    // functionally equivalent to looking at whether the first symbol is a '{' as the spec
166    // says. (there are probably some corner cases like leading whitespace)
167    /// A complete filter definition serialized to JSON.
168    #[serde(with = "ruma_common::serde::json_string")]
169    FilterDefinition(FilterDefinition),
170
171    /// The ID of a filter saved on the server.
172    FilterId(String),
173}
174
175impl From<FilterDefinition> for Filter {
176    fn from(def: FilterDefinition) -> Self {
177        Self::FilterDefinition(def)
178    }
179}
180
181impl From<String> for Filter {
182    fn from(id: String) -> Self {
183        Self::FilterId(id)
184    }
185}
186
187/// Updates to rooms.
188#[derive(Clone, Debug, Default, Deserialize, Serialize)]
189#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
190pub struct Rooms {
191    /// The rooms that the user has left or been banned from.
192    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
193    pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
194
195    /// The rooms that the user has joined.
196    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
197    pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
198
199    /// The rooms that the user has been invited to.
200    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
201    pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
202
203    /// The rooms that the user has knocked on.
204    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
205    pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
206}
207
208impl Rooms {
209    /// Creates an empty `Rooms`.
210    pub fn new() -> Self {
211        Default::default()
212    }
213
214    /// Returns true if there is no update in any room.
215    pub fn is_empty(&self) -> bool {
216        self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
217    }
218}
219
220/// Historical updates to left rooms.
221#[derive(Clone, Debug, Default, Serialize)]
222#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
223pub struct LeftRoom {
224    /// The timeline of messages and state changes in the room up to the point when the user
225    /// left.
226    #[serde(skip_serializing_if = "Timeline::is_empty")]
227    pub timeline: Timeline,
228
229    /// The state updates for the room up to the start of the timeline.
230    #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
231    pub state: State,
232
233    /// The private data that this user has attached to this room.
234    #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
235    pub account_data: RoomAccountData,
236}
237
238impl LeftRoom {
239    /// Creates an empty `LeftRoom`.
240    pub fn new() -> Self {
241        Default::default()
242    }
243
244    /// Returns true if there are updates in the room.
245    pub fn is_empty(&self) -> bool {
246        self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
247    }
248}
249
250/// Updates to joined rooms.
251#[derive(Clone, Debug, Default, Serialize)]
252#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
253pub struct JoinedRoom {
254    /// Information about the room which clients may need to correctly render it
255    /// to users.
256    #[serde(skip_serializing_if = "RoomSummary::is_empty")]
257    pub summary: RoomSummary,
258
259    /// Counts of [unread notifications] for this room.
260    ///
261    /// If `unread_thread_notifications` was set to `true` in the [`RoomEventFilter`], these
262    /// include only the unread notifications for the main timeline.
263    ///
264    /// [unread notifications]: https://spec.matrix.org/latest/client-server-api/#receiving-notifications
265    /// [`RoomEventFilter`]: crate::filter::RoomEventFilter
266    #[serde(skip_serializing_if = "UnreadNotificationsCount::is_empty")]
267    pub unread_notifications: UnreadNotificationsCount,
268
269    /// Counts of [unread notifications] for threads in this room.
270    ///
271    /// This is a map from thread root ID to unread notifications in the thread.
272    ///
273    /// Only set if `unread_thread_notifications` was set to `true` in the [`RoomEventFilter`].
274    ///
275    /// [unread notifications]: https://spec.matrix.org/latest/client-server-api/#receiving-notifications
276    /// [`RoomEventFilter`]: crate::filter::RoomEventFilter
277    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
278    pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
279
280    /// The timeline of messages and state changes in the room.
281    #[serde(skip_serializing_if = "Timeline::is_empty")]
282    pub timeline: Timeline,
283
284    /// Updates to the state, between the time indicated by the `since` parameter, and the
285    /// start of the `timeline` (or all state up to the start of the `timeline`, if
286    /// `since` is not given, or `full_state` is true).
287    #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
288    pub state: State,
289
290    /// The private data that this user has attached to this room.
291    #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
292    pub account_data: RoomAccountData,
293
294    /// The ephemeral events in the room that aren't recorded in the timeline or state of the
295    /// room.
296    #[serde(skip_serializing_if = "Ephemeral::is_empty")]
297    pub ephemeral: Ephemeral,
298
299    /// The number of unread events since the latest read receipt.
300    ///
301    /// This uses the unstable prefix in [MSC2654].
302    ///
303    /// [MSC2654]: https://github.com/matrix-org/matrix-spec-proposals/pull/2654
304    #[cfg(feature = "unstable-msc2654")]
305    #[serde(rename = "org.matrix.msc2654.unread_count", skip_serializing_if = "Option::is_none")]
306    pub unread_count: Option<UInt>,
307}
308
309impl JoinedRoom {
310    /// Creates an empty `JoinedRoom`.
311    pub fn new() -> Self {
312        Default::default()
313    }
314
315    /// Returns true if there are no updates in the room.
316    pub fn is_empty(&self) -> bool {
317        let is_empty = self.summary.is_empty()
318            && self.unread_notifications.is_empty()
319            && self.unread_thread_notifications.is_empty()
320            && self.timeline.is_empty()
321            && self.state.is_empty()
322            && self.account_data.is_empty()
323            && self.ephemeral.is_empty();
324
325        #[cfg(not(feature = "unstable-msc2654"))]
326        return is_empty;
327
328        #[cfg(feature = "unstable-msc2654")]
329        return is_empty && self.unread_count.is_none();
330    }
331}
332
333/// Updates to a room that the user has knocked upon.
334#[derive(Clone, Debug, Default, Deserialize, Serialize)]
335#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
336pub struct KnockedRoom {
337    /// Updates to the stripped state of the room.
338    #[serde(default, skip_serializing_if = "KnockState::is_empty")]
339    pub knock_state: KnockState,
340}
341
342impl KnockedRoom {
343    /// Creates an empty `KnockedRoom`.
344    pub fn new() -> Self {
345        Default::default()
346    }
347
348    /// Whether there are updates for this room.
349    pub fn is_empty(&self) -> bool {
350        self.knock_state.is_empty()
351    }
352}
353
354impl From<KnockState> for KnockedRoom {
355    fn from(knock_state: KnockState) -> Self {
356        KnockedRoom { knock_state, ..Default::default() }
357    }
358}
359
360/// Stripped state updates of a room that the user has knocked upon.
361#[derive(Clone, Debug, Default, Deserialize, Serialize)]
362#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
363pub struct KnockState {
364    /// The stripped state of a room that the user has knocked upon.
365    #[serde(default, skip_serializing_if = "Vec::is_empty")]
366    pub events: Vec<Raw<StrippedState>>,
367}
368
369impl KnockState {
370    /// Creates an empty `KnockState`.
371    pub fn new() -> Self {
372        Default::default()
373    }
374
375    /// Whether there are stripped state updates in this room.
376    pub fn is_empty(&self) -> bool {
377        self.events.is_empty()
378    }
379}
380
381/// Events in the room.
382#[derive(Clone, Debug, Default, Deserialize, Serialize)]
383#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
384pub struct Timeline {
385    /// True if the number of events returned was limited by the `limit` on the filter.
386    ///
387    /// Default to `false`.
388    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
389    pub limited: bool,
390
391    /// A token that can be supplied to to the `from` parameter of the
392    /// `/rooms/{roomId}/messages` endpoint.
393    #[serde(skip_serializing_if = "Option::is_none")]
394    pub prev_batch: Option<String>,
395
396    /// A list of events.
397    pub events: Vec<Raw<AnySyncTimelineEvent>>,
398}
399
400impl Timeline {
401    /// Creates an empty `Timeline`.
402    pub fn new() -> Self {
403        Default::default()
404    }
405
406    /// Returns true if there are no timeline updates.
407    ///
408    /// A `Timeline` is considered non-empty if it has at least one event, a
409    /// `prev_batch` value, or `limited` is `true`.
410    pub fn is_empty(&self) -> bool {
411        !self.limited && self.prev_batch.is_none() && self.events.is_empty()
412    }
413}
414
415/// State changes in a room.
416#[derive(Clone, Debug, Serialize)]
417#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
418pub enum State {
419    /// The state changes between the previous sync and the **start** of the timeline.
420    ///
421    /// To get the full list of state changes since the previous sync, the state events in
422    /// [`Timeline`] must be added to these events to update the local state.
423    ///
424    /// With the `unstable-msc4222` feature, to get this variant, `use_state_after` must be set to
425    /// `false` in the [`Request`], which is the default.
426    #[serde(rename = "state")]
427    Before(StateEvents),
428
429    /// The state changes between the previous sync and the **end** of the timeline.
430    ///
431    /// This contains the full list of state changes since the previous sync. State events in
432    /// [`Timeline`] must be ignored to update the local state.
433    ///
434    /// To get this variant, `use_state_after` must be set to `true` in the [`Request`].
435    #[cfg(feature = "unstable-msc4222")]
436    #[serde(rename = "org.matrix.msc4222.state_after")]
437    After(StateEvents),
438}
439
440impl State {
441    /// Returns true if this is the `Before` variant and there are no state updates.
442    fn is_before_and_empty(&self) -> bool {
443        as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
444    }
445
446    /// Returns true if there are no state updates.
447    pub fn is_empty(&self) -> bool {
448        match self {
449            Self::Before(state) => state.is_empty(),
450            #[cfg(feature = "unstable-msc4222")]
451            Self::After(state) => state.is_empty(),
452        }
453    }
454}
455
456impl Default for State {
457    fn default() -> Self {
458        Self::Before(Default::default())
459    }
460}
461
462/// State events in the room.
463#[derive(Clone, Debug, Default, Deserialize, Serialize)]
464#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
465pub struct StateEvents {
466    /// A list of state events.
467    #[serde(default, skip_serializing_if = "Vec::is_empty")]
468    pub events: Vec<Raw<AnySyncStateEvent>>,
469}
470
471impl StateEvents {
472    /// Creates an empty `State`.
473    pub fn new() -> Self {
474        Default::default()
475    }
476
477    /// Returns true if there are no state updates.
478    pub fn is_empty(&self) -> bool {
479        self.events.is_empty()
480    }
481
482    /// Creates a `State` with events
483    pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
484        Self { events, ..Default::default() }
485    }
486}
487
488impl From<Vec<Raw<AnySyncStateEvent>>> for StateEvents {
489    fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
490        Self::with_events(events)
491    }
492}
493
494/// The global private data created by this user.
495#[derive(Clone, Debug, Default, Deserialize, Serialize)]
496#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
497pub struct GlobalAccountData {
498    /// A list of events.
499    #[serde(default, skip_serializing_if = "Vec::is_empty")]
500    pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
501}
502
503impl GlobalAccountData {
504    /// Creates an empty `GlobalAccountData`.
505    pub fn new() -> Self {
506        Default::default()
507    }
508
509    /// Returns true if there are no global account data updates.
510    pub fn is_empty(&self) -> bool {
511        self.events.is_empty()
512    }
513}
514
515/// The private data that this user has attached to this room.
516#[derive(Clone, Debug, Default, Deserialize, Serialize)]
517#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
518pub struct RoomAccountData {
519    /// A list of events.
520    #[serde(default, skip_serializing_if = "Vec::is_empty")]
521    pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
522}
523
524impl RoomAccountData {
525    /// Creates an empty `RoomAccountData`.
526    pub fn new() -> Self {
527        Default::default()
528    }
529
530    /// Returns true if there are no room account data updates.
531    pub fn is_empty(&self) -> bool {
532        self.events.is_empty()
533    }
534}
535
536/// Ephemeral events not recorded in the timeline or state of the room.
537#[derive(Clone, Debug, Default, Deserialize, Serialize)]
538#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
539pub struct Ephemeral {
540    /// A list of events.
541    #[serde(default, skip_serializing_if = "Vec::is_empty")]
542    pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
543}
544
545impl Ephemeral {
546    /// Creates an empty `Ephemeral`.
547    pub fn new() -> Self {
548        Default::default()
549    }
550
551    /// Returns true if there are no ephemeral event updates.
552    pub fn is_empty(&self) -> bool {
553        self.events.is_empty()
554    }
555}
556
557/// Information about room for rendering to clients.
558#[derive(Clone, Debug, Default, Deserialize, Serialize)]
559#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
560pub struct RoomSummary {
561    /// Users which can be used to generate a room name if the room does not have one.
562    ///
563    /// Required if room name or canonical aliases are not set or empty.
564    #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
565    pub heroes: Vec<OwnedUserId>,
566
567    /// Number of users whose membership status is `join`.
568    /// Required if field has changed since last sync; otherwise, it may be
569    /// omitted.
570    #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
571    pub joined_member_count: Option<UInt>,
572
573    /// Number of users whose membership status is `invite`.
574    /// Required if field has changed since last sync; otherwise, it may be
575    /// omitted.
576    #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
577    pub invited_member_count: Option<UInt>,
578}
579
580impl RoomSummary {
581    /// Creates an empty `RoomSummary`.
582    pub fn new() -> Self {
583        Default::default()
584    }
585
586    /// Returns true if there are no room summary updates.
587    pub fn is_empty(&self) -> bool {
588        self.heroes.is_empty()
589            && self.joined_member_count.is_none()
590            && self.invited_member_count.is_none()
591    }
592}
593
594/// Updates to the rooms that the user has been invited to.
595#[derive(Clone, Debug, Default, Deserialize, Serialize)]
596#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
597pub struct InvitedRoom {
598    /// The state of a room that the user has been invited to.
599    #[serde(default, skip_serializing_if = "InviteState::is_empty")]
600    pub invite_state: InviteState,
601}
602
603impl InvitedRoom {
604    /// Creates an empty `InvitedRoom`.
605    pub fn new() -> Self {
606        Default::default()
607    }
608
609    /// Returns true if there are no updates to this room.
610    pub fn is_empty(&self) -> bool {
611        self.invite_state.is_empty()
612    }
613}
614
615impl From<InviteState> for InvitedRoom {
616    fn from(invite_state: InviteState) -> Self {
617        InvitedRoom { invite_state, ..Default::default() }
618    }
619}
620
621/// The state of a room that the user has been invited to.
622#[derive(Clone, Debug, Default, Deserialize, Serialize)]
623#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
624pub struct InviteState {
625    /// A list of state events.
626    #[serde(default, skip_serializing_if = "Vec::is_empty")]
627    pub events: Vec<Raw<StrippedState>>,
628}
629
630impl InviteState {
631    /// Creates an empty `InviteState`.
632    pub fn new() -> Self {
633        Default::default()
634    }
635
636    /// Returns true if there are no state updates.
637    pub fn is_empty(&self) -> bool {
638        self.events.is_empty()
639    }
640}
641
642impl From<Vec<Raw<StrippedState>>> for InviteState {
643    fn from(events: Vec<Raw<StrippedState>>) -> Self {
644        InviteState { events, ..Default::default() }
645    }
646}
647
648/// Updates to the presence status of other users.
649#[derive(Clone, Debug, Default, Deserialize, Serialize)]
650#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
651pub struct Presence {
652    /// A list of events.
653    #[serde(default, skip_serializing_if = "Vec::is_empty")]
654    pub events: Vec<Raw<PresenceEvent>>,
655}
656
657impl Presence {
658    /// Creates an empty `Presence`.
659    pub fn new() -> Self {
660        Default::default()
661    }
662
663    /// Returns true if there are no presence updates.
664    pub fn is_empty(&self) -> bool {
665        self.events.is_empty()
666    }
667}
668
669/// Messages sent directly between devices.
670#[derive(Clone, Debug, Default, Deserialize, Serialize)]
671#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
672pub struct ToDevice {
673    /// A list of to-device events.
674    #[serde(default, skip_serializing_if = "Vec::is_empty")]
675    pub events: Vec<Raw<AnyToDeviceEvent>>,
676}
677
678impl ToDevice {
679    /// Creates an empty `ToDevice`.
680    pub fn new() -> Self {
681        Default::default()
682    }
683
684    /// Returns true if there are no to-device events.
685    pub fn is_empty(&self) -> bool {
686        self.events.is_empty()
687    }
688}
689
690#[cfg(test)]
691mod tests {
692    use assign::assign;
693    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
694
695    use super::Timeline;
696
697    #[test]
698    fn timeline_serde() {
699        let timeline = assign!(Timeline::new(), { limited: true });
700        let timeline_serialized = json!({ "events": [], "limited": true });
701        assert_eq!(to_json_value(timeline).unwrap(), timeline_serialized);
702
703        let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
704        assert!(timeline_deserialized.limited);
705
706        let timeline_default = Timeline::default();
707        assert_eq!(to_json_value(timeline_default).unwrap(), json!({ "events": [] }));
708
709        let timeline_default_deserialized =
710            from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
711        assert!(!timeline_default_deserialized.limited);
712    }
713}
714
715#[cfg(all(test, feature = "client"))]
716mod client_tests {
717    use std::time::Duration;
718
719    use assert_matches2::assert_matches;
720    use ruma_common::{
721        api::{
722            IncomingResponse as _, MatrixVersion, OutgoingRequest as _, SendAccessToken,
723            SupportedVersions,
724        },
725        event_id, room_id, user_id, RoomVersionId,
726    };
727    use ruma_events::AnyStrippedStateEvent;
728    use serde_json::{json, to_vec as to_json_vec, Value as JsonValue};
729
730    use super::{Filter, PresenceState, Request, Response, State, StrippedState};
731
732    fn sync_state_event() -> JsonValue {
733        json!({
734            "content": {
735              "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
736              "displayname": "Alice Margatroid",
737              "membership": "join",
738            },
739            "event_id": "$143273582443PhrSn",
740            "origin_server_ts": 1_432_735_824,
741            "sender": "@alice:example.org",
742            "state_key": "@alice:example.org",
743            "type": "m.room.member",
744            "unsigned": {
745              "age": 1234,
746              "membership": "join",
747            },
748        })
749    }
750
751    #[test]
752    fn serialize_request_all_params() {
753        let supported = SupportedVersions {
754            versions: [MatrixVersion::V1_1].into(),
755            features: Default::default(),
756        };
757        let req: http::Request<Vec<u8>> = Request {
758            filter: Some(Filter::FilterId("66696p746572".to_owned())),
759            since: Some("s72594_4483_1934".to_owned()),
760            full_state: true,
761            set_presence: PresenceState::Offline,
762            timeout: Some(Duration::from_millis(30000)),
763            #[cfg(feature = "unstable-msc4222")]
764            use_state_after: true,
765        }
766        .try_into_http_request(
767            "https://homeserver.tld",
768            SendAccessToken::IfRequired("auth_tok"),
769            &supported,
770        )
771        .unwrap();
772
773        let uri = req.uri();
774        let query = uri.query().unwrap();
775
776        assert_eq!(uri.path(), "/_matrix/client/v3/sync");
777        assert!(query.contains("filter=66696p746572"));
778        assert!(query.contains("since=s72594_4483_1934"));
779        assert!(query.contains("full_state=true"));
780        assert!(query.contains("set_presence=offline"));
781        assert!(query.contains("timeout=30000"));
782        #[cfg(feature = "unstable-msc4222")]
783        assert!(query.contains("org.matrix.msc4222.use_state_after=true"));
784    }
785
786    #[test]
787    fn deserialize_response_invite() {
788        let creator = user_id!("@creator:localhost");
789        let invitee = user_id!("@invitee:localhost");
790        let room_id = room_id!("!privateroom:localhost");
791        let event_id = event_id!("$invite");
792
793        let body = json!({
794            "next_batch": "a00",
795            "rooms": {
796                "invite": {
797                    room_id: {
798                        "invite_state": {
799                            "events": [
800                                {
801                                    "content": {
802                                        "room_version": "11",
803                                    },
804                                    "type": "m.room.create",
805                                    "state_key": "",
806                                    "sender": creator,
807                                },
808                                {
809                                    "content": {
810                                        "membership": "invite",
811                                    },
812                                    "type": "m.room.member",
813                                    "state_key": invitee,
814                                    "sender": creator,
815                                    "origin_server_ts": 4_345_456,
816                                    "event_id": event_id,
817                                },
818                            ],
819                        },
820                    },
821                },
822            },
823        });
824        let http_response = http::Response::new(to_json_vec(&body).unwrap());
825
826        let response = Response::try_from_http_response(http_response).unwrap();
827        assert_eq!(response.next_batch, "a00");
828        let private_room = response.rooms.invite.get(room_id).unwrap();
829
830        let first_event = private_room.invite_state.events[0].deserialize().unwrap();
831        assert_matches!(
832            first_event,
833            StrippedState::Stripped(AnyStrippedStateEvent::RoomCreate(create_event))
834        );
835        assert_eq!(create_event.sender, creator);
836        assert_eq!(create_event.content.room_version, RoomVersionId::V11);
837
838        #[cfg(feature = "unstable-msc4319")]
839        {
840            use ruma_events::{room::member::MembershipState, AnySyncStateEvent, SyncStateEvent};
841
842            let second_event = private_room.invite_state.events[1].deserialize().unwrap();
843            assert_matches!(
844                second_event,
845                StrippedState::Sync(AnySyncStateEvent::RoomMember(SyncStateEvent::Original(
846                    invite_event
847                )))
848            );
849            assert_eq!(invite_event.sender, creator);
850            assert_eq!(invite_event.state_key, invitee);
851            assert_eq!(invite_event.content.membership, MembershipState::Invite);
852        }
853    }
854
855    #[test]
856    fn deserialize_response_no_state() {
857        let joined_room_id = room_id!("!joined:localhost");
858        let left_room_id = room_id!("!left:localhost");
859        let event = sync_state_event();
860
861        let body = json!({
862            "next_batch": "aaa",
863            "rooms": {
864                "join": {
865                    joined_room_id: {
866                        "timeline": {
867                            "events": [
868                                event,
869                            ],
870                        },
871                    },
872                },
873                "leave": {
874                    left_room_id: {
875                        "timeline": {
876                            "events": [
877                                event,
878                            ],
879                        },
880                    },
881                },
882            },
883        });
884
885        let http_response = http::Response::new(to_json_vec(&body).unwrap());
886
887        let response = Response::try_from_http_response(http_response).unwrap();
888        assert_eq!(response.next_batch, "aaa");
889
890        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
891        assert_eq!(joined_room.timeline.events.len(), 1);
892        assert!(joined_room.state.is_before_and_empty());
893
894        let left_room = response.rooms.leave.get(left_room_id).unwrap();
895        assert_eq!(left_room.timeline.events.len(), 1);
896        assert!(left_room.state.is_before_and_empty());
897    }
898
899    #[test]
900    fn deserialize_response_state_before() {
901        let joined_room_id = room_id!("!joined:localhost");
902        let left_room_id = room_id!("!left:localhost");
903        let event = sync_state_event();
904
905        let body = json!({
906            "next_batch": "aaa",
907            "rooms": {
908                "join": {
909                    joined_room_id: {
910                        "state": {
911                            "events": [
912                                event,
913                            ],
914                        },
915                    },
916                },
917                "leave": {
918                    left_room_id: {
919                        "state": {
920                            "events": [
921                                event,
922                            ],
923                        },
924                    },
925                },
926            },
927        });
928
929        let http_response = http::Response::new(to_json_vec(&body).unwrap());
930
931        let response = Response::try_from_http_response(http_response).unwrap();
932        assert_eq!(response.next_batch, "aaa");
933
934        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
935        assert!(joined_room.timeline.is_empty());
936        assert_matches!(&joined_room.state, State::Before(state));
937        assert_eq!(state.events.len(), 1);
938
939        let left_room = response.rooms.leave.get(left_room_id).unwrap();
940        assert!(left_room.timeline.is_empty());
941        assert_matches!(&left_room.state, State::Before(state));
942        assert_eq!(state.events.len(), 1);
943    }
944
945    #[test]
946    #[cfg(feature = "unstable-msc4222")]
947    fn deserialize_response_empty_state_after() {
948        let joined_room_id = room_id!("!joined:localhost");
949        let left_room_id = room_id!("!left:localhost");
950
951        let body = json!({
952            "next_batch": "aaa",
953            "rooms": {
954                "join": {
955                    joined_room_id: {
956                        "org.matrix.msc4222.state_after": {},
957                    },
958                },
959                "leave": {
960                    left_room_id: {
961                        "org.matrix.msc4222.state_after": {},
962                    },
963                },
964            },
965        });
966
967        let http_response = http::Response::new(to_json_vec(&body).unwrap());
968
969        let response = Response::try_from_http_response(http_response).unwrap();
970        assert_eq!(response.next_batch, "aaa");
971
972        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
973        assert!(joined_room.timeline.is_empty());
974        assert_matches!(&joined_room.state, State::After(state));
975        assert_eq!(state.events.len(), 0);
976
977        let left_room = response.rooms.leave.get(left_room_id).unwrap();
978        assert!(left_room.timeline.is_empty());
979        assert_matches!(&left_room.state, State::After(state));
980        assert_eq!(state.events.len(), 0);
981    }
982
983    #[test]
984    #[cfg(feature = "unstable-msc4222")]
985    fn deserialize_response_non_empty_state_after() {
986        let joined_room_id = room_id!("!joined:localhost");
987        let left_room_id = room_id!("!left:localhost");
988        let event = sync_state_event();
989
990        let body = json!({
991            "next_batch": "aaa",
992            "rooms": {
993                "join": {
994                    joined_room_id: {
995                        "org.matrix.msc4222.state_after": {
996                            "events": [
997                                event,
998                            ],
999                        },
1000                    },
1001                },
1002                "leave": {
1003                    left_room_id: {
1004                        "org.matrix.msc4222.state_after": {
1005                            "events": [
1006                                event,
1007                            ],
1008                        },
1009                    },
1010                },
1011            },
1012        });
1013
1014        let http_response = http::Response::new(to_json_vec(&body).unwrap());
1015
1016        let response = Response::try_from_http_response(http_response).unwrap();
1017        assert_eq!(response.next_batch, "aaa");
1018
1019        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
1020        assert!(joined_room.timeline.is_empty());
1021        assert_matches!(&joined_room.state, State::After(state));
1022        assert_eq!(state.events.len(), 1);
1023
1024        let left_room = response.rooms.leave.get(left_room_id).unwrap();
1025        assert!(left_room.timeline.is_empty());
1026        assert_matches!(&left_room.state, State::After(state));
1027        assert_eq!(state.events.len(), 1);
1028    }
1029}
1030
1031#[cfg(all(test, feature = "server"))]
1032mod server_tests {
1033    use std::time::Duration;
1034
1035    use assert_matches2::assert_matches;
1036    use ruma_common::{
1037        api::{IncomingRequest as _, OutgoingResponse as _},
1038        owned_room_id,
1039        presence::PresenceState,
1040        serde::Raw,
1041    };
1042    use ruma_events::AnySyncStateEvent;
1043    use serde_json::{from_slice as from_json_slice, json, Value as JsonValue};
1044
1045    use super::{Filter, JoinedRoom, LeftRoom, Request, Response, State};
1046
1047    fn sync_state_event() -> Raw<AnySyncStateEvent> {
1048        Raw::new(&json!({
1049            "content": {
1050              "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1051              "displayname": "Alice Margatroid",
1052              "membership": "join",
1053            },
1054            "event_id": "$143273582443PhrSn",
1055            "origin_server_ts": 1_432_735_824,
1056            "sender": "@alice:example.org",
1057            "state_key": "@alice:example.org",
1058            "type": "m.room.member",
1059            "unsigned": {
1060              "age": 1234,
1061              "membership": "join",
1062            },
1063        }))
1064        .unwrap()
1065        .cast_unchecked()
1066    }
1067
1068    #[test]
1069    fn deserialize_request_all_query_params() {
1070        let uri = http::Uri::builder()
1071            .scheme("https")
1072            .authority("matrix.org")
1073            .path_and_query(
1074                "/_matrix/client/r0/sync\
1075                ?filter=myfilter\
1076                &since=myts\
1077                &full_state=false\
1078                &set_presence=offline\
1079                &timeout=5000",
1080            )
1081            .build()
1082            .unwrap();
1083
1084        let req = Request::try_from_http_request(
1085            http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1086            &[] as &[String],
1087        )
1088        .unwrap();
1089
1090        assert_matches!(req.filter, Some(Filter::FilterId(id)));
1091        assert_eq!(id, "myfilter");
1092        assert_eq!(req.since.as_deref(), Some("myts"));
1093        assert!(!req.full_state);
1094        assert_eq!(req.set_presence, PresenceState::Offline);
1095        assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
1096    }
1097
1098    #[test]
1099    fn deserialize_request_no_query_params() {
1100        let uri = http::Uri::builder()
1101            .scheme("https")
1102            .authority("matrix.org")
1103            .path_and_query("/_matrix/client/r0/sync")
1104            .build()
1105            .unwrap();
1106
1107        let req = Request::try_from_http_request(
1108            http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1109            &[] as &[String],
1110        )
1111        .unwrap();
1112
1113        assert_matches!(req.filter, None);
1114        assert_eq!(req.since, None);
1115        assert!(!req.full_state);
1116        assert_eq!(req.set_presence, PresenceState::Online);
1117        assert_eq!(req.timeout, None);
1118    }
1119
1120    #[test]
1121    fn deserialize_request_some_query_params() {
1122        let uri = http::Uri::builder()
1123            .scheme("https")
1124            .authority("matrix.org")
1125            .path_and_query(
1126                "/_matrix/client/r0/sync\
1127                ?filter=EOKFFmdZYF\
1128                &timeout=0",
1129            )
1130            .build()
1131            .unwrap();
1132
1133        let req = Request::try_from_http_request(
1134            http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1135            &[] as &[String],
1136        )
1137        .unwrap();
1138
1139        assert_matches!(req.filter, Some(Filter::FilterId(id)));
1140        assert_eq!(id, "EOKFFmdZYF");
1141        assert_eq!(req.since, None);
1142        assert!(!req.full_state);
1143        assert_eq!(req.set_presence, PresenceState::Online);
1144        assert_eq!(req.timeout, Some(Duration::from_millis(0)));
1145    }
1146
1147    #[test]
1148    fn serialize_response_no_state() {
1149        let joined_room_id = owned_room_id!("!joined:localhost");
1150        let left_room_id = owned_room_id!("!left:localhost");
1151        let event = sync_state_event();
1152
1153        let mut response = Response::new("aaa".to_owned());
1154
1155        let mut joined_room = JoinedRoom::new();
1156        joined_room.timeline.events.push(event.clone().cast());
1157        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1158
1159        let mut left_room = LeftRoom::new();
1160        left_room.timeline.events.push(event.clone().cast());
1161        response.rooms.leave.insert(left_room_id.clone(), left_room);
1162
1163        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1164
1165        assert_eq!(
1166            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1167            json!({
1168                "next_batch": "aaa",
1169                "rooms": {
1170                    "join": {
1171                        joined_room_id: {
1172                            "timeline": {
1173                                "events": [
1174                                    event,
1175                                ],
1176                            },
1177                        },
1178                    },
1179                    "leave": {
1180                        left_room_id: {
1181                            "timeline": {
1182                                "events": [
1183                                    event,
1184                                ],
1185                            },
1186                        },
1187                    },
1188                },
1189            })
1190        );
1191    }
1192
1193    #[test]
1194    fn serialize_response_state_before() {
1195        let joined_room_id = owned_room_id!("!joined:localhost");
1196        let left_room_id = owned_room_id!("!left:localhost");
1197        let event = sync_state_event();
1198
1199        let mut response = Response::new("aaa".to_owned());
1200
1201        let mut joined_room = JoinedRoom::new();
1202        joined_room.state = State::Before(vec![event.clone()].into());
1203        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1204
1205        let mut left_room = LeftRoom::new();
1206        left_room.state = State::Before(vec![event.clone()].into());
1207        response.rooms.leave.insert(left_room_id.clone(), left_room);
1208
1209        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1210
1211        assert_eq!(
1212            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1213            json!({
1214                "next_batch": "aaa",
1215                "rooms": {
1216                    "join": {
1217                        joined_room_id: {
1218                            "state": {
1219                                "events": [
1220                                    event,
1221                                ],
1222                            },
1223                        },
1224                    },
1225                    "leave": {
1226                        left_room_id: {
1227                            "state": {
1228                                "events": [
1229                                    event,
1230                                ],
1231                            },
1232                        },
1233                    },
1234                },
1235            })
1236        );
1237    }
1238
1239    #[test]
1240    #[cfg(feature = "unstable-msc4222")]
1241    fn serialize_response_empty_state_after() {
1242        let joined_room_id = owned_room_id!("!joined:localhost");
1243        let left_room_id = owned_room_id!("!left:localhost");
1244
1245        let mut response = Response::new("aaa".to_owned());
1246
1247        let mut joined_room = JoinedRoom::new();
1248        joined_room.state = State::After(Default::default());
1249        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1250
1251        let mut left_room = LeftRoom::new();
1252        left_room.state = State::After(Default::default());
1253        response.rooms.leave.insert(left_room_id.clone(), left_room);
1254
1255        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1256
1257        assert_eq!(
1258            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1259            json!({
1260                "next_batch": "aaa",
1261                "rooms": {
1262                    "join": {
1263                        joined_room_id: {
1264                            "org.matrix.msc4222.state_after": {},
1265                        },
1266                    },
1267                    "leave": {
1268                        left_room_id: {
1269                            "org.matrix.msc4222.state_after": {},
1270                        },
1271                    },
1272                },
1273            })
1274        );
1275    }
1276
1277    #[test]
1278    #[cfg(feature = "unstable-msc4222")]
1279    fn serialize_response_non_empty_state_after() {
1280        let joined_room_id = owned_room_id!("!joined:localhost");
1281        let left_room_id = owned_room_id!("!left:localhost");
1282        let event = sync_state_event();
1283
1284        let mut response = Response::new("aaa".to_owned());
1285
1286        let mut joined_room = JoinedRoom::new();
1287        joined_room.state = State::After(vec![event.clone()].into());
1288        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1289
1290        let mut left_room = LeftRoom::new();
1291        left_room.state = State::After(vec![event.clone()].into());
1292        response.rooms.leave.insert(left_room_id.clone(), left_room);
1293
1294        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1295
1296        assert_eq!(
1297            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1298            json!({
1299                "next_batch": "aaa",
1300                "rooms": {
1301                    "join": {
1302                        joined_room_id: {
1303                            "org.matrix.msc4222.state_after": {
1304                                "events": [
1305                                    event,
1306                                ],
1307                            },
1308                        },
1309                    },
1310                    "leave": {
1311                        left_room_id: {
1312                            "org.matrix.msc4222.state_after": {
1313                                "events": [
1314                                    event,
1315                                ],
1316                            },
1317                        },
1318                    },
1319                },
1320            })
1321        );
1322    }
1323}