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-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 pub fn new() -> Self {
310 Default::default()
311 }
312
313 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
345#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
346pub struct KnockedRoom {
347 #[serde(default, skip_serializing_if = "KnockState::is_empty")]
349 pub knock_state: KnockState,
350}
351
352impl KnockedRoom {
353 pub fn new() -> Self {
355 Default::default()
356 }
357
358 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
373#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
374pub struct KnockState {
375 #[serde(default, skip_serializing_if = "Vec::is_empty")]
377 pub events: Vec<Raw<AnyStrippedStateEvent>>,
378}
379
380impl KnockState {
381 pub fn new() -> Self {
383 Default::default()
384 }
385
386 pub fn is_empty(&self) -> bool {
388 let Self { events } = self;
389 events.is_empty()
390 }
391}
392
393#[derive(Clone, Debug, Default, Deserialize, Serialize)]
395#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
396pub struct Timeline {
397 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
401 pub limited: bool,
402
403 #[serde(skip_serializing_if = "Option::is_none")]
406 pub prev_batch: Option<String>,
407
408 pub events: Vec<Raw<AnySyncTimelineEvent>>,
410}
411
412impl Timeline {
413 pub fn new() -> Self {
415 Default::default()
416 }
417
418 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#[derive(Clone, Debug, Serialize)]
430#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
431pub enum State {
432 #[serde(rename = "state")]
440 Before(StateEvents),
441
442 #[serde(rename = "state_after")]
449 After(StateEvents),
450}
451
452impl State {
453 fn is_before_and_empty(&self) -> bool {
455 as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
456 }
457
458 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
475#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
476pub struct StateEvents {
477 #[serde(default, skip_serializing_if = "Vec::is_empty")]
479 pub events: Vec<Raw<AnySyncStateEvent>>,
480}
481
482impl StateEvents {
483 pub fn new() -> Self {
485 Default::default()
486 }
487
488 pub fn is_empty(&self) -> bool {
490 let Self { events } = self;
491 events.is_empty()
492 }
493
494 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
508#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
509pub struct GlobalAccountData {
510 #[serde(default, skip_serializing_if = "Vec::is_empty")]
512 pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
513}
514
515impl GlobalAccountData {
516 pub fn new() -> Self {
518 Default::default()
519 }
520
521 pub fn is_empty(&self) -> bool {
523 let Self { events } = self;
524 events.is_empty()
525 }
526}
527
528#[derive(Clone, Debug, Default, Deserialize, Serialize)]
530#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
531pub struct RoomAccountData {
532 #[serde(default, skip_serializing_if = "Vec::is_empty")]
534 pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
535}
536
537impl RoomAccountData {
538 pub fn new() -> Self {
540 Default::default()
541 }
542
543 pub fn is_empty(&self) -> bool {
545 let Self { events } = self;
546 events.is_empty()
547 }
548}
549
550#[derive(Clone, Debug, Default, Deserialize, Serialize)]
552#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
553pub struct Ephemeral {
554 #[serde(default, skip_serializing_if = "Vec::is_empty")]
556 pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
557}
558
559impl Ephemeral {
560 pub fn new() -> Self {
562 Default::default()
563 }
564
565 pub fn is_empty(&self) -> bool {
567 let Self { events } = self;
568 events.is_empty()
569 }
570}
571
572#[derive(Clone, Debug, Default, Deserialize, Serialize)]
574#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
575pub struct RoomSummary {
576 #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
580 pub heroes: Vec<OwnedUserId>,
581
582 #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
586 pub joined_member_count: Option<UInt>,
587
588 #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
592 pub invited_member_count: Option<UInt>,
593}
594
595impl RoomSummary {
596 pub fn new() -> Self {
598 Default::default()
599 }
600
601 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
610#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
611pub struct InvitedRoom {
612 #[serde(default, skip_serializing_if = "InviteState::is_empty")]
614 pub invite_state: InviteState,
615}
616
617impl InvitedRoom {
618 pub fn new() -> Self {
620 Default::default()
621 }
622
623 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
638#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
639pub struct InviteState {
640 #[serde(default, skip_serializing_if = "Vec::is_empty")]
642 pub events: Vec<Raw<AnyStrippedStateEvent>>,
643}
644
645impl InviteState {
646 pub fn new() -> Self {
648 Default::default()
649 }
650
651 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
666#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
667pub struct Presence {
668 #[serde(default, skip_serializing_if = "Vec::is_empty")]
670 pub events: Vec<Raw<PresenceEvent>>,
671}
672
673impl Presence {
674 pub fn new() -> Self {
676 Default::default()
677 }
678
679 pub fn is_empty(&self) -> bool {
681 let Self { events } = self;
682 events.is_empty()
683 }
684}
685
686#[derive(Clone, Debug, Default, Deserialize, Serialize)]
688#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
689pub struct ToDevice {
690 #[serde(default, skip_serializing_if = "Vec::is_empty")]
692 pub events: Vec<Raw<AnyToDeviceEvent>>,
693}
694
695impl ToDevice {
696 pub fn new() -> Self {
698 Default::default()
699 }
700
701 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}