1use 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]
40#[derive(Default)]
41pub struct Request {
42 #[serde(skip_serializing_if = "Option::is_none")]
44 #[ruma_api(query)]
45 pub filter: Option<Filter>,
46
47 #[serde(skip_serializing_if = "Option::is_none")]
52 #[ruma_api(query)]
53 pub since: Option<String>,
54
55 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
57 #[ruma_api(query)]
58 pub full_state: bool,
59
60 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
64 #[ruma_api(query)]
65 pub set_presence: PresenceState,
66
67 #[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 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
79 #[ruma_api(query)]
80 pub use_state_after: bool,
81}
82
83#[response]
85pub struct Response {
86 pub next_batch: String,
88
89 #[serde(default, skip_serializing_if = "Rooms::is_empty")]
91 pub rooms: Rooms,
92
93 #[serde(default, skip_serializing_if = "Presence::is_empty")]
95 pub presence: Presence,
96
97 #[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
99 pub account_data: GlobalAccountData,
100
101 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
103 pub to_device: ToDevice,
104
105 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
109 pub device_lists: DeviceLists,
110
111 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
114 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
115
116 #[serde(skip_serializing_if = "Option::is_none")]
121 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
122}
123
124impl Request {
125 pub fn new() -> Self {
127 Default::default()
128 }
129}
130
131impl Response {
132 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#[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 #[serde(with = "ruma_common::serde::json_string")]
165 FilterDefinition(FilterDefinition),
166
167 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
185#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
186pub struct Rooms {
187 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
189 pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
190
191 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
193 pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
194
195 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
197 pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
198
199 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
201 pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
202}
203
204impl Rooms {
205 pub fn new() -> Self {
207 Default::default()
208 }
209
210 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#[derive(Clone, Debug, Default, Serialize)]
219#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
220pub struct LeftRoom {
221 #[serde(skip_serializing_if = "Timeline::is_empty")]
224 pub timeline: Timeline,
225
226 #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
228 pub state: State,
229
230 #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
232 pub account_data: RoomAccountData,
233}
234
235impl LeftRoom {
236 pub fn new() -> Self {
238 Default::default()
239 }
240
241 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#[derive(Clone, Debug, Default, Serialize)]
250#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
251pub struct JoinedRoom {
252 #[serde(skip_serializing_if = "RoomSummary::is_empty")]
255 pub summary: RoomSummary,
256
257 #[serde(skip_serializing_if = "UnreadNotificationsCount::is_empty")]
265 pub unread_notifications: UnreadNotificationsCount,
266
267 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
276 pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
277
278 #[serde(skip_serializing_if = "Timeline::is_empty")]
280 pub timeline: Timeline,
281
282 #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
286 pub state: State,
287
288 #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
290 pub account_data: RoomAccountData,
291
292 #[serde(skip_serializing_if = "Ephemeral::is_empty")]
295 pub ephemeral: Ephemeral,
296
297 #[cfg(feature = "unstable-msc4354")]
301 #[serde(rename = "msc4354_sticky", skip_serializing_if = "Sticky::is_empty")]
302 pub sticky: Sticky,
303
304 #[cfg(feature = "unstable-msc2654")]
310 #[serde(rename = "org.matrix.msc2654.unread_count", skip_serializing_if = "Option::is_none")]
311 pub unread_count: Option<UInt>,
312}
313
314impl JoinedRoom {
315 pub fn new() -> Self {
317 Default::default()
318 }
319
320 pub fn is_empty(&self) -> bool {
322 let Self {
323 summary,
324 unread_notifications,
325 unread_thread_notifications,
326 timeline,
327 state,
328 account_data,
329 ephemeral,
330 #[cfg(feature = "unstable-msc4354")]
331 sticky,
332 #[cfg(feature = "unstable-msc2654")]
333 unread_count,
334 } = self;
335
336 #[cfg(not(feature = "unstable-msc2654"))]
337 let unread_count_is_none = true;
338 #[cfg(feature = "unstable-msc2654")]
339 let unread_count_is_none = unread_count.is_none();
340
341 #[cfg(not(feature = "unstable-msc4354"))]
342 let sticky_is_empty = true;
343 #[cfg(feature = "unstable-msc4354")]
344 let sticky_is_empty = sticky.is_empty();
345
346 summary.is_empty()
347 && unread_notifications.is_empty()
348 && unread_thread_notifications.is_empty()
349 && timeline.is_empty()
350 && state.is_empty()
351 && account_data.is_empty()
352 && ephemeral.is_empty()
353 && unread_count_is_none
354 && sticky_is_empty
355 }
356}
357
358#[derive(Clone, Debug, Default, Deserialize, Serialize)]
360#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
361pub struct KnockedRoom {
362 #[serde(default, skip_serializing_if = "KnockState::is_empty")]
364 pub knock_state: KnockState,
365}
366
367impl KnockedRoom {
368 pub fn new() -> Self {
370 Default::default()
371 }
372
373 pub fn is_empty(&self) -> bool {
375 let Self { knock_state } = self;
376 knock_state.is_empty()
377 }
378}
379
380impl From<KnockState> for KnockedRoom {
381 fn from(knock_state: KnockState) -> Self {
382 KnockedRoom { knock_state, ..Default::default() }
383 }
384}
385
386#[derive(Clone, Debug, Default, Deserialize, Serialize)]
388#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
389pub struct KnockState {
390 #[serde(default, skip_serializing_if = "Vec::is_empty")]
392 pub events: Vec<Raw<AnyStrippedStateEvent>>,
393}
394
395impl KnockState {
396 pub fn new() -> Self {
398 Default::default()
399 }
400
401 pub fn is_empty(&self) -> bool {
403 let Self { events } = self;
404 events.is_empty()
405 }
406}
407
408#[derive(Clone, Debug, Default, Deserialize, Serialize)]
410#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
411pub struct Timeline {
412 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
416 pub limited: bool,
417
418 #[serde(skip_serializing_if = "Option::is_none")]
421 pub prev_batch: Option<String>,
422
423 pub events: Vec<Raw<AnySyncTimelineEvent>>,
425}
426
427impl Timeline {
428 pub fn new() -> Self {
430 Default::default()
431 }
432
433 pub fn is_empty(&self) -> bool {
438 let Self { limited, prev_batch, events } = self;
439 !limited && prev_batch.is_none() && events.is_empty()
440 }
441}
442
443#[derive(Clone, Debug, Serialize)]
445#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
446pub enum State {
447 #[serde(rename = "state")]
455 Before(StateEvents),
456
457 #[serde(rename = "state_after")]
464 After(StateEvents),
465}
466
467impl State {
468 fn is_before_and_empty(&self) -> bool {
470 as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
471 }
472
473 pub fn is_empty(&self) -> bool {
475 match self {
476 Self::Before(state) => state.is_empty(),
477 Self::After(state) => state.is_empty(),
478 }
479 }
480}
481
482impl Default for State {
483 fn default() -> Self {
484 Self::Before(Default::default())
485 }
486}
487
488#[derive(Clone, Debug, Default, Deserialize, Serialize)]
490#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
491pub struct StateEvents {
492 #[serde(default, skip_serializing_if = "Vec::is_empty")]
494 pub events: Vec<Raw<AnySyncStateEvent>>,
495}
496
497impl StateEvents {
498 pub fn new() -> Self {
500 Default::default()
501 }
502
503 pub fn is_empty(&self) -> bool {
505 let Self { events } = self;
506 events.is_empty()
507 }
508
509 pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
511 Self { events, ..Default::default() }
512 }
513}
514
515impl From<Vec<Raw<AnySyncStateEvent>>> for StateEvents {
516 fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
517 Self::with_events(events)
518 }
519}
520
521#[derive(Clone, Debug, Default, Deserialize, Serialize)]
523#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
524pub struct GlobalAccountData {
525 #[serde(default, skip_serializing_if = "Vec::is_empty")]
527 pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
528}
529
530impl GlobalAccountData {
531 pub fn new() -> Self {
533 Default::default()
534 }
535
536 pub fn is_empty(&self) -> bool {
538 let Self { events } = self;
539 events.is_empty()
540 }
541}
542
543#[derive(Clone, Debug, Default, Deserialize, Serialize)]
545#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
546pub struct RoomAccountData {
547 #[serde(default, skip_serializing_if = "Vec::is_empty")]
549 pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
550}
551
552impl RoomAccountData {
553 pub fn new() -> Self {
555 Default::default()
556 }
557
558 pub fn is_empty(&self) -> bool {
560 let Self { events } = self;
561 events.is_empty()
562 }
563}
564
565#[derive(Clone, Debug, Default, Deserialize, Serialize)]
567#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
568pub struct Ephemeral {
569 #[serde(default, skip_serializing_if = "Vec::is_empty")]
571 pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
572}
573
574impl Ephemeral {
575 pub fn new() -> Self {
577 Default::default()
578 }
579
580 pub fn is_empty(&self) -> bool {
582 let Self { events } = self;
583 events.is_empty()
584 }
585}
586
587#[cfg(feature = "unstable-msc4354")]
591#[derive(Clone, Debug, Default, Deserialize, Serialize)]
592#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
593pub struct Sticky {
594 #[serde(default, skip_serializing_if = "Vec::is_empty")]
596 pub events: Vec<Raw<AnySyncTimelineEvent>>,
597}
598
599#[cfg(feature = "unstable-msc4354")]
600impl Sticky {
601 pub fn new() -> Self {
603 Default::default()
604 }
605
606 pub fn is_empty(&self) -> bool {
608 let Self { events } = self;
609 events.is_empty()
610 }
611}
612
613#[derive(Clone, Debug, Default, Deserialize, Serialize)]
615#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
616pub struct RoomSummary {
617 #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
621 pub heroes: Vec<OwnedUserId>,
622
623 #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
627 pub joined_member_count: Option<UInt>,
628
629 #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
633 pub invited_member_count: Option<UInt>,
634}
635
636impl RoomSummary {
637 pub fn new() -> Self {
639 Default::default()
640 }
641
642 pub fn is_empty(&self) -> bool {
644 let Self { heroes, joined_member_count, invited_member_count } = self;
645 heroes.is_empty() && joined_member_count.is_none() && invited_member_count.is_none()
646 }
647}
648
649#[derive(Clone, Debug, Default, Deserialize, Serialize)]
651#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
652pub struct InvitedRoom {
653 #[serde(default, skip_serializing_if = "InviteState::is_empty")]
655 pub invite_state: InviteState,
656}
657
658impl InvitedRoom {
659 pub fn new() -> Self {
661 Default::default()
662 }
663
664 pub fn is_empty(&self) -> bool {
666 let Self { invite_state } = self;
667 invite_state.is_empty()
668 }
669}
670
671impl From<InviteState> for InvitedRoom {
672 fn from(invite_state: InviteState) -> Self {
673 InvitedRoom { invite_state, ..Default::default() }
674 }
675}
676
677#[derive(Clone, Debug, Default, Deserialize, Serialize)]
679#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
680pub struct InviteState {
681 #[serde(default, skip_serializing_if = "Vec::is_empty")]
683 pub events: Vec<Raw<AnyStrippedStateEvent>>,
684}
685
686impl InviteState {
687 pub fn new() -> Self {
689 Default::default()
690 }
691
692 pub fn is_empty(&self) -> bool {
694 let Self { events } = self;
695 events.is_empty()
696 }
697}
698
699impl From<Vec<Raw<AnyStrippedStateEvent>>> for InviteState {
700 fn from(events: Vec<Raw<AnyStrippedStateEvent>>) -> Self {
701 InviteState { events, ..Default::default() }
702 }
703}
704
705#[derive(Clone, Debug, Default, Deserialize, Serialize)]
707#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
708pub struct Presence {
709 #[serde(default, skip_serializing_if = "Vec::is_empty")]
711 pub events: Vec<Raw<PresenceEvent>>,
712}
713
714impl Presence {
715 pub fn new() -> Self {
717 Default::default()
718 }
719
720 pub fn is_empty(&self) -> bool {
722 let Self { events } = self;
723 events.is_empty()
724 }
725}
726
727#[derive(Clone, Debug, Default, Deserialize, Serialize)]
729#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
730pub struct ToDevice {
731 #[serde(default, skip_serializing_if = "Vec::is_empty")]
733 pub events: Vec<Raw<AnyToDeviceEvent>>,
734}
735
736impl ToDevice {
737 pub fn new() -> Self {
739 Default::default()
740 }
741
742 pub fn is_empty(&self) -> bool {
744 let Self { events } = self;
745 events.is_empty()
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use assign::assign;
752 use ruma_common::canonical_json::assert_to_canonical_json_eq;
753 use serde_json::{from_value as from_json_value, json};
754
755 use super::Timeline;
756
757 #[test]
758 fn timeline_serde() {
759 let timeline = assign!(Timeline::new(), { limited: true });
760 let timeline_serialized = json!({ "events": [], "limited": true });
761 assert_to_canonical_json_eq!(timeline, timeline_serialized.clone());
762
763 let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
764 assert!(timeline_deserialized.limited);
765
766 let timeline_default = Timeline::default();
767 assert_to_canonical_json_eq!(timeline_default, json!({ "events": [] }));
768
769 let timeline_default_deserialized =
770 from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
771 assert!(!timeline_default_deserialized.limited);
772 }
773
774 #[cfg(feature = "unstable-msc4354")]
775 #[test]
776 fn joined_room_sticky_section_serde() {
777 use ruma_events::{AnySyncMessageLikeEvent, AnySyncTimelineEvent, SyncMessageLikeEvent};
778 use serde_json::to_value as to_json_value;
779 use strass::assert_let;
780
781 use super::JoinedRoom;
782
783 let sticky_event = json!({
784 "content": { "body": "sticky", "msgtype": "m.text" },
785 "event_id": "$1:example.com",
786 "origin_server_ts": 1,
787 "sender": "@alice:example.com",
788 "type": "m.room.message",
789 "msc4354_sticky": {
790 "duration_ms": 300_000
791 },
792 "unsigned": { "sticky_duration_ttl_ms": 258_113 }
793 });
794
795 let joined_room = from_json_value::<JoinedRoom>(json!({
797 "msc4354_sticky": { "events": [sticky_event] }
798 }))
799 .unwrap();
800 assert_eq!(joined_room.sticky.events.len(), 1);
801
802 let joined_room = from_json_value::<JoinedRoom>(json!({
805 "sticky": { "events": [] },
806 "msc4354_sticky": { "events": [sticky_event] },
807 }))
808 .unwrap();
809 assert_eq!(joined_room.sticky.events.len(), 1);
810 let event_raw = joined_room.sticky.events.first().unwrap();
811
812 assert_let!(
813 AnySyncTimelineEvent::MessageLike(AnySyncMessageLikeEvent::RoomMessage(
814 SyncMessageLikeEvent::Original(ev)
815 )) = event_raw.deserialize().unwrap()
816 );
817
818 let duration = ev.sticky.map(|s| s.duration_ms.get());
819 assert_eq!(duration, Some(300_000));
820
821 let serialized = to_json_value(&joined_room).unwrap();
823 assert!(serialized.get("msc4354_sticky").is_some());
824 assert!(serialized.get("sticky").is_none());
825 }
826}
827
828#[cfg(all(test, feature = "client"))]
829mod client_tests {
830 use std::{borrow::Cow, time::Duration};
831
832 use ruma_common::{
833 RoomVersionId,
834 api::{
835 IncomingResponseExt as _, MatrixVersion, OutgoingRequestExt as _, SupportedVersions,
836 auth_scheme::SendAccessToken,
837 },
838 event_id, room_id, user_id,
839 };
840 use ruma_events::AnyStrippedStateEvent;
841 use serde_json::{Value as JsonValue, json};
842 use strass::assert_let;
843
844 use super::{Filter, PresenceState, Request, Response, State};
845
846 fn sync_state_event() -> JsonValue {
847 json!({
848 "content": {
849 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
850 "displayname": "Alice Margatroid",
851 "membership": "join",
852 },
853 "event_id": "$143273582443PhrSn",
854 "origin_server_ts": 1_432_735_824,
855 "sender": "@alice:example.org",
856 "state_key": "@alice:example.org",
857 "type": "m.room.member",
858 "unsigned": {
859 "age": 1234,
860 "membership": "join",
861 },
862 })
863 }
864
865 #[test]
866 fn serialize_request_all_params() {
867 let supported = SupportedVersions {
868 versions: [MatrixVersion::V1_1].into(),
869 features: Default::default(),
870 };
871 let req: http::Request<Vec<u8>> = Request {
872 filter: Some(Filter::FilterId("66696p746572".to_owned())),
873 since: Some("s72594_4483_1934".to_owned()),
874 full_state: true,
875 set_presence: PresenceState::Offline,
876 timeout: Some(Duration::from_millis(30000)),
877 use_state_after: true,
878 }
879 .try_into_http_request(
880 "https://homeserver.tld",
881 SendAccessToken::IfRequired("auth_tok"),
882 Cow::Owned(supported),
883 )
884 .unwrap();
885
886 let uri = req.uri();
887 let query = uri.query().unwrap();
888
889 assert_eq!(uri.path(), "/_matrix/client/v3/sync");
890 assert!(query.contains("filter=66696p746572"));
891 assert!(query.contains("since=s72594_4483_1934"));
892 assert!(query.contains("full_state=true"));
893 assert!(query.contains("set_presence=offline"));
894 assert!(query.contains("timeout=30000"));
895 assert!(query.contains("use_state_after=true"));
896 }
897
898 #[test]
899 fn deserialize_response_invite() {
900 let creator = user_id!("@creator:localhost");
901 let invitee = user_id!("@invitee:localhost");
902 let room_id = room_id!("!privateroom:localhost");
903 let event_id = event_id!("$invite");
904
905 let body = json!({
906 "next_batch": "a00",
907 "rooms": {
908 "invite": {
909 room_id: {
910 "invite_state": {
911 "events": [
912 {
913 "content": {
914 "room_version": "11",
915 },
916 "type": "m.room.create",
917 "state_key": "",
918 "sender": creator,
919 },
920 {
921 "content": {
922 "membership": "invite",
923 },
924 "type": "m.room.member",
925 "state_key": invitee,
926 "sender": creator,
927 "origin_server_ts": 4_345_456,
928 "event_id": event_id,
929 },
930 ],
931 },
932 },
933 },
934 },
935 })
936 .to_string();
937 let http_response = http::Response::new(body.as_bytes());
938
939 let response = Response::try_from_http_response(http_response).unwrap();
940 assert_eq!(response.next_batch, "a00");
941 let private_room = response.rooms.invite.get(room_id).unwrap();
942
943 let first_event = private_room.invite_state.events[0].deserialize().unwrap();
944 assert_let!(AnyStrippedStateEvent::RoomCreate(create_event) = first_event);
945 assert_eq!(create_event.sender, creator);
946 assert_eq!(create_event.content.room_version, RoomVersionId::V11);
947 }
948
949 #[test]
950 fn deserialize_response_no_state() {
951 let joined_room_id = room_id!("!joined:localhost");
952 let left_room_id = room_id!("!left:localhost");
953 let event = sync_state_event();
954
955 let body = json!({
956 "next_batch": "aaa",
957 "rooms": {
958 "join": {
959 joined_room_id: {
960 "timeline": {
961 "events": [
962 event,
963 ],
964 },
965 },
966 },
967 "leave": {
968 left_room_id: {
969 "timeline": {
970 "events": [
971 event,
972 ],
973 },
974 },
975 },
976 },
977 })
978 .to_string();
979
980 let http_response = http::Response::new(body.as_bytes());
981
982 let response = Response::try_from_http_response(http_response).unwrap();
983 assert_eq!(response.next_batch, "aaa");
984
985 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
986 assert_eq!(joined_room.timeline.events.len(), 1);
987 assert!(joined_room.state.is_before_and_empty());
988
989 let left_room = response.rooms.leave.get(left_room_id).unwrap();
990 assert_eq!(left_room.timeline.events.len(), 1);
991 assert!(left_room.state.is_before_and_empty());
992 }
993
994 #[test]
995 fn deserialize_response_state_before() {
996 let joined_room_id = room_id!("!joined:localhost");
997 let left_room_id = room_id!("!left:localhost");
998 let event = sync_state_event();
999
1000 let body = json!({
1001 "next_batch": "aaa",
1002 "rooms": {
1003 "join": {
1004 joined_room_id: {
1005 "state": {
1006 "events": [
1007 event,
1008 ],
1009 },
1010 },
1011 },
1012 "leave": {
1013 left_room_id: {
1014 "state": {
1015 "events": [
1016 event,
1017 ],
1018 },
1019 },
1020 },
1021 },
1022 })
1023 .to_string();
1024
1025 let http_response = http::Response::new(body.as_bytes());
1026
1027 let response = Response::try_from_http_response(http_response).unwrap();
1028 assert_eq!(response.next_batch, "aaa");
1029
1030 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
1031 assert!(joined_room.timeline.is_empty());
1032 assert_let!(State::Before(state) = &joined_room.state);
1033 assert_eq!(state.events.len(), 1);
1034
1035 let left_room = response.rooms.leave.get(left_room_id).unwrap();
1036 assert!(left_room.timeline.is_empty());
1037 assert_let!(State::Before(state) = &left_room.state);
1038 assert_eq!(state.events.len(), 1);
1039 }
1040
1041 #[test]
1042 fn deserialize_response_empty_state_after() {
1043 let joined_room_id = room_id!("!joined:localhost");
1044 let left_room_id = room_id!("!left:localhost");
1045
1046 let body = json!({
1047 "next_batch": "aaa",
1048 "rooms": {
1049 "join": {
1050 joined_room_id: {
1051 "state_after": {},
1052 },
1053 },
1054 "leave": {
1055 left_room_id: {
1056 "state_after": {},
1057 },
1058 },
1059 },
1060 })
1061 .to_string();
1062
1063 let http_response = http::Response::new(body.as_bytes());
1064
1065 let response = Response::try_from_http_response(http_response).unwrap();
1066 assert_eq!(response.next_batch, "aaa");
1067
1068 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
1069 assert!(joined_room.timeline.is_empty());
1070 assert_let!(State::After(state) = &joined_room.state);
1071 assert_eq!(state.events.len(), 0);
1072
1073 let left_room = response.rooms.leave.get(left_room_id).unwrap();
1074 assert!(left_room.timeline.is_empty());
1075 assert_let!(State::After(state) = &left_room.state);
1076 assert_eq!(state.events.len(), 0);
1077 }
1078
1079 #[test]
1080 fn deserialize_response_non_empty_state_after() {
1081 let joined_room_id = room_id!("!joined:localhost");
1082 let left_room_id = room_id!("!left:localhost");
1083 let event = sync_state_event();
1084
1085 let body = json!({
1086 "next_batch": "aaa",
1087 "rooms": {
1088 "join": {
1089 joined_room_id: {
1090 "state_after": {
1091 "events": [
1092 event,
1093 ],
1094 },
1095 },
1096 },
1097 "leave": {
1098 left_room_id: {
1099 "state_after": {
1100 "events": [
1101 event,
1102 ],
1103 },
1104 },
1105 },
1106 },
1107 })
1108 .to_string();
1109
1110 let http_response = http::Response::new(body.as_bytes());
1111
1112 let response = Response::try_from_http_response(http_response).unwrap();
1113 assert_eq!(response.next_batch, "aaa");
1114
1115 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
1116 assert!(joined_room.timeline.is_empty());
1117 assert_let!(State::After(state) = &joined_room.state);
1118 assert_eq!(state.events.len(), 1);
1119
1120 let left_room = response.rooms.leave.get(left_room_id).unwrap();
1121 assert!(left_room.timeline.is_empty());
1122 assert_let!(State::After(state) = &left_room.state);
1123 assert_eq!(state.events.len(), 1);
1124 }
1125}
1126
1127#[cfg(all(test, feature = "server"))]
1128mod server_tests {
1129 use std::time::Duration;
1130
1131 use assert_matches::assert_matches;
1132 use ruma_common::{
1133 api::{IncomingRequestExt as _, OutgoingResponseExt as _},
1134 owned_room_id,
1135 presence::PresenceState,
1136 serde::Raw,
1137 };
1138 use ruma_events::{AnyStrippedStateEvent, AnySyncStateEvent};
1139 use serde_json::{Value as JsonValue, from_slice as from_json_slice, json};
1140 use strass::assert_let;
1141
1142 use super::{Filter, JoinedRoom, KnockedRoom, LeftRoom, Request, Response, State};
1143
1144 fn sync_state_event() -> Raw<AnySyncStateEvent> {
1145 Raw::new(&json!({
1146 "content": {
1147 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1148 "displayname": "Alice Margatroid",
1149 "membership": "join",
1150 },
1151 "event_id": "$143273582443PhrSn",
1152 "origin_server_ts": 1_432_735_824,
1153 "sender": "@alice:example.org",
1154 "state_key": "@alice:example.org",
1155 "type": "m.room.member",
1156 "unsigned": {
1157 "age": 1234,
1158 "membership": "join",
1159 },
1160 }))
1161 .unwrap()
1162 .cast_unchecked()
1163 }
1164
1165 #[test]
1166 fn deserialize_request_all_query_params() {
1167 let uri = http::Uri::builder()
1168 .scheme("https")
1169 .authority("matrix.org")
1170 .path_and_query(
1171 "/_matrix/client/r0/sync\
1172 ?filter=myfilter\
1173 &since=myts\
1174 &full_state=false\
1175 &set_presence=offline\
1176 &timeout=5000",
1177 )
1178 .build()
1179 .unwrap();
1180
1181 let req = Request::try_from_http_request(
1182 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1183 &[],
1184 )
1185 .unwrap();
1186
1187 assert_let!(Some(Filter::FilterId(id)) = req.filter);
1188 assert_eq!(id, "myfilter");
1189 assert_eq!(req.since.as_deref(), Some("myts"));
1190 assert!(!req.full_state);
1191 assert_eq!(req.set_presence, PresenceState::Offline);
1192 assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
1193 }
1194
1195 #[test]
1196 fn deserialize_request_no_query_params() {
1197 let uri = http::Uri::builder()
1198 .scheme("https")
1199 .authority("matrix.org")
1200 .path_and_query("/_matrix/client/r0/sync")
1201 .build()
1202 .unwrap();
1203
1204 let req = Request::try_from_http_request(
1205 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1206 &[],
1207 )
1208 .unwrap();
1209
1210 assert_matches!(req.filter, None);
1211 assert_eq!(req.since, None);
1212 assert!(!req.full_state);
1213 assert_eq!(req.set_presence, PresenceState::Online);
1214 assert_eq!(req.timeout, None);
1215 }
1216
1217 #[test]
1218 fn deserialize_request_some_query_params() {
1219 let uri = http::Uri::builder()
1220 .scheme("https")
1221 .authority("matrix.org")
1222 .path_and_query(
1223 "/_matrix/client/r0/sync\
1224 ?filter=EOKFFmdZYF\
1225 &timeout=0",
1226 )
1227 .build()
1228 .unwrap();
1229
1230 let req = Request::try_from_http_request(
1231 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1232 &[],
1233 )
1234 .unwrap();
1235
1236 assert_let!(Some(Filter::FilterId(id)) = req.filter);
1237 assert_eq!(id, "EOKFFmdZYF");
1238 assert_eq!(req.since, None);
1239 assert!(!req.full_state);
1240 assert_eq!(req.set_presence, PresenceState::Online);
1241 assert_eq!(req.timeout, Some(Duration::from_millis(0)));
1242 }
1243
1244 #[test]
1245 fn serialize_response_no_state() {
1246 let joined_room_id = owned_room_id!("!joined:localhost");
1247 let left_room_id = owned_room_id!("!left:localhost");
1248 let event = sync_state_event();
1249
1250 let mut response = Response::new("aaa".to_owned());
1251
1252 let mut joined_room = JoinedRoom::new();
1253 joined_room.timeline.events.push(event.clone().cast());
1254 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1255
1256 let mut left_room = LeftRoom::new();
1257 left_room.timeline.events.push(event.clone().cast());
1258 response.rooms.leave.insert(left_room_id.clone(), left_room);
1259
1260 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1261
1262 assert_eq!(
1263 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1264 json!({
1265 "next_batch": "aaa",
1266 "rooms": {
1267 "join": {
1268 joined_room_id: {
1269 "timeline": {
1270 "events": [
1271 event,
1272 ],
1273 },
1274 },
1275 },
1276 "leave": {
1277 left_room_id: {
1278 "timeline": {
1279 "events": [
1280 event,
1281 ],
1282 },
1283 },
1284 },
1285 },
1286 })
1287 );
1288 }
1289
1290 #[test]
1291 fn serialize_response_state_before() {
1292 let joined_room_id = owned_room_id!("!joined:localhost");
1293 let left_room_id = owned_room_id!("!left:localhost");
1294 let event = sync_state_event();
1295
1296 let mut response = Response::new("aaa".to_owned());
1297
1298 let mut joined_room = JoinedRoom::new();
1299 joined_room.state = State::Before(vec![event.clone()].into());
1300 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1301
1302 let mut left_room = LeftRoom::new();
1303 left_room.state = State::Before(vec![event.clone()].into());
1304 response.rooms.leave.insert(left_room_id.clone(), left_room);
1305
1306 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1307
1308 assert_eq!(
1309 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1310 json!({
1311 "next_batch": "aaa",
1312 "rooms": {
1313 "join": {
1314 joined_room_id: {
1315 "state": {
1316 "events": [
1317 event,
1318 ],
1319 },
1320 },
1321 },
1322 "leave": {
1323 left_room_id: {
1324 "state": {
1325 "events": [
1326 event,
1327 ],
1328 },
1329 },
1330 },
1331 },
1332 })
1333 );
1334 }
1335
1336 #[test]
1337 fn serialize_response_empty_state_after() {
1338 let joined_room_id = owned_room_id!("!joined:localhost");
1339 let left_room_id = owned_room_id!("!left:localhost");
1340
1341 let mut response = Response::new("aaa".to_owned());
1342
1343 let mut joined_room = JoinedRoom::new();
1344 joined_room.state = State::After(Default::default());
1345 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1346
1347 let mut left_room = LeftRoom::new();
1348 left_room.state = State::After(Default::default());
1349 response.rooms.leave.insert(left_room_id.clone(), left_room);
1350
1351 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1352
1353 assert_eq!(
1354 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1355 json!({
1356 "next_batch": "aaa",
1357 "rooms": {
1358 "join": {
1359 joined_room_id: {
1360 "state_after": {},
1361 },
1362 },
1363 "leave": {
1364 left_room_id: {
1365 "state_after": {},
1366 },
1367 },
1368 },
1369 })
1370 );
1371 }
1372
1373 #[test]
1374 fn serialize_response_non_empty_state_after() {
1375 let joined_room_id = owned_room_id!("!joined:localhost");
1376 let left_room_id = owned_room_id!("!left:localhost");
1377 let event = sync_state_event();
1378
1379 let mut response = Response::new("aaa".to_owned());
1380
1381 let mut joined_room = JoinedRoom::new();
1382 joined_room.state = State::After(vec![event.clone()].into());
1383 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1384
1385 let mut left_room = LeftRoom::new();
1386 left_room.state = State::After(vec![event.clone()].into());
1387 response.rooms.leave.insert(left_room_id.clone(), left_room);
1388
1389 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1390
1391 assert_eq!(
1392 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1393 json!({
1394 "next_batch": "aaa",
1395 "rooms": {
1396 "join": {
1397 joined_room_id: {
1398 "state_after": {
1399 "events": [
1400 event,
1401 ],
1402 },
1403 },
1404 },
1405 "leave": {
1406 left_room_id: {
1407 "state_after": {
1408 "events": [
1409 event,
1410 ],
1411 },
1412 },
1413 },
1414 },
1415 })
1416 );
1417 }
1418
1419 #[test]
1420 fn serialize_response_knocked_room() {
1421 let knocked_room_id = owned_room_id!("!knocked:localhost");
1422 let event: Raw<AnyStrippedStateEvent> = Raw::new(&json!({
1423 "content": {
1424 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1425 "displayname": "Alice Margatroid",
1426 "membership": "join",
1427 },
1428 "sender": "@alice:example.org",
1429 "state_key": "@alice:example.org",
1430 "type": "m.room.member",
1431 }))
1432 .unwrap()
1433 .cast_unchecked();
1434
1435 let mut response = Response::new("aaa".to_owned());
1436
1437 let mut knocked_room = KnockedRoom::new();
1438 knocked_room.knock_state.events.push(event.clone());
1439 response.rooms.knock.insert(knocked_room_id.clone(), knocked_room);
1440
1441 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1442
1443 assert_eq!(
1444 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1445 json!({
1446 "next_batch": "aaa",
1447 "rooms": {
1448 "knock": {
1449 knocked_room_id: {
1450 "knock_state": {
1451 "events": [
1452 event,
1453 ],
1454 },
1455 },
1456 },
1457 },
1458 })
1459 );
1460 }
1461}