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