Skip to main content

ruma_client_api/sync/sync_events/
v3.rs

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