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    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(error = crate::Error)]
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(error = crate::Error)]
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        self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
213    }
214}
215
216/// Historical updates to left rooms.
217#[derive(Clone, Debug, Default, Serialize)]
218#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
219pub struct LeftRoom {
220    /// The timeline of messages and state changes in the room up to the point when the user
221    /// left.
222    #[serde(skip_serializing_if = "Timeline::is_empty")]
223    pub timeline: Timeline,
224
225    /// The state updates for the room up to the start of the timeline.
226    #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
227    pub state: State,
228
229    /// The private data that this user has attached to this room.
230    #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
231    pub account_data: RoomAccountData,
232}
233
234impl LeftRoom {
235    /// Creates an empty `LeftRoom`.
236    pub fn new() -> Self {
237        Default::default()
238    }
239
240    /// Returns true if there are updates in the room.
241    pub fn is_empty(&self) -> bool {
242        self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
243    }
244}
245
246/// Updates to joined rooms.
247#[derive(Clone, Debug, Default, Serialize)]
248#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
249pub struct JoinedRoom {
250    /// Information about the room which clients may need to correctly render it
251    /// to users.
252    #[serde(skip_serializing_if = "RoomSummary::is_empty")]
253    pub summary: RoomSummary,
254
255    /// Counts of [unread notifications] for this room.
256    ///
257    /// If `unread_thread_notifications` was set to `true` in the [`RoomEventFilter`], these
258    /// include only the unread notifications for the main timeline.
259    ///
260    /// [unread notifications]: https://spec.matrix.org/latest/client-server-api/#receiving-notifications
261    /// [`RoomEventFilter`]: crate::filter::RoomEventFilter
262    #[serde(skip_serializing_if = "UnreadNotificationsCount::is_empty")]
263    pub unread_notifications: UnreadNotificationsCount,
264
265    /// Counts of [unread notifications] for threads in this room.
266    ///
267    /// This is a map from thread root ID to unread notifications in the thread.
268    ///
269    /// Only set if `unread_thread_notifications` was set to `true` in the [`RoomEventFilter`].
270    ///
271    /// [unread notifications]: https://spec.matrix.org/latest/client-server-api/#receiving-notifications
272    /// [`RoomEventFilter`]: crate::filter::RoomEventFilter
273    #[serde(skip_serializing_if = "BTreeMap::is_empty")]
274    pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
275
276    /// The timeline of messages and state changes in the room.
277    #[serde(skip_serializing_if = "Timeline::is_empty")]
278    pub timeline: Timeline,
279
280    /// Updates to the state, between the time indicated by the `since` parameter, and the
281    /// start of the `timeline` (or all state up to the start of the `timeline`, if
282    /// `since` is not given, or `full_state` is true).
283    #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
284    pub state: State,
285
286    /// The private data that this user has attached to this room.
287    #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
288    pub account_data: RoomAccountData,
289
290    /// The ephemeral events in the room that aren't recorded in the timeline or state of the
291    /// room.
292    #[serde(skip_serializing_if = "Ephemeral::is_empty")]
293    pub ephemeral: Ephemeral,
294
295    /// The number of unread events since the latest read receipt.
296    ///
297    /// This uses the unstable prefix in [MSC2654].
298    ///
299    /// [MSC2654]: https://github.com/matrix-org/matrix-spec-proposals/pull/2654
300    #[cfg(feature = "unstable-msc2654")]
301    #[serde(rename = "org.matrix.msc2654.unread_count", skip_serializing_if = "Option::is_none")]
302    pub unread_count: Option<UInt>,
303}
304
305impl JoinedRoom {
306    /// Creates an empty `JoinedRoom`.
307    pub fn new() -> Self {
308        Default::default()
309    }
310
311    /// Returns true if there are no updates in the room.
312    pub fn is_empty(&self) -> bool {
313        let is_empty = self.summary.is_empty()
314            && self.unread_notifications.is_empty()
315            && self.unread_thread_notifications.is_empty()
316            && self.timeline.is_empty()
317            && self.state.is_empty()
318            && self.account_data.is_empty()
319            && self.ephemeral.is_empty();
320
321        #[cfg(not(feature = "unstable-msc2654"))]
322        return is_empty;
323
324        #[cfg(feature = "unstable-msc2654")]
325        return is_empty && self.unread_count.is_none();
326    }
327}
328
329/// Updates to a room that the user has knocked upon.
330#[derive(Clone, Debug, Default, Deserialize, Serialize)]
331#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
332pub struct KnockedRoom {
333    /// Updates to the stripped state of the room.
334    #[serde(default, skip_serializing_if = "KnockState::is_empty")]
335    pub knock_state: KnockState,
336}
337
338impl KnockedRoom {
339    /// Creates an empty `KnockedRoom`.
340    pub fn new() -> Self {
341        Default::default()
342    }
343
344    /// Whether there are updates for this room.
345    pub fn is_empty(&self) -> bool {
346        self.knock_state.is_empty()
347    }
348}
349
350impl From<KnockState> for KnockedRoom {
351    fn from(knock_state: KnockState) -> Self {
352        KnockedRoom { knock_state, ..Default::default() }
353    }
354}
355
356/// Stripped state updates of a room that the user has knocked upon.
357#[derive(Clone, Debug, Default, Deserialize, Serialize)]
358#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
359pub struct KnockState {
360    /// The stripped state of a room that the user has knocked upon.
361    #[serde(default, skip_serializing_if = "Vec::is_empty")]
362    pub events: Vec<Raw<AnyStrippedStateEvent>>,
363}
364
365impl KnockState {
366    /// Creates an empty `KnockState`.
367    pub fn new() -> Self {
368        Default::default()
369    }
370
371    /// Whether there are stripped state updates in this room.
372    pub fn is_empty(&self) -> bool {
373        self.events.is_empty()
374    }
375}
376
377/// Events in the room.
378#[derive(Clone, Debug, Default, Deserialize, Serialize)]
379#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
380pub struct Timeline {
381    /// True if the number of events returned was limited by the `limit` on the filter.
382    ///
383    /// Default to `false`.
384    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
385    pub limited: bool,
386
387    /// A token that can be supplied to to the `from` parameter of the
388    /// `/rooms/{roomId}/messages` endpoint.
389    #[serde(skip_serializing_if = "Option::is_none")]
390    pub prev_batch: Option<String>,
391
392    /// A list of events.
393    pub events: Vec<Raw<AnySyncTimelineEvent>>,
394}
395
396impl Timeline {
397    /// Creates an empty `Timeline`.
398    pub fn new() -> Self {
399        Default::default()
400    }
401
402    /// Returns true if there are no timeline updates.
403    ///
404    /// A `Timeline` is considered non-empty if it has at least one event, a
405    /// `prev_batch` value, or `limited` is `true`.
406    pub fn is_empty(&self) -> bool {
407        !self.limited && self.prev_batch.is_none() && self.events.is_empty()
408    }
409}
410
411/// State changes in a room.
412#[derive(Clone, Debug, Serialize)]
413#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
414pub enum State {
415    /// The state changes between the previous sync and the **start** of the timeline.
416    ///
417    /// To get the full list of state changes since the previous sync, the state events in
418    /// [`Timeline`] must be added to these events to update the local state.
419    ///
420    /// To get this variant, `use_state_after` must be set to `false` in the [`Request`], which is
421    /// the default.
422    #[serde(rename = "state")]
423    Before(StateEvents),
424
425    /// The state changes between the previous sync and the **end** of the timeline.
426    ///
427    /// This contains the full list of state changes since the previous sync. State events in
428    /// [`Timeline`] must be ignored to update the local state.
429    ///
430    /// To get this variant, `use_state_after` must be set to `true` in the [`Request`].
431    #[serde(rename = "state_after")]
432    After(StateEvents),
433}
434
435impl State {
436    /// Returns true if this is the `Before` variant and there are no state updates.
437    fn is_before_and_empty(&self) -> bool {
438        as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
439    }
440
441    /// Returns true if there are no state updates.
442    pub fn is_empty(&self) -> bool {
443        match self {
444            Self::Before(state) => state.is_empty(),
445            Self::After(state) => state.is_empty(),
446        }
447    }
448}
449
450impl Default for State {
451    fn default() -> Self {
452        Self::Before(Default::default())
453    }
454}
455
456/// State events in the room.
457#[derive(Clone, Debug, Default, Deserialize, Serialize)]
458#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
459pub struct StateEvents {
460    /// A list of state events.
461    #[serde(default, skip_serializing_if = "Vec::is_empty")]
462    pub events: Vec<Raw<AnySyncStateEvent>>,
463}
464
465impl StateEvents {
466    /// Creates an empty `State`.
467    pub fn new() -> Self {
468        Default::default()
469    }
470
471    /// Returns true if there are no state updates.
472    pub fn is_empty(&self) -> bool {
473        self.events.is_empty()
474    }
475
476    /// Creates a `State` with events
477    pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
478        Self { events, ..Default::default() }
479    }
480}
481
482impl From<Vec<Raw<AnySyncStateEvent>>> for StateEvents {
483    fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
484        Self::with_events(events)
485    }
486}
487
488/// The global private data created by this user.
489#[derive(Clone, Debug, Default, Deserialize, Serialize)]
490#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
491pub struct GlobalAccountData {
492    /// A list of events.
493    #[serde(default, skip_serializing_if = "Vec::is_empty")]
494    pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
495}
496
497impl GlobalAccountData {
498    /// Creates an empty `GlobalAccountData`.
499    pub fn new() -> Self {
500        Default::default()
501    }
502
503    /// Returns true if there are no global account data updates.
504    pub fn is_empty(&self) -> bool {
505        self.events.is_empty()
506    }
507}
508
509/// The private data that this user has attached to this room.
510#[derive(Clone, Debug, Default, Deserialize, Serialize)]
511#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
512pub struct RoomAccountData {
513    /// A list of events.
514    #[serde(default, skip_serializing_if = "Vec::is_empty")]
515    pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
516}
517
518impl RoomAccountData {
519    /// Creates an empty `RoomAccountData`.
520    pub fn new() -> Self {
521        Default::default()
522    }
523
524    /// Returns true if there are no room account data updates.
525    pub fn is_empty(&self) -> bool {
526        self.events.is_empty()
527    }
528}
529
530/// Ephemeral events not recorded in the timeline or state of the room.
531#[derive(Clone, Debug, Default, Deserialize, Serialize)]
532#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
533pub struct Ephemeral {
534    /// A list of events.
535    #[serde(default, skip_serializing_if = "Vec::is_empty")]
536    pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
537}
538
539impl Ephemeral {
540    /// Creates an empty `Ephemeral`.
541    pub fn new() -> Self {
542        Default::default()
543    }
544
545    /// Returns true if there are no ephemeral event updates.
546    pub fn is_empty(&self) -> bool {
547        self.events.is_empty()
548    }
549}
550
551/// Information about room for rendering to clients.
552#[derive(Clone, Debug, Default, Deserialize, Serialize)]
553#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
554pub struct RoomSummary {
555    /// Users which can be used to generate a room name if the room does not have one.
556    ///
557    /// Required if room name or canonical aliases are not set or empty.
558    #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
559    pub heroes: Vec<OwnedUserId>,
560
561    /// Number of users whose membership status is `join`.
562    /// Required if field has changed since last sync; otherwise, it may be
563    /// omitted.
564    #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
565    pub joined_member_count: Option<UInt>,
566
567    /// Number of users whose membership status is `invite`.
568    /// Required if field has changed since last sync; otherwise, it may be
569    /// omitted.
570    #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
571    pub invited_member_count: Option<UInt>,
572}
573
574impl RoomSummary {
575    /// Creates an empty `RoomSummary`.
576    pub fn new() -> Self {
577        Default::default()
578    }
579
580    /// Returns true if there are no room summary updates.
581    pub fn is_empty(&self) -> bool {
582        self.heroes.is_empty()
583            && self.joined_member_count.is_none()
584            && self.invited_member_count.is_none()
585    }
586}
587
588/// Updates to the rooms that the user has been invited to.
589#[derive(Clone, Debug, Default, Deserialize, Serialize)]
590#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
591pub struct InvitedRoom {
592    /// The state of a room that the user has been invited to.
593    #[serde(default, skip_serializing_if = "InviteState::is_empty")]
594    pub invite_state: InviteState,
595}
596
597impl InvitedRoom {
598    /// Creates an empty `InvitedRoom`.
599    pub fn new() -> Self {
600        Default::default()
601    }
602
603    /// Returns true if there are no updates to this room.
604    pub fn is_empty(&self) -> bool {
605        self.invite_state.is_empty()
606    }
607}
608
609impl From<InviteState> for InvitedRoom {
610    fn from(invite_state: InviteState) -> Self {
611        InvitedRoom { invite_state, ..Default::default() }
612    }
613}
614
615/// The state of a room that the user has been invited to.
616#[derive(Clone, Debug, Default, Deserialize, Serialize)]
617#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
618pub struct InviteState {
619    /// A list of state events.
620    #[serde(default, skip_serializing_if = "Vec::is_empty")]
621    pub events: Vec<Raw<AnyStrippedStateEvent>>,
622}
623
624impl InviteState {
625    /// Creates an empty `InviteState`.
626    pub fn new() -> Self {
627        Default::default()
628    }
629
630    /// Returns true if there are no state updates.
631    pub fn is_empty(&self) -> bool {
632        self.events.is_empty()
633    }
634}
635
636impl From<Vec<Raw<AnyStrippedStateEvent>>> for InviteState {
637    fn from(events: Vec<Raw<AnyStrippedStateEvent>>) -> Self {
638        InviteState { events, ..Default::default() }
639    }
640}
641
642/// Updates to the presence status of other users.
643#[derive(Clone, Debug, Default, Deserialize, Serialize)]
644#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
645pub struct Presence {
646    /// A list of events.
647    #[serde(default, skip_serializing_if = "Vec::is_empty")]
648    pub events: Vec<Raw<PresenceEvent>>,
649}
650
651impl Presence {
652    /// Creates an empty `Presence`.
653    pub fn new() -> Self {
654        Default::default()
655    }
656
657    /// Returns true if there are no presence updates.
658    pub fn is_empty(&self) -> bool {
659        self.events.is_empty()
660    }
661}
662
663/// Messages sent directly between devices.
664#[derive(Clone, Debug, Default, Deserialize, Serialize)]
665#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
666pub struct ToDevice {
667    /// A list of to-device events.
668    #[serde(default, skip_serializing_if = "Vec::is_empty")]
669    pub events: Vec<Raw<AnyToDeviceEvent>>,
670}
671
672impl ToDevice {
673    /// Creates an empty `ToDevice`.
674    pub fn new() -> Self {
675        Default::default()
676    }
677
678    /// Returns true if there are no to-device events.
679    pub fn is_empty(&self) -> bool {
680        self.events.is_empty()
681    }
682}
683
684#[cfg(test)]
685mod tests {
686    use assign::assign;
687    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
688
689    use super::Timeline;
690
691    #[test]
692    fn timeline_serde() {
693        let timeline = assign!(Timeline::new(), { limited: true });
694        let timeline_serialized = json!({ "events": [], "limited": true });
695        assert_eq!(to_json_value(timeline).unwrap(), timeline_serialized);
696
697        let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
698        assert!(timeline_deserialized.limited);
699
700        let timeline_default = Timeline::default();
701        assert_eq!(to_json_value(timeline_default).unwrap(), json!({ "events": [] }));
702
703        let timeline_default_deserialized =
704            from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
705        assert!(!timeline_default_deserialized.limited);
706    }
707}
708
709#[cfg(all(test, feature = "client"))]
710mod client_tests {
711    use std::{borrow::Cow, time::Duration};
712
713    use assert_matches2::assert_matches;
714    use ruma_common::{
715        RoomVersionId,
716        api::{
717            IncomingResponse as _, MatrixVersion, OutgoingRequest as _, SupportedVersions,
718            auth_scheme::SendAccessToken,
719        },
720        event_id, room_id, user_id,
721    };
722    use ruma_events::AnyStrippedStateEvent;
723    use serde_json::{Value as JsonValue, json, to_vec as to_json_vec};
724
725    use super::{Filter, PresenceState, Request, Response, State};
726
727    fn sync_state_event() -> JsonValue {
728        json!({
729            "content": {
730              "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
731              "displayname": "Alice Margatroid",
732              "membership": "join",
733            },
734            "event_id": "$143273582443PhrSn",
735            "origin_server_ts": 1_432_735_824,
736            "sender": "@alice:example.org",
737            "state_key": "@alice:example.org",
738            "type": "m.room.member",
739            "unsigned": {
740              "age": 1234,
741              "membership": "join",
742            },
743        })
744    }
745
746    #[test]
747    fn serialize_request_all_params() {
748        let supported = SupportedVersions {
749            versions: [MatrixVersion::V1_1].into(),
750            features: Default::default(),
751        };
752        let req: http::Request<Vec<u8>> = Request {
753            filter: Some(Filter::FilterId("66696p746572".to_owned())),
754            since: Some("s72594_4483_1934".to_owned()),
755            full_state: true,
756            set_presence: PresenceState::Offline,
757            timeout: Some(Duration::from_millis(30000)),
758            use_state_after: true,
759        }
760        .try_into_http_request(
761            "https://homeserver.tld",
762            SendAccessToken::IfRequired("auth_tok"),
763            Cow::Owned(supported),
764        )
765        .unwrap();
766
767        let uri = req.uri();
768        let query = uri.query().unwrap();
769
770        assert_eq!(uri.path(), "/_matrix/client/v3/sync");
771        assert!(query.contains("filter=66696p746572"));
772        assert!(query.contains("since=s72594_4483_1934"));
773        assert!(query.contains("full_state=true"));
774        assert!(query.contains("set_presence=offline"));
775        assert!(query.contains("timeout=30000"));
776        assert!(query.contains("use_state_after=true"));
777    }
778
779    #[test]
780    fn deserialize_response_invite() {
781        let creator = user_id!("@creator:localhost");
782        let invitee = user_id!("@invitee:localhost");
783        let room_id = room_id!("!privateroom:localhost");
784        let event_id = event_id!("$invite");
785
786        let body = json!({
787            "next_batch": "a00",
788            "rooms": {
789                "invite": {
790                    room_id: {
791                        "invite_state": {
792                            "events": [
793                                {
794                                    "content": {
795                                        "room_version": "11",
796                                    },
797                                    "type": "m.room.create",
798                                    "state_key": "",
799                                    "sender": creator,
800                                },
801                                {
802                                    "content": {
803                                        "membership": "invite",
804                                    },
805                                    "type": "m.room.member",
806                                    "state_key": invitee,
807                                    "sender": creator,
808                                    "origin_server_ts": 4_345_456,
809                                    "event_id": event_id,
810                                },
811                            ],
812                        },
813                    },
814                },
815            },
816        });
817        let http_response = http::Response::new(to_json_vec(&body).unwrap());
818
819        let response = Response::try_from_http_response(http_response).unwrap();
820        assert_eq!(response.next_batch, "a00");
821        let private_room = response.rooms.invite.get(room_id).unwrap();
822
823        let first_event = private_room.invite_state.events[0].deserialize().unwrap();
824        assert_matches!(first_event, AnyStrippedStateEvent::RoomCreate(create_event));
825        assert_eq!(create_event.sender, creator);
826        assert_eq!(create_event.content.room_version, RoomVersionId::V11);
827    }
828
829    #[test]
830    fn deserialize_response_no_state() {
831        let joined_room_id = room_id!("!joined:localhost");
832        let left_room_id = room_id!("!left:localhost");
833        let event = sync_state_event();
834
835        let body = json!({
836            "next_batch": "aaa",
837            "rooms": {
838                "join": {
839                    joined_room_id: {
840                        "timeline": {
841                            "events": [
842                                event,
843                            ],
844                        },
845                    },
846                },
847                "leave": {
848                    left_room_id: {
849                        "timeline": {
850                            "events": [
851                                event,
852                            ],
853                        },
854                    },
855                },
856            },
857        });
858
859        let http_response = http::Response::new(to_json_vec(&body).unwrap());
860
861        let response = Response::try_from_http_response(http_response).unwrap();
862        assert_eq!(response.next_batch, "aaa");
863
864        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
865        assert_eq!(joined_room.timeline.events.len(), 1);
866        assert!(joined_room.state.is_before_and_empty());
867
868        let left_room = response.rooms.leave.get(left_room_id).unwrap();
869        assert_eq!(left_room.timeline.events.len(), 1);
870        assert!(left_room.state.is_before_and_empty());
871    }
872
873    #[test]
874    fn deserialize_response_state_before() {
875        let joined_room_id = room_id!("!joined:localhost");
876        let left_room_id = room_id!("!left:localhost");
877        let event = sync_state_event();
878
879        let body = json!({
880            "next_batch": "aaa",
881            "rooms": {
882                "join": {
883                    joined_room_id: {
884                        "state": {
885                            "events": [
886                                event,
887                            ],
888                        },
889                    },
890                },
891                "leave": {
892                    left_room_id: {
893                        "state": {
894                            "events": [
895                                event,
896                            ],
897                        },
898                    },
899                },
900            },
901        });
902
903        let http_response = http::Response::new(to_json_vec(&body).unwrap());
904
905        let response = Response::try_from_http_response(http_response).unwrap();
906        assert_eq!(response.next_batch, "aaa");
907
908        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
909        assert!(joined_room.timeline.is_empty());
910        assert_matches!(&joined_room.state, State::Before(state));
911        assert_eq!(state.events.len(), 1);
912
913        let left_room = response.rooms.leave.get(left_room_id).unwrap();
914        assert!(left_room.timeline.is_empty());
915        assert_matches!(&left_room.state, State::Before(state));
916        assert_eq!(state.events.len(), 1);
917    }
918
919    #[test]
920    fn deserialize_response_empty_state_after() {
921        let joined_room_id = room_id!("!joined:localhost");
922        let left_room_id = room_id!("!left:localhost");
923
924        let body = json!({
925            "next_batch": "aaa",
926            "rooms": {
927                "join": {
928                    joined_room_id: {
929                        "state_after": {},
930                    },
931                },
932                "leave": {
933                    left_room_id: {
934                        "state_after": {},
935                    },
936                },
937            },
938        });
939
940        let http_response = http::Response::new(to_json_vec(&body).unwrap());
941
942        let response = Response::try_from_http_response(http_response).unwrap();
943        assert_eq!(response.next_batch, "aaa");
944
945        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
946        assert!(joined_room.timeline.is_empty());
947        assert_matches!(&joined_room.state, State::After(state));
948        assert_eq!(state.events.len(), 0);
949
950        let left_room = response.rooms.leave.get(left_room_id).unwrap();
951        assert!(left_room.timeline.is_empty());
952        assert_matches!(&left_room.state, State::After(state));
953        assert_eq!(state.events.len(), 0);
954    }
955
956    #[test]
957    fn deserialize_response_non_empty_state_after() {
958        let joined_room_id = room_id!("!joined:localhost");
959        let left_room_id = room_id!("!left:localhost");
960        let event = sync_state_event();
961
962        let body = json!({
963            "next_batch": "aaa",
964            "rooms": {
965                "join": {
966                    joined_room_id: {
967                        "state_after": {
968                            "events": [
969                                event,
970                            ],
971                        },
972                    },
973                },
974                "leave": {
975                    left_room_id: {
976                        "state_after": {
977                            "events": [
978                                event,
979                            ],
980                        },
981                    },
982                },
983            },
984        });
985
986        let http_response = http::Response::new(to_json_vec(&body).unwrap());
987
988        let response = Response::try_from_http_response(http_response).unwrap();
989        assert_eq!(response.next_batch, "aaa");
990
991        let joined_room = response.rooms.join.get(joined_room_id).unwrap();
992        assert!(joined_room.timeline.is_empty());
993        assert_matches!(&joined_room.state, State::After(state));
994        assert_eq!(state.events.len(), 1);
995
996        let left_room = response.rooms.leave.get(left_room_id).unwrap();
997        assert!(left_room.timeline.is_empty());
998        assert_matches!(&left_room.state, State::After(state));
999        assert_eq!(state.events.len(), 1);
1000    }
1001}
1002
1003#[cfg(all(test, feature = "server"))]
1004mod server_tests {
1005    use std::time::Duration;
1006
1007    use assert_matches2::assert_matches;
1008    use ruma_common::{
1009        api::{IncomingRequest as _, OutgoingResponse as _},
1010        owned_room_id,
1011        presence::PresenceState,
1012        serde::Raw,
1013    };
1014    use ruma_events::AnySyncStateEvent;
1015    use serde_json::{Value as JsonValue, from_slice as from_json_slice, json};
1016
1017    use super::{Filter, JoinedRoom, LeftRoom, Request, Response, State};
1018
1019    fn sync_state_event() -> Raw<AnySyncStateEvent> {
1020        Raw::new(&json!({
1021            "content": {
1022              "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1023              "displayname": "Alice Margatroid",
1024              "membership": "join",
1025            },
1026            "event_id": "$143273582443PhrSn",
1027            "origin_server_ts": 1_432_735_824,
1028            "sender": "@alice:example.org",
1029            "state_key": "@alice:example.org",
1030            "type": "m.room.member",
1031            "unsigned": {
1032              "age": 1234,
1033              "membership": "join",
1034            },
1035        }))
1036        .unwrap()
1037        .cast_unchecked()
1038    }
1039
1040    #[test]
1041    fn deserialize_request_all_query_params() {
1042        let uri = http::Uri::builder()
1043            .scheme("https")
1044            .authority("matrix.org")
1045            .path_and_query(
1046                "/_matrix/client/r0/sync\
1047                ?filter=myfilter\
1048                &since=myts\
1049                &full_state=false\
1050                &set_presence=offline\
1051                &timeout=5000",
1052            )
1053            .build()
1054            .unwrap();
1055
1056        let req = Request::try_from_http_request(
1057            http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1058            &[] as &[String],
1059        )
1060        .unwrap();
1061
1062        assert_matches!(req.filter, Some(Filter::FilterId(id)));
1063        assert_eq!(id, "myfilter");
1064        assert_eq!(req.since.as_deref(), Some("myts"));
1065        assert!(!req.full_state);
1066        assert_eq!(req.set_presence, PresenceState::Offline);
1067        assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
1068    }
1069
1070    #[test]
1071    fn deserialize_request_no_query_params() {
1072        let uri = http::Uri::builder()
1073            .scheme("https")
1074            .authority("matrix.org")
1075            .path_and_query("/_matrix/client/r0/sync")
1076            .build()
1077            .unwrap();
1078
1079        let req = Request::try_from_http_request(
1080            http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1081            &[] as &[String],
1082        )
1083        .unwrap();
1084
1085        assert_matches!(req.filter, None);
1086        assert_eq!(req.since, None);
1087        assert!(!req.full_state);
1088        assert_eq!(req.set_presence, PresenceState::Online);
1089        assert_eq!(req.timeout, None);
1090    }
1091
1092    #[test]
1093    fn deserialize_request_some_query_params() {
1094        let uri = http::Uri::builder()
1095            .scheme("https")
1096            .authority("matrix.org")
1097            .path_and_query(
1098                "/_matrix/client/r0/sync\
1099                ?filter=EOKFFmdZYF\
1100                &timeout=0",
1101            )
1102            .build()
1103            .unwrap();
1104
1105        let req = Request::try_from_http_request(
1106            http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1107            &[] as &[String],
1108        )
1109        .unwrap();
1110
1111        assert_matches!(req.filter, Some(Filter::FilterId(id)));
1112        assert_eq!(id, "EOKFFmdZYF");
1113        assert_eq!(req.since, None);
1114        assert!(!req.full_state);
1115        assert_eq!(req.set_presence, PresenceState::Online);
1116        assert_eq!(req.timeout, Some(Duration::from_millis(0)));
1117    }
1118
1119    #[test]
1120    fn serialize_response_no_state() {
1121        let joined_room_id = owned_room_id!("!joined:localhost");
1122        let left_room_id = owned_room_id!("!left:localhost");
1123        let event = sync_state_event();
1124
1125        let mut response = Response::new("aaa".to_owned());
1126
1127        let mut joined_room = JoinedRoom::new();
1128        joined_room.timeline.events.push(event.clone().cast());
1129        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1130
1131        let mut left_room = LeftRoom::new();
1132        left_room.timeline.events.push(event.clone().cast());
1133        response.rooms.leave.insert(left_room_id.clone(), left_room);
1134
1135        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1136
1137        assert_eq!(
1138            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1139            json!({
1140                "next_batch": "aaa",
1141                "rooms": {
1142                    "join": {
1143                        joined_room_id: {
1144                            "timeline": {
1145                                "events": [
1146                                    event,
1147                                ],
1148                            },
1149                        },
1150                    },
1151                    "leave": {
1152                        left_room_id: {
1153                            "timeline": {
1154                                "events": [
1155                                    event,
1156                                ],
1157                            },
1158                        },
1159                    },
1160                },
1161            })
1162        );
1163    }
1164
1165    #[test]
1166    fn serialize_response_state_before() {
1167        let joined_room_id = owned_room_id!("!joined:localhost");
1168        let left_room_id = owned_room_id!("!left:localhost");
1169        let event = sync_state_event();
1170
1171        let mut response = Response::new("aaa".to_owned());
1172
1173        let mut joined_room = JoinedRoom::new();
1174        joined_room.state = State::Before(vec![event.clone()].into());
1175        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1176
1177        let mut left_room = LeftRoom::new();
1178        left_room.state = State::Before(vec![event.clone()].into());
1179        response.rooms.leave.insert(left_room_id.clone(), left_room);
1180
1181        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1182
1183        assert_eq!(
1184            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1185            json!({
1186                "next_batch": "aaa",
1187                "rooms": {
1188                    "join": {
1189                        joined_room_id: {
1190                            "state": {
1191                                "events": [
1192                                    event,
1193                                ],
1194                            },
1195                        },
1196                    },
1197                    "leave": {
1198                        left_room_id: {
1199                            "state": {
1200                                "events": [
1201                                    event,
1202                                ],
1203                            },
1204                        },
1205                    },
1206                },
1207            })
1208        );
1209    }
1210
1211    #[test]
1212    fn serialize_response_empty_state_after() {
1213        let joined_room_id = owned_room_id!("!joined:localhost");
1214        let left_room_id = owned_room_id!("!left:localhost");
1215
1216        let mut response = Response::new("aaa".to_owned());
1217
1218        let mut joined_room = JoinedRoom::new();
1219        joined_room.state = State::After(Default::default());
1220        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1221
1222        let mut left_room = LeftRoom::new();
1223        left_room.state = State::After(Default::default());
1224        response.rooms.leave.insert(left_room_id.clone(), left_room);
1225
1226        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1227
1228        assert_eq!(
1229            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1230            json!({
1231                "next_batch": "aaa",
1232                "rooms": {
1233                    "join": {
1234                        joined_room_id: {
1235                            "state_after": {},
1236                        },
1237                    },
1238                    "leave": {
1239                        left_room_id: {
1240                            "state_after": {},
1241                        },
1242                    },
1243                },
1244            })
1245        );
1246    }
1247
1248    #[test]
1249    fn serialize_response_non_empty_state_after() {
1250        let joined_room_id = owned_room_id!("!joined:localhost");
1251        let left_room_id = owned_room_id!("!left:localhost");
1252        let event = sync_state_event();
1253
1254        let mut response = Response::new("aaa".to_owned());
1255
1256        let mut joined_room = JoinedRoom::new();
1257        joined_room.state = State::After(vec![event.clone()].into());
1258        response.rooms.join.insert(joined_room_id.clone(), joined_room);
1259
1260        let mut left_room = LeftRoom::new();
1261        left_room.state = State::After(vec![event.clone()].into());
1262        response.rooms.leave.insert(left_room_id.clone(), left_room);
1263
1264        let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1265
1266        assert_eq!(
1267            from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1268            json!({
1269                "next_batch": "aaa",
1270                "rooms": {
1271                    "join": {
1272                        joined_room_id: {
1273                            "state_after": {
1274                                "events": [
1275                                    event,
1276                                ],
1277                            },
1278                        },
1279                    },
1280                    "leave": {
1281                        left_room_id: {
1282                            "state_after": {
1283                                "events": [
1284                                    event,
1285                                ],
1286                            },
1287                        },
1288                    },
1289                },
1290            })
1291        );
1292    }
1293}