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