1use std::{collections::BTreeMap, time::Duration};
6
7use as_variant::as_variant;
8use js_int::UInt;
9use ruma_common::{
10 api::{request, response},
11 metadata,
12 presence::PresenceState,
13 serde::Raw,
14 OneTimeKeyAlgorithm, OwnedEventId, OwnedRoomId, OwnedUserId,
15};
16use ruma_events::{
17 presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
18 AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnySyncTimelineEvent,
19 AnyToDeviceEvent,
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(error = crate::Error)]
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(error = crate::Error)]
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 self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
213 }
214}
215
216#[derive(Clone, Debug, Default, Serialize)]
218#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
219pub struct LeftRoom {
220 #[serde(skip_serializing_if = "Timeline::is_empty")]
223 pub timeline: Timeline,
224
225 #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
227 pub state: State,
228
229 #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
231 pub account_data: RoomAccountData,
232}
233
234impl LeftRoom {
235 pub fn new() -> Self {
237 Default::default()
238 }
239
240 pub fn is_empty(&self) -> bool {
242 self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
243 }
244}
245
246#[derive(Clone, Debug, Default, Serialize)]
248#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
249pub struct JoinedRoom {
250 #[serde(skip_serializing_if = "RoomSummary::is_empty")]
253 pub summary: RoomSummary,
254
255 #[serde(skip_serializing_if = "UnreadNotificationsCount::is_empty")]
263 pub unread_notifications: UnreadNotificationsCount,
264
265 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
274 pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
275
276 #[serde(skip_serializing_if = "Timeline::is_empty")]
278 pub timeline: Timeline,
279
280 #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
284 pub state: State,
285
286 #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
288 pub account_data: RoomAccountData,
289
290 #[serde(skip_serializing_if = "Ephemeral::is_empty")]
293 pub ephemeral: Ephemeral,
294
295 #[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 pub fn new() -> Self {
308 Default::default()
309 }
310
311 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
331#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
332pub struct KnockedRoom {
333 #[serde(default, skip_serializing_if = "KnockState::is_empty")]
335 pub knock_state: KnockState,
336}
337
338impl KnockedRoom {
339 pub fn new() -> Self {
341 Default::default()
342 }
343
344 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
358#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
359pub struct KnockState {
360 #[serde(default, skip_serializing_if = "Vec::is_empty")]
362 pub events: Vec<Raw<AnyStrippedStateEvent>>,
363}
364
365impl KnockState {
366 pub fn new() -> Self {
368 Default::default()
369 }
370
371 pub fn is_empty(&self) -> bool {
373 self.events.is_empty()
374 }
375}
376
377#[derive(Clone, Debug, Default, Deserialize, Serialize)]
379#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
380pub struct Timeline {
381 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
385 pub limited: bool,
386
387 #[serde(skip_serializing_if = "Option::is_none")]
390 pub prev_batch: Option<String>,
391
392 pub events: Vec<Raw<AnySyncTimelineEvent>>,
394}
395
396impl Timeline {
397 pub fn new() -> Self {
399 Default::default()
400 }
401
402 pub fn is_empty(&self) -> bool {
407 !self.limited && self.prev_batch.is_none() && self.events.is_empty()
408 }
409}
410
411#[derive(Clone, Debug, Serialize)]
413#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
414pub enum State {
415 #[serde(rename = "state")]
423 Before(StateEvents),
424
425 #[serde(rename = "state_after")]
432 After(StateEvents),
433}
434
435impl State {
436 fn is_before_and_empty(&self) -> bool {
438 as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
439 }
440
441 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
458#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
459pub struct StateEvents {
460 #[serde(default, skip_serializing_if = "Vec::is_empty")]
462 pub events: Vec<Raw<AnySyncStateEvent>>,
463}
464
465impl StateEvents {
466 pub fn new() -> Self {
468 Default::default()
469 }
470
471 pub fn is_empty(&self) -> bool {
473 self.events.is_empty()
474 }
475
476 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
490#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
491pub struct GlobalAccountData {
492 #[serde(default, skip_serializing_if = "Vec::is_empty")]
494 pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
495}
496
497impl GlobalAccountData {
498 pub fn new() -> Self {
500 Default::default()
501 }
502
503 pub fn is_empty(&self) -> bool {
505 self.events.is_empty()
506 }
507}
508
509#[derive(Clone, Debug, Default, Deserialize, Serialize)]
511#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
512pub struct RoomAccountData {
513 #[serde(default, skip_serializing_if = "Vec::is_empty")]
515 pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
516}
517
518impl RoomAccountData {
519 pub fn new() -> Self {
521 Default::default()
522 }
523
524 pub fn is_empty(&self) -> bool {
526 self.events.is_empty()
527 }
528}
529
530#[derive(Clone, Debug, Default, Deserialize, Serialize)]
532#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
533pub struct Ephemeral {
534 #[serde(default, skip_serializing_if = "Vec::is_empty")]
536 pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
537}
538
539impl Ephemeral {
540 pub fn new() -> Self {
542 Default::default()
543 }
544
545 pub fn is_empty(&self) -> bool {
547 self.events.is_empty()
548 }
549}
550
551#[derive(Clone, Debug, Default, Deserialize, Serialize)]
553#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
554pub struct RoomSummary {
555 #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
559 pub heroes: Vec<OwnedUserId>,
560
561 #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
565 pub joined_member_count: Option<UInt>,
566
567 #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
571 pub invited_member_count: Option<UInt>,
572}
573
574impl RoomSummary {
575 pub fn new() -> Self {
577 Default::default()
578 }
579
580 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
590#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
591pub struct InvitedRoom {
592 #[serde(default, skip_serializing_if = "InviteState::is_empty")]
594 pub invite_state: InviteState,
595}
596
597impl InvitedRoom {
598 pub fn new() -> Self {
600 Default::default()
601 }
602
603 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
617#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
618pub struct InviteState {
619 #[serde(default, skip_serializing_if = "Vec::is_empty")]
621 pub events: Vec<Raw<AnyStrippedStateEvent>>,
622}
623
624impl InviteState {
625 pub fn new() -> Self {
627 Default::default()
628 }
629
630 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#[derive(Clone, Debug, Default, Deserialize, Serialize)]
644#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
645pub struct Presence {
646 #[serde(default, skip_serializing_if = "Vec::is_empty")]
648 pub events: Vec<Raw<PresenceEvent>>,
649}
650
651impl Presence {
652 pub fn new() -> Self {
654 Default::default()
655 }
656
657 pub fn is_empty(&self) -> bool {
659 self.events.is_empty()
660 }
661}
662
663#[derive(Clone, Debug, Default, Deserialize, Serialize)]
665#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
666pub struct ToDevice {
667 #[serde(default, skip_serializing_if = "Vec::is_empty")]
669 pub events: Vec<Raw<AnyToDeviceEvent>>,
670}
671
672impl ToDevice {
673 pub fn new() -> Self {
675 Default::default()
676 }
677
678 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::time::Duration;
712
713 use assert_matches2::assert_matches;
714 use ruma_common::{
715 api::{
716 IncomingResponse as _, MatrixVersion, OutgoingRequest as _, SendAccessToken,
717 SupportedVersions,
718 },
719 event_id, room_id, user_id, RoomVersionId,
720 };
721 use ruma_events::AnyStrippedStateEvent;
722 use serde_json::{json, to_vec as to_json_vec, Value as JsonValue};
723
724 use super::{Filter, PresenceState, Request, Response, State};
725
726 fn sync_state_event() -> JsonValue {
727 json!({
728 "content": {
729 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
730 "displayname": "Alice Margatroid",
731 "membership": "join",
732 },
733 "event_id": "$143273582443PhrSn",
734 "origin_server_ts": 1_432_735_824,
735 "sender": "@alice:example.org",
736 "state_key": "@alice:example.org",
737 "type": "m.room.member",
738 "unsigned": {
739 "age": 1234,
740 "membership": "join",
741 },
742 })
743 }
744
745 #[test]
746 fn serialize_request_all_params() {
747 let supported = SupportedVersions {
748 versions: [MatrixVersion::V1_1].into(),
749 features: Default::default(),
750 };
751 let req: http::Request<Vec<u8>> = Request {
752 filter: Some(Filter::FilterId("66696p746572".to_owned())),
753 since: Some("s72594_4483_1934".to_owned()),
754 full_state: true,
755 set_presence: PresenceState::Offline,
756 timeout: Some(Duration::from_millis(30000)),
757 use_state_after: true,
758 }
759 .try_into_http_request(
760 "https://homeserver.tld",
761 SendAccessToken::IfRequired("auth_tok"),
762 &supported,
763 )
764 .unwrap();
765
766 let uri = req.uri();
767 let query = uri.query().unwrap();
768
769 assert_eq!(uri.path(), "/_matrix/client/v3/sync");
770 assert!(query.contains("filter=66696p746572"));
771 assert!(query.contains("since=s72594_4483_1934"));
772 assert!(query.contains("full_state=true"));
773 assert!(query.contains("set_presence=offline"));
774 assert!(query.contains("timeout=30000"));
775 assert!(query.contains("use_state_after=true"));
776 }
777
778 #[test]
779 fn deserialize_response_invite() {
780 let creator = user_id!("@creator:localhost");
781 let invitee = user_id!("@invitee:localhost");
782 let room_id = room_id!("!privateroom:localhost");
783 let event_id = event_id!("$invite");
784
785 let body = json!({
786 "next_batch": "a00",
787 "rooms": {
788 "invite": {
789 room_id: {
790 "invite_state": {
791 "events": [
792 {
793 "content": {
794 "room_version": "11",
795 },
796 "type": "m.room.create",
797 "state_key": "",
798 "sender": creator,
799 },
800 {
801 "content": {
802 "membership": "invite",
803 },
804 "type": "m.room.member",
805 "state_key": invitee,
806 "sender": creator,
807 "origin_server_ts": 4_345_456,
808 "event_id": event_id,
809 },
810 ],
811 },
812 },
813 },
814 },
815 });
816 let http_response = http::Response::new(to_json_vec(&body).unwrap());
817
818 let response = Response::try_from_http_response(http_response).unwrap();
819 assert_eq!(response.next_batch, "a00");
820 let private_room = response.rooms.invite.get(room_id).unwrap();
821
822 let first_event = private_room.invite_state.events[0].deserialize().unwrap();
823 assert_matches!(first_event, AnyStrippedStateEvent::RoomCreate(create_event));
824 assert_eq!(create_event.sender, creator);
825 assert_eq!(create_event.content.room_version, RoomVersionId::V11);
826 }
827
828 #[test]
829 fn deserialize_response_no_state() {
830 let joined_room_id = room_id!("!joined:localhost");
831 let left_room_id = room_id!("!left:localhost");
832 let event = sync_state_event();
833
834 let body = json!({
835 "next_batch": "aaa",
836 "rooms": {
837 "join": {
838 joined_room_id: {
839 "timeline": {
840 "events": [
841 event,
842 ],
843 },
844 },
845 },
846 "leave": {
847 left_room_id: {
848 "timeline": {
849 "events": [
850 event,
851 ],
852 },
853 },
854 },
855 },
856 });
857
858 let http_response = http::Response::new(to_json_vec(&body).unwrap());
859
860 let response = Response::try_from_http_response(http_response).unwrap();
861 assert_eq!(response.next_batch, "aaa");
862
863 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
864 assert_eq!(joined_room.timeline.events.len(), 1);
865 assert!(joined_room.state.is_before_and_empty());
866
867 let left_room = response.rooms.leave.get(left_room_id).unwrap();
868 assert_eq!(left_room.timeline.events.len(), 1);
869 assert!(left_room.state.is_before_and_empty());
870 }
871
872 #[test]
873 fn deserialize_response_state_before() {
874 let joined_room_id = room_id!("!joined:localhost");
875 let left_room_id = room_id!("!left:localhost");
876 let event = sync_state_event();
877
878 let body = json!({
879 "next_batch": "aaa",
880 "rooms": {
881 "join": {
882 joined_room_id: {
883 "state": {
884 "events": [
885 event,
886 ],
887 },
888 },
889 },
890 "leave": {
891 left_room_id: {
892 "state": {
893 "events": [
894 event,
895 ],
896 },
897 },
898 },
899 },
900 });
901
902 let http_response = http::Response::new(to_json_vec(&body).unwrap());
903
904 let response = Response::try_from_http_response(http_response).unwrap();
905 assert_eq!(response.next_batch, "aaa");
906
907 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
908 assert!(joined_room.timeline.is_empty());
909 assert_matches!(&joined_room.state, State::Before(state));
910 assert_eq!(state.events.len(), 1);
911
912 let left_room = response.rooms.leave.get(left_room_id).unwrap();
913 assert!(left_room.timeline.is_empty());
914 assert_matches!(&left_room.state, State::Before(state));
915 assert_eq!(state.events.len(), 1);
916 }
917
918 #[test]
919 fn deserialize_response_empty_state_after() {
920 let joined_room_id = room_id!("!joined:localhost");
921 let left_room_id = room_id!("!left:localhost");
922
923 let body = json!({
924 "next_batch": "aaa",
925 "rooms": {
926 "join": {
927 joined_room_id: {
928 "state_after": {},
929 },
930 },
931 "leave": {
932 left_room_id: {
933 "state_after": {},
934 },
935 },
936 },
937 });
938
939 let http_response = http::Response::new(to_json_vec(&body).unwrap());
940
941 let response = Response::try_from_http_response(http_response).unwrap();
942 assert_eq!(response.next_batch, "aaa");
943
944 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
945 assert!(joined_room.timeline.is_empty());
946 assert_matches!(&joined_room.state, State::After(state));
947 assert_eq!(state.events.len(), 0);
948
949 let left_room = response.rooms.leave.get(left_room_id).unwrap();
950 assert!(left_room.timeline.is_empty());
951 assert_matches!(&left_room.state, State::After(state));
952 assert_eq!(state.events.len(), 0);
953 }
954
955 #[test]
956 fn deserialize_response_non_empty_state_after() {
957 let joined_room_id = room_id!("!joined:localhost");
958 let left_room_id = room_id!("!left:localhost");
959 let event = sync_state_event();
960
961 let body = json!({
962 "next_batch": "aaa",
963 "rooms": {
964 "join": {
965 joined_room_id: {
966 "state_after": {
967 "events": [
968 event,
969 ],
970 },
971 },
972 },
973 "leave": {
974 left_room_id: {
975 "state_after": {
976 "events": [
977 event,
978 ],
979 },
980 },
981 },
982 },
983 });
984
985 let http_response = http::Response::new(to_json_vec(&body).unwrap());
986
987 let response = Response::try_from_http_response(http_response).unwrap();
988 assert_eq!(response.next_batch, "aaa");
989
990 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
991 assert!(joined_room.timeline.is_empty());
992 assert_matches!(&joined_room.state, State::After(state));
993 assert_eq!(state.events.len(), 1);
994
995 let left_room = response.rooms.leave.get(left_room_id).unwrap();
996 assert!(left_room.timeline.is_empty());
997 assert_matches!(&left_room.state, State::After(state));
998 assert_eq!(state.events.len(), 1);
999 }
1000}
1001
1002#[cfg(all(test, feature = "server"))]
1003mod server_tests {
1004 use std::time::Duration;
1005
1006 use assert_matches2::assert_matches;
1007 use ruma_common::{
1008 api::{IncomingRequest as _, OutgoingResponse as _},
1009 owned_room_id,
1010 presence::PresenceState,
1011 serde::Raw,
1012 };
1013 use ruma_events::AnySyncStateEvent;
1014 use serde_json::{from_slice as from_json_slice, json, Value as JsonValue};
1015
1016 use super::{Filter, JoinedRoom, LeftRoom, Request, Response, State};
1017
1018 fn sync_state_event() -> Raw<AnySyncStateEvent> {
1019 Raw::new(&json!({
1020 "content": {
1021 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1022 "displayname": "Alice Margatroid",
1023 "membership": "join",
1024 },
1025 "event_id": "$143273582443PhrSn",
1026 "origin_server_ts": 1_432_735_824,
1027 "sender": "@alice:example.org",
1028 "state_key": "@alice:example.org",
1029 "type": "m.room.member",
1030 "unsigned": {
1031 "age": 1234,
1032 "membership": "join",
1033 },
1034 }))
1035 .unwrap()
1036 .cast_unchecked()
1037 }
1038
1039 #[test]
1040 fn deserialize_request_all_query_params() {
1041 let uri = http::Uri::builder()
1042 .scheme("https")
1043 .authority("matrix.org")
1044 .path_and_query(
1045 "/_matrix/client/r0/sync\
1046 ?filter=myfilter\
1047 &since=myts\
1048 &full_state=false\
1049 &set_presence=offline\
1050 &timeout=5000",
1051 )
1052 .build()
1053 .unwrap();
1054
1055 let req = Request::try_from_http_request(
1056 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1057 &[] as &[String],
1058 )
1059 .unwrap();
1060
1061 assert_matches!(req.filter, Some(Filter::FilterId(id)));
1062 assert_eq!(id, "myfilter");
1063 assert_eq!(req.since.as_deref(), Some("myts"));
1064 assert!(!req.full_state);
1065 assert_eq!(req.set_presence, PresenceState::Offline);
1066 assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
1067 }
1068
1069 #[test]
1070 fn deserialize_request_no_query_params() {
1071 let uri = http::Uri::builder()
1072 .scheme("https")
1073 .authority("matrix.org")
1074 .path_and_query("/_matrix/client/r0/sync")
1075 .build()
1076 .unwrap();
1077
1078 let req = Request::try_from_http_request(
1079 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1080 &[] as &[String],
1081 )
1082 .unwrap();
1083
1084 assert_matches!(req.filter, None);
1085 assert_eq!(req.since, None);
1086 assert!(!req.full_state);
1087 assert_eq!(req.set_presence, PresenceState::Online);
1088 assert_eq!(req.timeout, None);
1089 }
1090
1091 #[test]
1092 fn deserialize_request_some_query_params() {
1093 let uri = http::Uri::builder()
1094 .scheme("https")
1095 .authority("matrix.org")
1096 .path_and_query(
1097 "/_matrix/client/r0/sync\
1098 ?filter=EOKFFmdZYF\
1099 &timeout=0",
1100 )
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, Some(Filter::FilterId(id)));
1111 assert_eq!(id, "EOKFFmdZYF");
1112 assert_eq!(req.since, None);
1113 assert!(!req.full_state);
1114 assert_eq!(req.set_presence, PresenceState::Online);
1115 assert_eq!(req.timeout, Some(Duration::from_millis(0)));
1116 }
1117
1118 #[test]
1119 fn serialize_response_no_state() {
1120 let joined_room_id = owned_room_id!("!joined:localhost");
1121 let left_room_id = owned_room_id!("!left:localhost");
1122 let event = sync_state_event();
1123
1124 let mut response = Response::new("aaa".to_owned());
1125
1126 let mut joined_room = JoinedRoom::new();
1127 joined_room.timeline.events.push(event.clone().cast());
1128 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1129
1130 let mut left_room = LeftRoom::new();
1131 left_room.timeline.events.push(event.clone().cast());
1132 response.rooms.leave.insert(left_room_id.clone(), left_room);
1133
1134 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1135
1136 assert_eq!(
1137 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1138 json!({
1139 "next_batch": "aaa",
1140 "rooms": {
1141 "join": {
1142 joined_room_id: {
1143 "timeline": {
1144 "events": [
1145 event,
1146 ],
1147 },
1148 },
1149 },
1150 "leave": {
1151 left_room_id: {
1152 "timeline": {
1153 "events": [
1154 event,
1155 ],
1156 },
1157 },
1158 },
1159 },
1160 })
1161 );
1162 }
1163
1164 #[test]
1165 fn serialize_response_state_before() {
1166 let joined_room_id = owned_room_id!("!joined:localhost");
1167 let left_room_id = owned_room_id!("!left:localhost");
1168 let event = sync_state_event();
1169
1170 let mut response = Response::new("aaa".to_owned());
1171
1172 let mut joined_room = JoinedRoom::new();
1173 joined_room.state = State::Before(vec![event.clone()].into());
1174 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1175
1176 let mut left_room = LeftRoom::new();
1177 left_room.state = State::Before(vec![event.clone()].into());
1178 response.rooms.leave.insert(left_room_id.clone(), left_room);
1179
1180 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1181
1182 assert_eq!(
1183 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1184 json!({
1185 "next_batch": "aaa",
1186 "rooms": {
1187 "join": {
1188 joined_room_id: {
1189 "state": {
1190 "events": [
1191 event,
1192 ],
1193 },
1194 },
1195 },
1196 "leave": {
1197 left_room_id: {
1198 "state": {
1199 "events": [
1200 event,
1201 ],
1202 },
1203 },
1204 },
1205 },
1206 })
1207 );
1208 }
1209
1210 #[test]
1211 fn serialize_response_empty_state_after() {
1212 let joined_room_id = owned_room_id!("!joined:localhost");
1213 let left_room_id = owned_room_id!("!left:localhost");
1214
1215 let mut response = Response::new("aaa".to_owned());
1216
1217 let mut joined_room = JoinedRoom::new();
1218 joined_room.state = State::After(Default::default());
1219 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1220
1221 let mut left_room = LeftRoom::new();
1222 left_room.state = State::After(Default::default());
1223 response.rooms.leave.insert(left_room_id.clone(), left_room);
1224
1225 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1226
1227 assert_eq!(
1228 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1229 json!({
1230 "next_batch": "aaa",
1231 "rooms": {
1232 "join": {
1233 joined_room_id: {
1234 "state_after": {},
1235 },
1236 },
1237 "leave": {
1238 left_room_id: {
1239 "state_after": {},
1240 },
1241 },
1242 },
1243 })
1244 );
1245 }
1246
1247 #[test]
1248 fn serialize_response_non_empty_state_after() {
1249 let joined_room_id = owned_room_id!("!joined:localhost");
1250 let left_room_id = owned_room_id!("!left:localhost");
1251 let event = sync_state_event();
1252
1253 let mut response = Response::new("aaa".to_owned());
1254
1255 let mut joined_room = JoinedRoom::new();
1256 joined_room.state = State::After(vec![event.clone()].into());
1257 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1258
1259 let mut left_room = LeftRoom::new();
1260 left_room.state = State::After(vec![event.clone()].into());
1261 response.rooms.leave.insert(left_room_id.clone(), left_room);
1262
1263 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1264
1265 assert_eq!(
1266 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1267 json!({
1268 "next_batch": "aaa",
1269 "rooms": {
1270 "join": {
1271 joined_room_id: {
1272 "state_after": {
1273 "events": [
1274 event,
1275 ],
1276 },
1277 },
1278 },
1279 "leave": {
1280 left_room_id: {
1281 "state_after": {
1282 "events": [
1283 event,
1284 ],
1285 },
1286 },
1287 },
1288 },
1289 })
1290 );
1291 }
1292}