1use std::{collections::BTreeMap, time::Duration};
6
7use as_variant::as_variant;
8use js_int::UInt;
9use ruma_common::{
10 api::{request, response, Metadata},
11 metadata,
12 presence::PresenceState,
13 serde::Raw,
14 OneTimeKeyAlgorithm, OwnedEventId, OwnedRoomId, OwnedUserId,
15};
16use ruma_events::{
17 presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
18 AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnySyncTimelineEvent, AnyToDeviceEvent,
19};
20use serde::{Deserialize, Serialize};
21
22mod response_serde;
23
24use super::{DeviceLists, StrippedState, UnreadNotificationsCount};
25use crate::filter::FilterDefinition;
26
27const METADATA: Metadata = metadata! {
28 method: GET,
29 rate_limited: false,
30 authentication: AccessToken,
31 history: {
32 1.0 => "/_matrix/client/r0/sync",
33 1.1 => "/_matrix/client/v3/sync",
34 }
35};
36
37#[request(error = crate::Error)]
39#[derive(Default)]
40pub struct Request {
41 #[serde(skip_serializing_if = "Option::is_none")]
43 #[ruma_api(query)]
44 pub filter: Option<Filter>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
51 #[ruma_api(query)]
52 pub since: Option<String>,
53
54 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
56 #[ruma_api(query)]
57 pub full_state: bool,
58
59 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
63 #[ruma_api(query)]
64 pub set_presence: PresenceState,
65
66 #[serde(
68 with = "ruma_common::serde::duration::opt_ms",
69 default,
70 skip_serializing_if = "Option::is_none"
71 )]
72 #[ruma_api(query)]
73 pub timeout: Option<Duration>,
74
75 #[cfg(feature = "unstable-msc4222")]
78 #[serde(
79 default,
80 skip_serializing_if = "ruma_common::serde::is_default",
81 rename = "org.matrix.msc4222.use_state_after"
82 )]
83 #[ruma_api(query)]
84 pub use_state_after: bool,
85}
86
87#[response(error = crate::Error)]
89pub struct Response {
90 pub next_batch: String,
92
93 #[serde(default, skip_serializing_if = "Rooms::is_empty")]
95 pub rooms: Rooms,
96
97 #[serde(default, skip_serializing_if = "Presence::is_empty")]
99 pub presence: Presence,
100
101 #[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
103 pub account_data: GlobalAccountData,
104
105 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
107 pub to_device: ToDevice,
108
109 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
113 pub device_lists: DeviceLists,
114
115 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
118 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
119
120 #[serde(skip_serializing_if = "Option::is_none")]
125 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
126}
127
128impl Request {
129 pub fn new() -> Self {
131 Default::default()
132 }
133}
134
135impl Response {
136 pub fn new(next_batch: String) -> Self {
138 Self {
139 next_batch,
140 rooms: Default::default(),
141 presence: Default::default(),
142 account_data: Default::default(),
143 to_device: Default::default(),
144 device_lists: Default::default(),
145 device_one_time_keys_count: BTreeMap::new(),
146 device_unused_fallback_key_types: None,
147 }
148 }
149}
150
151#[derive(Clone, Debug, Deserialize, Serialize)]
153#[allow(clippy::large_enum_variant)]
154#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
155#[serde(untagged)]
156pub enum Filter {
157 #[serde(with = "ruma_common::serde::json_string")]
169 FilterDefinition(FilterDefinition),
170
171 FilterId(String),
173}
174
175impl From<FilterDefinition> for Filter {
176 fn from(def: FilterDefinition) -> Self {
177 Self::FilterDefinition(def)
178 }
179}
180
181impl From<String> for Filter {
182 fn from(id: String) -> Self {
183 Self::FilterId(id)
184 }
185}
186
187#[derive(Clone, Debug, Default, Deserialize, Serialize)]
189#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
190pub struct Rooms {
191 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
193 pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
194
195 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
197 pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
198
199 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
201 pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
202
203 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
205 pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
206}
207
208impl Rooms {
209 pub fn new() -> Self {
211 Default::default()
212 }
213
214 pub fn is_empty(&self) -> bool {
216 self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
217 }
218}
219
220#[derive(Clone, Debug, Default, Serialize)]
222#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
223pub struct LeftRoom {
224 #[serde(skip_serializing_if = "Timeline::is_empty")]
227 pub timeline: Timeline,
228
229 #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
231 pub state: State,
232
233 #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
235 pub account_data: RoomAccountData,
236}
237
238impl LeftRoom {
239 pub fn new() -> Self {
241 Default::default()
242 }
243
244 pub fn is_empty(&self) -> bool {
246 self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
247 }
248}
249
250#[derive(Clone, Debug, Default, Serialize)]
252#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
253pub struct JoinedRoom {
254 #[serde(skip_serializing_if = "RoomSummary::is_empty")]
257 pub summary: RoomSummary,
258
259 #[serde(skip_serializing_if = "UnreadNotificationsCount::is_empty")]
267 pub unread_notifications: UnreadNotificationsCount,
268
269 #[serde(skip_serializing_if = "BTreeMap::is_empty")]
278 pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
279
280 #[serde(skip_serializing_if = "Timeline::is_empty")]
282 pub timeline: Timeline,
283
284 #[serde(flatten, skip_serializing_if = "State::is_before_and_empty")]
288 pub state: State,
289
290 #[serde(skip_serializing_if = "RoomAccountData::is_empty")]
292 pub account_data: RoomAccountData,
293
294 #[serde(skip_serializing_if = "Ephemeral::is_empty")]
297 pub ephemeral: Ephemeral,
298
299 #[cfg(feature = "unstable-msc2654")]
305 #[serde(rename = "org.matrix.msc2654.unread_count", skip_serializing_if = "Option::is_none")]
306 pub unread_count: Option<UInt>,
307}
308
309impl JoinedRoom {
310 pub fn new() -> Self {
312 Default::default()
313 }
314
315 pub fn is_empty(&self) -> bool {
317 let is_empty = self.summary.is_empty()
318 && self.unread_notifications.is_empty()
319 && self.unread_thread_notifications.is_empty()
320 && self.timeline.is_empty()
321 && self.state.is_empty()
322 && self.account_data.is_empty()
323 && self.ephemeral.is_empty();
324
325 #[cfg(not(feature = "unstable-msc2654"))]
326 return is_empty;
327
328 #[cfg(feature = "unstable-msc2654")]
329 return is_empty && self.unread_count.is_none();
330 }
331}
332
333#[derive(Clone, Debug, Default, Deserialize, Serialize)]
335#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
336pub struct KnockedRoom {
337 #[serde(default, skip_serializing_if = "KnockState::is_empty")]
339 pub knock_state: KnockState,
340}
341
342impl KnockedRoom {
343 pub fn new() -> Self {
345 Default::default()
346 }
347
348 pub fn is_empty(&self) -> bool {
350 self.knock_state.is_empty()
351 }
352}
353
354impl From<KnockState> for KnockedRoom {
355 fn from(knock_state: KnockState) -> Self {
356 KnockedRoom { knock_state, ..Default::default() }
357 }
358}
359
360#[derive(Clone, Debug, Default, Deserialize, Serialize)]
362#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
363pub struct KnockState {
364 #[serde(default, skip_serializing_if = "Vec::is_empty")]
366 pub events: Vec<Raw<StrippedState>>,
367}
368
369impl KnockState {
370 pub fn new() -> Self {
372 Default::default()
373 }
374
375 pub fn is_empty(&self) -> bool {
377 self.events.is_empty()
378 }
379}
380
381#[derive(Clone, Debug, Default, Deserialize, Serialize)]
383#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
384pub struct Timeline {
385 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
389 pub limited: bool,
390
391 #[serde(skip_serializing_if = "Option::is_none")]
394 pub prev_batch: Option<String>,
395
396 pub events: Vec<Raw<AnySyncTimelineEvent>>,
398}
399
400impl Timeline {
401 pub fn new() -> Self {
403 Default::default()
404 }
405
406 pub fn is_empty(&self) -> bool {
411 !self.limited && self.prev_batch.is_none() && self.events.is_empty()
412 }
413}
414
415#[derive(Clone, Debug, Serialize)]
417#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
418pub enum State {
419 #[serde(rename = "state")]
427 Before(StateEvents),
428
429 #[cfg(feature = "unstable-msc4222")]
436 #[serde(rename = "org.matrix.msc4222.state_after")]
437 After(StateEvents),
438}
439
440impl State {
441 fn is_before_and_empty(&self) -> bool {
443 as_variant!(self, Self::Before).is_some_and(|state| state.is_empty())
444 }
445
446 pub fn is_empty(&self) -> bool {
448 match self {
449 Self::Before(state) => state.is_empty(),
450 #[cfg(feature = "unstable-msc4222")]
451 Self::After(state) => state.is_empty(),
452 }
453 }
454}
455
456impl Default for State {
457 fn default() -> Self {
458 Self::Before(Default::default())
459 }
460}
461
462#[derive(Clone, Debug, Default, Deserialize, Serialize)]
464#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
465pub struct StateEvents {
466 #[serde(default, skip_serializing_if = "Vec::is_empty")]
468 pub events: Vec<Raw<AnySyncStateEvent>>,
469}
470
471impl StateEvents {
472 pub fn new() -> Self {
474 Default::default()
475 }
476
477 pub fn is_empty(&self) -> bool {
479 self.events.is_empty()
480 }
481
482 pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
484 Self { events, ..Default::default() }
485 }
486}
487
488impl From<Vec<Raw<AnySyncStateEvent>>> for StateEvents {
489 fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
490 Self::with_events(events)
491 }
492}
493
494#[derive(Clone, Debug, Default, Deserialize, Serialize)]
496#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
497pub struct GlobalAccountData {
498 #[serde(default, skip_serializing_if = "Vec::is_empty")]
500 pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
501}
502
503impl GlobalAccountData {
504 pub fn new() -> Self {
506 Default::default()
507 }
508
509 pub fn is_empty(&self) -> bool {
511 self.events.is_empty()
512 }
513}
514
515#[derive(Clone, Debug, Default, Deserialize, Serialize)]
517#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
518pub struct RoomAccountData {
519 #[serde(default, skip_serializing_if = "Vec::is_empty")]
521 pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
522}
523
524impl RoomAccountData {
525 pub fn new() -> Self {
527 Default::default()
528 }
529
530 pub fn is_empty(&self) -> bool {
532 self.events.is_empty()
533 }
534}
535
536#[derive(Clone, Debug, Default, Deserialize, Serialize)]
538#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
539pub struct Ephemeral {
540 #[serde(default, skip_serializing_if = "Vec::is_empty")]
542 pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
543}
544
545impl Ephemeral {
546 pub fn new() -> Self {
548 Default::default()
549 }
550
551 pub fn is_empty(&self) -> bool {
553 self.events.is_empty()
554 }
555}
556
557#[derive(Clone, Debug, Default, Deserialize, Serialize)]
559#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
560pub struct RoomSummary {
561 #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
565 pub heroes: Vec<OwnedUserId>,
566
567 #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
571 pub joined_member_count: Option<UInt>,
572
573 #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
577 pub invited_member_count: Option<UInt>,
578}
579
580impl RoomSummary {
581 pub fn new() -> Self {
583 Default::default()
584 }
585
586 pub fn is_empty(&self) -> bool {
588 self.heroes.is_empty()
589 && self.joined_member_count.is_none()
590 && self.invited_member_count.is_none()
591 }
592}
593
594#[derive(Clone, Debug, Default, Deserialize, Serialize)]
596#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
597pub struct InvitedRoom {
598 #[serde(default, skip_serializing_if = "InviteState::is_empty")]
600 pub invite_state: InviteState,
601}
602
603impl InvitedRoom {
604 pub fn new() -> Self {
606 Default::default()
607 }
608
609 pub fn is_empty(&self) -> bool {
611 self.invite_state.is_empty()
612 }
613}
614
615impl From<InviteState> for InvitedRoom {
616 fn from(invite_state: InviteState) -> Self {
617 InvitedRoom { invite_state, ..Default::default() }
618 }
619}
620
621#[derive(Clone, Debug, Default, Deserialize, Serialize)]
623#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
624pub struct InviteState {
625 #[serde(default, skip_serializing_if = "Vec::is_empty")]
627 pub events: Vec<Raw<StrippedState>>,
628}
629
630impl InviteState {
631 pub fn new() -> Self {
633 Default::default()
634 }
635
636 pub fn is_empty(&self) -> bool {
638 self.events.is_empty()
639 }
640}
641
642impl From<Vec<Raw<StrippedState>>> for InviteState {
643 fn from(events: Vec<Raw<StrippedState>>) -> Self {
644 InviteState { events, ..Default::default() }
645 }
646}
647
648#[derive(Clone, Debug, Default, Deserialize, Serialize)]
650#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
651pub struct Presence {
652 #[serde(default, skip_serializing_if = "Vec::is_empty")]
654 pub events: Vec<Raw<PresenceEvent>>,
655}
656
657impl Presence {
658 pub fn new() -> Self {
660 Default::default()
661 }
662
663 pub fn is_empty(&self) -> bool {
665 self.events.is_empty()
666 }
667}
668
669#[derive(Clone, Debug, Default, Deserialize, Serialize)]
671#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
672pub struct ToDevice {
673 #[serde(default, skip_serializing_if = "Vec::is_empty")]
675 pub events: Vec<Raw<AnyToDeviceEvent>>,
676}
677
678impl ToDevice {
679 pub fn new() -> Self {
681 Default::default()
682 }
683
684 pub fn is_empty(&self) -> bool {
686 self.events.is_empty()
687 }
688}
689
690#[cfg(test)]
691mod tests {
692 use assign::assign;
693 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
694
695 use super::Timeline;
696
697 #[test]
698 fn timeline_serde() {
699 let timeline = assign!(Timeline::new(), { limited: true });
700 let timeline_serialized = json!({ "events": [], "limited": true });
701 assert_eq!(to_json_value(timeline).unwrap(), timeline_serialized);
702
703 let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
704 assert!(timeline_deserialized.limited);
705
706 let timeline_default = Timeline::default();
707 assert_eq!(to_json_value(timeline_default).unwrap(), json!({ "events": [] }));
708
709 let timeline_default_deserialized =
710 from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
711 assert!(!timeline_default_deserialized.limited);
712 }
713}
714
715#[cfg(all(test, feature = "client"))]
716mod client_tests {
717 use std::time::Duration;
718
719 use assert_matches2::assert_matches;
720 use ruma_common::{
721 api::{
722 IncomingResponse as _, MatrixVersion, OutgoingRequest as _, SendAccessToken,
723 SupportedVersions,
724 },
725 event_id, room_id, user_id, RoomVersionId,
726 };
727 use ruma_events::AnyStrippedStateEvent;
728 use serde_json::{json, to_vec as to_json_vec, Value as JsonValue};
729
730 use super::{Filter, PresenceState, Request, Response, State, StrippedState};
731
732 fn sync_state_event() -> JsonValue {
733 json!({
734 "content": {
735 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
736 "displayname": "Alice Margatroid",
737 "membership": "join",
738 },
739 "event_id": "$143273582443PhrSn",
740 "origin_server_ts": 1_432_735_824,
741 "sender": "@alice:example.org",
742 "state_key": "@alice:example.org",
743 "type": "m.room.member",
744 "unsigned": {
745 "age": 1234,
746 "membership": "join",
747 },
748 })
749 }
750
751 #[test]
752 fn serialize_request_all_params() {
753 let supported = SupportedVersions {
754 versions: [MatrixVersion::V1_1].into(),
755 features: Default::default(),
756 };
757 let req: http::Request<Vec<u8>> = Request {
758 filter: Some(Filter::FilterId("66696p746572".to_owned())),
759 since: Some("s72594_4483_1934".to_owned()),
760 full_state: true,
761 set_presence: PresenceState::Offline,
762 timeout: Some(Duration::from_millis(30000)),
763 #[cfg(feature = "unstable-msc4222")]
764 use_state_after: true,
765 }
766 .try_into_http_request(
767 "https://homeserver.tld",
768 SendAccessToken::IfRequired("auth_tok"),
769 &supported,
770 )
771 .unwrap();
772
773 let uri = req.uri();
774 let query = uri.query().unwrap();
775
776 assert_eq!(uri.path(), "/_matrix/client/v3/sync");
777 assert!(query.contains("filter=66696p746572"));
778 assert!(query.contains("since=s72594_4483_1934"));
779 assert!(query.contains("full_state=true"));
780 assert!(query.contains("set_presence=offline"));
781 assert!(query.contains("timeout=30000"));
782 #[cfg(feature = "unstable-msc4222")]
783 assert!(query.contains("org.matrix.msc4222.use_state_after=true"));
784 }
785
786 #[test]
787 fn deserialize_response_invite() {
788 let creator = user_id!("@creator:localhost");
789 let invitee = user_id!("@invitee:localhost");
790 let room_id = room_id!("!privateroom:localhost");
791 let event_id = event_id!("$invite");
792
793 let body = json!({
794 "next_batch": "a00",
795 "rooms": {
796 "invite": {
797 room_id: {
798 "invite_state": {
799 "events": [
800 {
801 "content": {
802 "room_version": "11",
803 },
804 "type": "m.room.create",
805 "state_key": "",
806 "sender": creator,
807 },
808 {
809 "content": {
810 "membership": "invite",
811 },
812 "type": "m.room.member",
813 "state_key": invitee,
814 "sender": creator,
815 "origin_server_ts": 4_345_456,
816 "event_id": event_id,
817 },
818 ],
819 },
820 },
821 },
822 },
823 });
824 let http_response = http::Response::new(to_json_vec(&body).unwrap());
825
826 let response = Response::try_from_http_response(http_response).unwrap();
827 assert_eq!(response.next_batch, "a00");
828 let private_room = response.rooms.invite.get(room_id).unwrap();
829
830 let first_event = private_room.invite_state.events[0].deserialize().unwrap();
831 assert_matches!(
832 first_event,
833 StrippedState::Stripped(AnyStrippedStateEvent::RoomCreate(create_event))
834 );
835 assert_eq!(create_event.sender, creator);
836 assert_eq!(create_event.content.room_version, RoomVersionId::V11);
837
838 #[cfg(feature = "unstable-msc4319")]
839 {
840 use ruma_events::{room::member::MembershipState, AnySyncStateEvent, SyncStateEvent};
841
842 let second_event = private_room.invite_state.events[1].deserialize().unwrap();
843 assert_matches!(
844 second_event,
845 StrippedState::Sync(AnySyncStateEvent::RoomMember(SyncStateEvent::Original(
846 invite_event
847 )))
848 );
849 assert_eq!(invite_event.sender, creator);
850 assert_eq!(invite_event.state_key, invitee);
851 assert_eq!(invite_event.content.membership, MembershipState::Invite);
852 }
853 }
854
855 #[test]
856 fn deserialize_response_no_state() {
857 let joined_room_id = room_id!("!joined:localhost");
858 let left_room_id = room_id!("!left:localhost");
859 let event = sync_state_event();
860
861 let body = json!({
862 "next_batch": "aaa",
863 "rooms": {
864 "join": {
865 joined_room_id: {
866 "timeline": {
867 "events": [
868 event,
869 ],
870 },
871 },
872 },
873 "leave": {
874 left_room_id: {
875 "timeline": {
876 "events": [
877 event,
878 ],
879 },
880 },
881 },
882 },
883 });
884
885 let http_response = http::Response::new(to_json_vec(&body).unwrap());
886
887 let response = Response::try_from_http_response(http_response).unwrap();
888 assert_eq!(response.next_batch, "aaa");
889
890 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
891 assert_eq!(joined_room.timeline.events.len(), 1);
892 assert!(joined_room.state.is_before_and_empty());
893
894 let left_room = response.rooms.leave.get(left_room_id).unwrap();
895 assert_eq!(left_room.timeline.events.len(), 1);
896 assert!(left_room.state.is_before_and_empty());
897 }
898
899 #[test]
900 fn deserialize_response_state_before() {
901 let joined_room_id = room_id!("!joined:localhost");
902 let left_room_id = room_id!("!left:localhost");
903 let event = sync_state_event();
904
905 let body = json!({
906 "next_batch": "aaa",
907 "rooms": {
908 "join": {
909 joined_room_id: {
910 "state": {
911 "events": [
912 event,
913 ],
914 },
915 },
916 },
917 "leave": {
918 left_room_id: {
919 "state": {
920 "events": [
921 event,
922 ],
923 },
924 },
925 },
926 },
927 });
928
929 let http_response = http::Response::new(to_json_vec(&body).unwrap());
930
931 let response = Response::try_from_http_response(http_response).unwrap();
932 assert_eq!(response.next_batch, "aaa");
933
934 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
935 assert!(joined_room.timeline.is_empty());
936 assert_matches!(&joined_room.state, State::Before(state));
937 assert_eq!(state.events.len(), 1);
938
939 let left_room = response.rooms.leave.get(left_room_id).unwrap();
940 assert!(left_room.timeline.is_empty());
941 assert_matches!(&left_room.state, State::Before(state));
942 assert_eq!(state.events.len(), 1);
943 }
944
945 #[test]
946 #[cfg(feature = "unstable-msc4222")]
947 fn deserialize_response_empty_state_after() {
948 let joined_room_id = room_id!("!joined:localhost");
949 let left_room_id = room_id!("!left:localhost");
950
951 let body = json!({
952 "next_batch": "aaa",
953 "rooms": {
954 "join": {
955 joined_room_id: {
956 "org.matrix.msc4222.state_after": {},
957 },
958 },
959 "leave": {
960 left_room_id: {
961 "org.matrix.msc4222.state_after": {},
962 },
963 },
964 },
965 });
966
967 let http_response = http::Response::new(to_json_vec(&body).unwrap());
968
969 let response = Response::try_from_http_response(http_response).unwrap();
970 assert_eq!(response.next_batch, "aaa");
971
972 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
973 assert!(joined_room.timeline.is_empty());
974 assert_matches!(&joined_room.state, State::After(state));
975 assert_eq!(state.events.len(), 0);
976
977 let left_room = response.rooms.leave.get(left_room_id).unwrap();
978 assert!(left_room.timeline.is_empty());
979 assert_matches!(&left_room.state, State::After(state));
980 assert_eq!(state.events.len(), 0);
981 }
982
983 #[test]
984 #[cfg(feature = "unstable-msc4222")]
985 fn deserialize_response_non_empty_state_after() {
986 let joined_room_id = room_id!("!joined:localhost");
987 let left_room_id = room_id!("!left:localhost");
988 let event = sync_state_event();
989
990 let body = json!({
991 "next_batch": "aaa",
992 "rooms": {
993 "join": {
994 joined_room_id: {
995 "org.matrix.msc4222.state_after": {
996 "events": [
997 event,
998 ],
999 },
1000 },
1001 },
1002 "leave": {
1003 left_room_id: {
1004 "org.matrix.msc4222.state_after": {
1005 "events": [
1006 event,
1007 ],
1008 },
1009 },
1010 },
1011 },
1012 });
1013
1014 let http_response = http::Response::new(to_json_vec(&body).unwrap());
1015
1016 let response = Response::try_from_http_response(http_response).unwrap();
1017 assert_eq!(response.next_batch, "aaa");
1018
1019 let joined_room = response.rooms.join.get(joined_room_id).unwrap();
1020 assert!(joined_room.timeline.is_empty());
1021 assert_matches!(&joined_room.state, State::After(state));
1022 assert_eq!(state.events.len(), 1);
1023
1024 let left_room = response.rooms.leave.get(left_room_id).unwrap();
1025 assert!(left_room.timeline.is_empty());
1026 assert_matches!(&left_room.state, State::After(state));
1027 assert_eq!(state.events.len(), 1);
1028 }
1029}
1030
1031#[cfg(all(test, feature = "server"))]
1032mod server_tests {
1033 use std::time::Duration;
1034
1035 use assert_matches2::assert_matches;
1036 use ruma_common::{
1037 api::{IncomingRequest as _, OutgoingResponse as _},
1038 owned_room_id,
1039 presence::PresenceState,
1040 serde::Raw,
1041 };
1042 use ruma_events::AnySyncStateEvent;
1043 use serde_json::{from_slice as from_json_slice, json, Value as JsonValue};
1044
1045 use super::{Filter, JoinedRoom, LeftRoom, Request, Response, State};
1046
1047 fn sync_state_event() -> Raw<AnySyncStateEvent> {
1048 Raw::new(&json!({
1049 "content": {
1050 "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
1051 "displayname": "Alice Margatroid",
1052 "membership": "join",
1053 },
1054 "event_id": "$143273582443PhrSn",
1055 "origin_server_ts": 1_432_735_824,
1056 "sender": "@alice:example.org",
1057 "state_key": "@alice:example.org",
1058 "type": "m.room.member",
1059 "unsigned": {
1060 "age": 1234,
1061 "membership": "join",
1062 },
1063 }))
1064 .unwrap()
1065 .cast_unchecked()
1066 }
1067
1068 #[test]
1069 fn deserialize_request_all_query_params() {
1070 let uri = http::Uri::builder()
1071 .scheme("https")
1072 .authority("matrix.org")
1073 .path_and_query(
1074 "/_matrix/client/r0/sync\
1075 ?filter=myfilter\
1076 &since=myts\
1077 &full_state=false\
1078 &set_presence=offline\
1079 &timeout=5000",
1080 )
1081 .build()
1082 .unwrap();
1083
1084 let req = Request::try_from_http_request(
1085 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1086 &[] as &[String],
1087 )
1088 .unwrap();
1089
1090 assert_matches!(req.filter, Some(Filter::FilterId(id)));
1091 assert_eq!(id, "myfilter");
1092 assert_eq!(req.since.as_deref(), Some("myts"));
1093 assert!(!req.full_state);
1094 assert_eq!(req.set_presence, PresenceState::Offline);
1095 assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
1096 }
1097
1098 #[test]
1099 fn deserialize_request_no_query_params() {
1100 let uri = http::Uri::builder()
1101 .scheme("https")
1102 .authority("matrix.org")
1103 .path_and_query("/_matrix/client/r0/sync")
1104 .build()
1105 .unwrap();
1106
1107 let req = Request::try_from_http_request(
1108 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1109 &[] as &[String],
1110 )
1111 .unwrap();
1112
1113 assert_matches!(req.filter, None);
1114 assert_eq!(req.since, None);
1115 assert!(!req.full_state);
1116 assert_eq!(req.set_presence, PresenceState::Online);
1117 assert_eq!(req.timeout, None);
1118 }
1119
1120 #[test]
1121 fn deserialize_request_some_query_params() {
1122 let uri = http::Uri::builder()
1123 .scheme("https")
1124 .authority("matrix.org")
1125 .path_and_query(
1126 "/_matrix/client/r0/sync\
1127 ?filter=EOKFFmdZYF\
1128 &timeout=0",
1129 )
1130 .build()
1131 .unwrap();
1132
1133 let req = Request::try_from_http_request(
1134 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
1135 &[] as &[String],
1136 )
1137 .unwrap();
1138
1139 assert_matches!(req.filter, Some(Filter::FilterId(id)));
1140 assert_eq!(id, "EOKFFmdZYF");
1141 assert_eq!(req.since, None);
1142 assert!(!req.full_state);
1143 assert_eq!(req.set_presence, PresenceState::Online);
1144 assert_eq!(req.timeout, Some(Duration::from_millis(0)));
1145 }
1146
1147 #[test]
1148 fn serialize_response_no_state() {
1149 let joined_room_id = owned_room_id!("!joined:localhost");
1150 let left_room_id = owned_room_id!("!left:localhost");
1151 let event = sync_state_event();
1152
1153 let mut response = Response::new("aaa".to_owned());
1154
1155 let mut joined_room = JoinedRoom::new();
1156 joined_room.timeline.events.push(event.clone().cast());
1157 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1158
1159 let mut left_room = LeftRoom::new();
1160 left_room.timeline.events.push(event.clone().cast());
1161 response.rooms.leave.insert(left_room_id.clone(), left_room);
1162
1163 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1164
1165 assert_eq!(
1166 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1167 json!({
1168 "next_batch": "aaa",
1169 "rooms": {
1170 "join": {
1171 joined_room_id: {
1172 "timeline": {
1173 "events": [
1174 event,
1175 ],
1176 },
1177 },
1178 },
1179 "leave": {
1180 left_room_id: {
1181 "timeline": {
1182 "events": [
1183 event,
1184 ],
1185 },
1186 },
1187 },
1188 },
1189 })
1190 );
1191 }
1192
1193 #[test]
1194 fn serialize_response_state_before() {
1195 let joined_room_id = owned_room_id!("!joined:localhost");
1196 let left_room_id = owned_room_id!("!left:localhost");
1197 let event = sync_state_event();
1198
1199 let mut response = Response::new("aaa".to_owned());
1200
1201 let mut joined_room = JoinedRoom::new();
1202 joined_room.state = State::Before(vec![event.clone()].into());
1203 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1204
1205 let mut left_room = LeftRoom::new();
1206 left_room.state = State::Before(vec![event.clone()].into());
1207 response.rooms.leave.insert(left_room_id.clone(), left_room);
1208
1209 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1210
1211 assert_eq!(
1212 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1213 json!({
1214 "next_batch": "aaa",
1215 "rooms": {
1216 "join": {
1217 joined_room_id: {
1218 "state": {
1219 "events": [
1220 event,
1221 ],
1222 },
1223 },
1224 },
1225 "leave": {
1226 left_room_id: {
1227 "state": {
1228 "events": [
1229 event,
1230 ],
1231 },
1232 },
1233 },
1234 },
1235 })
1236 );
1237 }
1238
1239 #[test]
1240 #[cfg(feature = "unstable-msc4222")]
1241 fn serialize_response_empty_state_after() {
1242 let joined_room_id = owned_room_id!("!joined:localhost");
1243 let left_room_id = owned_room_id!("!left:localhost");
1244
1245 let mut response = Response::new("aaa".to_owned());
1246
1247 let mut joined_room = JoinedRoom::new();
1248 joined_room.state = State::After(Default::default());
1249 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1250
1251 let mut left_room = LeftRoom::new();
1252 left_room.state = State::After(Default::default());
1253 response.rooms.leave.insert(left_room_id.clone(), left_room);
1254
1255 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1256
1257 assert_eq!(
1258 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1259 json!({
1260 "next_batch": "aaa",
1261 "rooms": {
1262 "join": {
1263 joined_room_id: {
1264 "org.matrix.msc4222.state_after": {},
1265 },
1266 },
1267 "leave": {
1268 left_room_id: {
1269 "org.matrix.msc4222.state_after": {},
1270 },
1271 },
1272 },
1273 })
1274 );
1275 }
1276
1277 #[test]
1278 #[cfg(feature = "unstable-msc4222")]
1279 fn serialize_response_non_empty_state_after() {
1280 let joined_room_id = owned_room_id!("!joined:localhost");
1281 let left_room_id = owned_room_id!("!left:localhost");
1282 let event = sync_state_event();
1283
1284 let mut response = Response::new("aaa".to_owned());
1285
1286 let mut joined_room = JoinedRoom::new();
1287 joined_room.state = State::After(vec![event.clone()].into());
1288 response.rooms.join.insert(joined_room_id.clone(), joined_room);
1289
1290 let mut left_room = LeftRoom::new();
1291 left_room.state = State::After(vec![event.clone()].into());
1292 response.rooms.leave.insert(left_room_id.clone(), left_room);
1293
1294 let http_response = response.try_into_http_response::<Vec<u8>>().unwrap();
1295
1296 assert_eq!(
1297 from_json_slice::<JsonValue>(http_response.body()).unwrap(),
1298 json!({
1299 "next_batch": "aaa",
1300 "rooms": {
1301 "join": {
1302 joined_room_id: {
1303 "org.matrix.msc4222.state_after": {
1304 "events": [
1305 event,
1306 ],
1307 },
1308 },
1309 },
1310 "leave": {
1311 left_room_id: {
1312 "org.matrix.msc4222.state_after": {
1313 "events": [
1314 event,
1315 ],
1316 },
1317 },
1318 },
1319 },
1320 })
1321 );
1322 }
1323}