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(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 ruma_common::canonical_json::assert_to_canonical_json_eq;
688 use serde_json::{from_value as from_json_value, json};
689
690 use super::Timeline;
691
692 #[test]
693 fn timeline_serde() {
694 let timeline = assign!(Timeline::new(), { limited: true });
695 let timeline_serialized = json!({ "events": [], "limited": true });
696 assert_to_canonical_json_eq!(timeline, timeline_serialized.clone());
697
698 let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
699 assert!(timeline_deserialized.limited);
700
701 let timeline_default = Timeline::default();
702 assert_to_canonical_json_eq!(timeline_default, json!({ "events": [] }));
703
704 let timeline_default_deserialized =
705 from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
706 assert!(!timeline_default_deserialized.limited);
707 }
708}
709
710#[cfg(all(test, feature = "client"))]
711mod client_tests {
712 use std::{borrow::Cow, time::Duration};
713
714 use assert_matches2::assert_matches;
715 use ruma_common::{
716 RoomVersionId,
717 api::{
718 IncomingResponse as _, MatrixVersion, OutgoingRequest as _, SupportedVersions,
719 auth_scheme::SendAccessToken,
720 },
721 event_id, room_id, user_id,
722 };
723 use ruma_events::AnyStrippedStateEvent;
724 use serde_json::{Value as JsonValue, json, to_vec as to_json_vec};
725
726 use super::{Filter, PresenceState, Request, Response, State};
727
728 fn sync_state_event() -> JsonValue {
729 json!({
730 "content": {
731 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
732 "displayname": "Alice Margatroid",
733 "membership": "join",
734 },
735 "event_id": "$143273582443PhrSn",
736 "origin_server_ts": 1_432_735_824,
737 "sender": "@alice:example.org",
738 "state_key": "@alice:example.org",
739 "type": "m.room.member",
740 "unsigned": {
741 "age": 1234,
742 "membership": "join",
743 },
744 })
745 }
746
747 #[test]
748 fn serialize_request_all_params() {
749 let supported = SupportedVersions {
750 versions: [MatrixVersion::V1_1].into(),
751 features: Default::default(),
752 };
753 let req: http::Request<Vec<u8>> = Request {
754 filter: Some(Filter::FilterId("66696p746572".to_owned())),
755 since: Some("s72594_4483_1934".to_owned()),
756 full_state: true,
757 set_presence: PresenceState::Offline,
758 timeout: Some(Duration::from_millis(30000)),
759 use_state_after: true,
760 }
761 .try_into_http_request(
762 "https://homeserver.tld",
763 SendAccessToken::IfRequired("auth_tok"),
764 Cow::Owned(supported),
765 )
766 .unwrap();
767
768 let uri = req.uri();
769 let query = uri.query().unwrap();
770
771 assert_eq!(uri.path(), "/_matrix/client/v3/sync");
772 assert!(query.contains("filter=66696p746572"));
773 assert!(query.contains("since=s72594_4483_1934"));
774 assert!(query.contains("full_state=true"));
775 assert!(query.contains("set_presence=offline"));
776 assert!(query.contains("timeout=30000"));
777 assert!(query.contains("use_state_after=true"));
778 }
779
780 #[test]
781 fn deserialize_response_invite() {
782 let creator = user_id!("@creator:localhost");
783 let invitee = user_id!("@invitee:localhost");
784 let room_id = room_id!("!privateroom:localhost");
785 let event_id = event_id!("$invite");
786
787 let body = json!({
788 "next_batch": "a00",
789 "rooms": {
790 "invite": {
791 room_id: {
792 "invite_state": {
793 "events": [
794 {
795 "content": {
796 "room_version": "11",
797 },
798 "type": "m.room.create",
799 "state_key": "",
800 "sender": creator,
801 },
802 {
803 "content": {
804 "membership": "invite",
805 },
806 "type": "m.room.member",
807 "state_key": invitee,
808 "sender": creator,
809 "origin_server_ts": 4_345_456,
810 "event_id": event_id,
811 },
812 ],
813 },
814 },
815 },
816 },
817 });
818 let http_response = http::Response::new(to_json_vec(&body).unwrap());
819
820 let response = Response::try_from_http_response(http_response).unwrap();
821 assert_eq!(response.next_batch, "a00");
822 let private_room = response.rooms.invite.get(room_id).unwrap();
823
824 let first_event = private_room.invite_state.events[0].deserialize().unwrap();
825 assert_matches!(first_event, AnyStrippedStateEvent::RoomCreate(create_event));
826 assert_eq!(create_event.sender, creator);
827 assert_eq!(create_event.content.room_version, RoomVersionId::V11);
828 }
829
830 #[test]
831 fn deserialize_response_no_state() {
832 let joined_room_id = room_id!("!joined:localhost");
833 let left_room_id = room_id!("!left:localhost");
834 let event = sync_state_event();
835
836 let body = json!({
837 "next_batch": "aaa",
838 "rooms": {
839 "join": {
840 joined_room_id: {
841 "timeline": {
842 "events": [
843 event,
844 ],
845 },
846 },
847 },
848 "leave": {
849 left_room_id: {
850 "timeline": {
851 "events": [
852 event,
853 ],
854 },
855 },
856 },
857 },
858 });
859
860 let http_response = http::Response::new(to_json_vec(&body).unwrap());
861
862 let response = Response::try_from_http_response(http_response).unwrap();
863 assert_eq!(response.next_batch, "aaa");
864
865 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
866 assert_eq!(joined_room.timeline.events.len(), 1);
867 assert!(joined_room.state.is_before_and_empty());
868
869 let left_room = response.rooms.leave.get(left_room_id).unwrap();
870 assert_eq!(left_room.timeline.events.len(), 1);
871 assert!(left_room.state.is_before_and_empty());
872 }
873
874 #[test]
875 fn deserialize_response_state_before() {
876 let joined_room_id = room_id!("!joined:localhost");
877 let left_room_id = room_id!("!left:localhost");
878 let event = sync_state_event();
879
880 let body = json!({
881 "next_batch": "aaa",
882 "rooms": {
883 "join": {
884 joined_room_id: {
885 "state": {
886 "events": [
887 event,
888 ],
889 },
890 },
891 },
892 "leave": {
893 left_room_id: {
894 "state": {
895 "events": [
896 event,
897 ],
898 },
899 },
900 },
901 },
902 });
903
904 let http_response = http::Response::new(to_json_vec(&body).unwrap());
905
906 let response = Response::try_from_http_response(http_response).unwrap();
907 assert_eq!(response.next_batch, "aaa");
908
909 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
910 assert!(joined_room.timeline.is_empty());
911 assert_matches!(&joined_room.state, State::Before(state));
912 assert_eq!(state.events.len(), 1);
913
914 let left_room = response.rooms.leave.get(left_room_id).unwrap();
915 assert!(left_room.timeline.is_empty());
916 assert_matches!(&left_room.state, State::Before(state));
917 assert_eq!(state.events.len(), 1);
918 }
919
920 #[test]
921 fn deserialize_response_empty_state_after() {
922 let joined_room_id = room_id!("!joined:localhost");
923 let left_room_id = room_id!("!left:localhost");
924
925 let body = json!({
926 "next_batch": "aaa",
927 "rooms": {
928 "join": {
929 joined_room_id: {
930 "state_after": {},
931 },
932 },
933 "leave": {
934 left_room_id: {
935 "state_after": {},
936 },
937 },
938 },
939 });
940
941 let http_response = http::Response::new(to_json_vec(&body).unwrap());
942
943 let response = Response::try_from_http_response(http_response).unwrap();
944 assert_eq!(response.next_batch, "aaa");
945
946 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
947 assert!(joined_room.timeline.is_empty());
948 assert_matches!(&joined_room.state, State::After(state));
949 assert_eq!(state.events.len(), 0);
950
951 let left_room = response.rooms.leave.get(left_room_id).unwrap();
952 assert!(left_room.timeline.is_empty());
953 assert_matches!(&left_room.state, State::After(state));
954 assert_eq!(state.events.len(), 0);
955 }
956
957 #[test]
958 fn deserialize_response_non_empty_state_after() {
959 let joined_room_id = room_id!("!joined:localhost");
960 let left_room_id = room_id!("!left:localhost");
961 let event = sync_state_event();
962
963 let body = json!({
964 "next_batch": "aaa",
965 "rooms": {
966 "join": {
967 joined_room_id: {
968 "state_after": {
969 "events": [
970 event,
971 ],
972 },
973 },
974 },
975 "leave": {
976 left_room_id: {
977 "state_after": {
978 "events": [
979 event,
980 ],
981 },
982 },
983 },
984 },
985 });
986
987 let http_response = http::Response::new(to_json_vec(&body).unwrap());
988
989 let response = Response::try_from_http_response(http_response).unwrap();
990 assert_eq!(response.next_batch, "aaa");
991
992 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
993 assert!(joined_room.timeline.is_empty());
994 assert_matches!(&joined_room.state, State::After(state));
995 assert_eq!(state.events.len(), 1);
996
997 let left_room = response.rooms.leave.get(left_room_id).unwrap();
998 assert!(left_room.timeline.is_empty());
999 assert_matches!(&left_room.state, State::After(state));
1000 assert_eq!(state.events.len(), 1);
1001 }
1002}
1003
1004#[cfg(all(test, feature = "server"))]
1005mod server_tests {
1006 use std::time::Duration;
1007
1008 use assert_matches2::assert_matches;
1009 use ruma_common::{
1010 api::{IncomingRequest as _, OutgoingResponse as _},
1011 owned_room_id,
1012 presence::PresenceState,
1013 serde::Raw,
1014 };
1015 use ruma_events::AnySyncStateEvent;
1016 use serde_json::{Value as JsonValue, from_slice as from_json_slice, json};
1017
1018 use super::{Filter, JoinedRoom, LeftRoom, Request, Response, State};
1019
1020 fn sync_state_event() -> Raw<AnySyncStateEvent> {
1021 Raw::new(&json!({
1022 "content": {
1023 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1024 "displayname": "Alice Margatroid",
1025 "membership": "join",
1026 },
1027 "event_id": "$143273582443PhrSn",
1028 "origin_server_ts": 1_432_735_824,
1029 "sender": "@alice:example.org",
1030 "state_key": "@alice:example.org",
1031 "type": "m.room.member",
1032 "unsigned": {
1033 "age": 1234,
1034 "membership": "join",
1035 },
1036 }))
1037 .unwrap()
1038 .cast_unchecked()
1039 }
1040
1041 #[test]
1042 fn deserialize_request_all_query_params() {
1043 let uri = http::Uri::builder()
1044 .scheme("https")
1045 .authority("matrix.org")
1046 .path_and_query(
1047 "/_matrix/client/r0/sync\
1048 ?filter=myfilter\
1049 &since=myts\
1050 &full_state=false\
1051 &set_presence=offline\
1052 &timeout=5000",
1053 )
1054 .build()
1055 .unwrap();
1056
1057 let req = Request::try_from_http_request(
1058 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1059 &[] as &[String],
1060 )
1061 .unwrap();
1062
1063 assert_matches!(req.filter, Some(Filter::FilterId(id)));
1064 assert_eq!(id, "myfilter");
1065 assert_eq!(req.since.as_deref(), Some("myts"));
1066 assert!(!req.full_state);
1067 assert_eq!(req.set_presence, PresenceState::Offline);
1068 assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
1069 }
1070
1071 #[test]
1072 fn deserialize_request_no_query_params() {
1073 let uri = http::Uri::builder()
1074 .scheme("https")
1075 .authority("matrix.org")
1076 .path_and_query("/_matrix/client/r0/sync")
1077 .build()
1078 .unwrap();
1079
1080 let req = Request::try_from_http_request(
1081 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1082 &[] as &[String],
1083 )
1084 .unwrap();
1085
1086 assert_matches!(req.filter, None);
1087 assert_eq!(req.since, None);
1088 assert!(!req.full_state);
1089 assert_eq!(req.set_presence, PresenceState::Online);
1090 assert_eq!(req.timeout, None);
1091 }
1092
1093 #[test]
1094 fn deserialize_request_some_query_params() {
1095 let uri = http::Uri::builder()
1096 .scheme("https")
1097 .authority("matrix.org")
1098 .path_and_query(
1099 "/_matrix/client/r0/sync\
1100 ?filter=EOKFFmdZYF\
1101 &timeout=0",
1102 )
1103 .build()
1104 .unwrap();
1105
1106 let req = Request::try_from_http_request(
1107 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1108 &[] as &[String],
1109 )
1110 .unwrap();
1111
1112 assert_matches!(req.filter, Some(Filter::FilterId(id)));
1113 assert_eq!(id, "EOKFFmdZYF");
1114 assert_eq!(req.since, None);
1115 assert!(!req.full_state);
1116 assert_eq!(req.set_presence, PresenceState::Online);
1117 assert_eq!(req.timeout, Some(Duration::from_millis(0)));
1118 }
1119
1120 #[test]
1121 fn serialize_response_no_state() {
1122 let joined_room_id = owned_room_id!("!joined:localhost");
1123 let left_room_id = owned_room_id!("!left:localhost");
1124 let event = sync_state_event();
1125
1126 let mut response = Response::new("aaa".to_owned());
1127
1128 let mut joined_room = JoinedRoom::new();
1129 joined_room.timeline.events.push(event.clone().cast());
1130 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1131
1132 let mut left_room = LeftRoom::new();
1133 left_room.timeline.events.push(event.clone().cast());
1134 response.rooms.leave.insert(left_room_id.clone(), left_room);
1135
1136 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1137
1138 assert_eq!(
1139 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1140 json!({
1141 "next_batch": "aaa",
1142 "rooms": {
1143 "join": {
1144 joined_room_id: {
1145 "timeline": {
1146 "events": [
1147 event,
1148 ],
1149 },
1150 },
1151 },
1152 "leave": {
1153 left_room_id: {
1154 "timeline": {
1155 "events": [
1156 event,
1157 ],
1158 },
1159 },
1160 },
1161 },
1162 })
1163 );
1164 }
1165
1166 #[test]
1167 fn serialize_response_state_before() {
1168 let joined_room_id = owned_room_id!("!joined:localhost");
1169 let left_room_id = owned_room_id!("!left:localhost");
1170 let event = sync_state_event();
1171
1172 let mut response = Response::new("aaa".to_owned());
1173
1174 let mut joined_room = JoinedRoom::new();
1175 joined_room.state = State::Before(vec![event.clone()].into());
1176 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1177
1178 let mut left_room = LeftRoom::new();
1179 left_room.state = State::Before(vec![event.clone()].into());
1180 response.rooms.leave.insert(left_room_id.clone(), left_room);
1181
1182 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1183
1184 assert_eq!(
1185 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1186 json!({
1187 "next_batch": "aaa",
1188 "rooms": {
1189 "join": {
1190 joined_room_id: {
1191 "state": {
1192 "events": [
1193 event,
1194 ],
1195 },
1196 },
1197 },
1198 "leave": {
1199 left_room_id: {
1200 "state": {
1201 "events": [
1202 event,
1203 ],
1204 },
1205 },
1206 },
1207 },
1208 })
1209 );
1210 }
1211
1212 #[test]
1213 fn serialize_response_empty_state_after() {
1214 let joined_room_id = owned_room_id!("!joined:localhost");
1215 let left_room_id = owned_room_id!("!left:localhost");
1216
1217 let mut response = Response::new("aaa".to_owned());
1218
1219 let mut joined_room = JoinedRoom::new();
1220 joined_room.state = State::After(Default::default());
1221 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1222
1223 let mut left_room = LeftRoom::new();
1224 left_room.state = State::After(Default::default());
1225 response.rooms.leave.insert(left_room_id.clone(), left_room);
1226
1227 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1228
1229 assert_eq!(
1230 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1231 json!({
1232 "next_batch": "aaa",
1233 "rooms": {
1234 "join": {
1235 joined_room_id: {
1236 "state_after": {},
1237 },
1238 },
1239 "leave": {
1240 left_room_id: {
1241 "state_after": {},
1242 },
1243 },
1244 },
1245 })
1246 );
1247 }
1248
1249 #[test]
1250 fn serialize_response_non_empty_state_after() {
1251 let joined_room_id = owned_room_id!("!joined:localhost");
1252 let left_room_id = owned_room_id!("!left:localhost");
1253 let event = sync_state_event();
1254
1255 let mut response = Response::new("aaa".to_owned());
1256
1257 let mut joined_room = JoinedRoom::new();
1258 joined_room.state = State::After(vec![event.clone()].into());
1259 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1260
1261 let mut left_room = LeftRoom::new();
1262 left_room.state = State::After(vec![event.clone()].into());
1263 response.rooms.leave.insert(left_room_id.clone(), left_room);
1264
1265 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1266
1267 assert_eq!(
1268 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1269 json!({
1270 "next_batch": "aaa",
1271 "rooms": {
1272 "join": {
1273 joined_room_id: {
1274 "state_after": {
1275 "events": [
1276 event,
1277 ],
1278 },
1279 },
1280 },
1281 "leave": {
1282 left_room_id: {
1283 "state_after": {
1284 "events": [
1285 event,
1286 ],
1287 },
1288 },
1289 },
1290 },
1291 })
1292 );
1293 }
1294}