1use std::{collections::BTreeMap, time::Duration};
11
12use js_int::UInt;
13use js_option::JsOption;
14use ruma_common::{
15 OwnedMxcUri, OwnedRoomId, OwnedUserId,
16 api::{auth_scheme::AccessToken, request, response},
17 metadata,
18 presence::PresenceState,
19 serde::{Raw, duration::opt_ms},
20};
21use ruma_events::{AnySyncStateEvent, AnySyncTimelineEvent, StateEventType};
22use serde::{Deserialize, Serialize};
23
24use super::UnreadNotificationsCount;
25
26metadata! {
27 method: POST,
28 rate_limited: false,
29 authentication: AccessToken,
30 history: {
31 unstable("org.matrix.simplified_msc3575") => "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync",
32 }
34}
35
36#[request]
38#[derive(Default)]
39pub struct Request {
40 #[serde(skip_serializing_if = "Option::is_none")]
46 #[ruma_api(query)]
47 pub pos: Option<String>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
60 pub conn_id: Option<String>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
65 pub txn_id: Option<String>,
66
67 #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")]
71 #[ruma_api(query)]
72 pub timeout: Option<Duration>,
73
74 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
78 #[ruma_api(query)]
79 pub set_presence: PresenceState,
80
81 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
83 pub lists: BTreeMap<String, request::List>,
84
85 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
90 pub room_subscriptions: BTreeMap<OwnedRoomId, request::RoomSubscription>,
91
92 #[serde(default, skip_serializing_if = "request::Extensions::is_empty")]
94 pub extensions: request::Extensions,
95}
96
97impl Request {
98 pub fn new() -> Self {
100 Default::default()
101 }
102}
103
104pub mod request {
106 use ruma_common::{RoomId, directory::RoomTypeFilter, serde::deserialize_cow_str};
107 use serde::de::Error as _;
108
109 use super::{BTreeMap, Deserialize, OwnedRoomId, Serialize, StateEventType, UInt};
110
111 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
113 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
114 pub struct List {
115 pub ranges: Vec<(UInt, UInt)>,
117
118 #[serde(flatten)]
120 pub room_details: RoomDetails,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub filters: Option<ListFilters>,
125 }
126
127 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
132 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
133 pub struct ListFilters {
134 #[serde(skip_serializing_if = "Option::is_none")]
137 pub is_dm: Option<bool>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
142 pub is_encrypted: Option<bool>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub is_invite: Option<bool>,
147
148 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
153 pub room_types: Vec<RoomTypeFilter>,
154
155 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
160 pub not_room_types: Vec<RoomTypeFilter>,
161 }
162
163 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
165 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
166 pub struct RoomSubscription {
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
170 pub required_state: Vec<(StateEventType, String)>,
171
172 pub timeline_limit: UInt,
174 }
175
176 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
178 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
179 pub struct RoomDetails {
180 #[serde(default, skip_serializing_if = "Vec::is_empty")]
182 pub required_state: Vec<(StateEventType, String)>,
183
184 pub timeline_limit: UInt,
186 }
187
188 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
190 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
191 pub struct Extensions {
192 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
194 pub to_device: ToDevice,
195
196 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
198 pub e2ee: E2EE,
199
200 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
202 pub account_data: AccountData,
203
204 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
206 pub receipts: Receipts,
207
208 #[serde(default, skip_serializing_if = "Typing::is_empty")]
210 pub typing: Typing,
211
212 #[cfg(feature = "unstable-msc4308")]
214 #[serde(
215 default,
216 skip_serializing_if = "ThreadSubscriptions::is_empty",
217 rename = "io.element.msc4308.thread_subscriptions"
218 )]
219 pub thread_subscriptions: ThreadSubscriptions,
220
221 #[cfg(feature = "unstable-msc4262")]
223 #[serde(
224 default,
225 skip_serializing_if = "Profiles::is_empty",
226 rename = "org.matrix.msc4262.profiles"
227 )]
228 pub profiles: Profiles,
229
230 #[cfg(feature = "unstable-msc4480")]
232 #[serde(
233 default,
234 skip_serializing_if = "StickyEvents::is_empty",
235 rename = "org.matrix.msc4354.sticky_events"
236 )]
237 pub sticky_events: StickyEvents,
238
239 #[cfg(feature = "unstable-msc4559")]
241 #[serde(
242 default,
243 skip_serializing_if = "Presence::is_empty",
244 rename = "org.continuwuity.presence_v2.msc4559.presence"
245 )]
246 pub presence: Presence,
247
248 #[serde(flatten)]
250 other: BTreeMap<String, serde_json::Value>,
251 }
252
253 impl Extensions {
254 pub fn is_empty(&self) -> bool {
256 let mut empty = self.to_device.is_empty()
257 && self.e2ee.is_empty()
258 && self.account_data.is_empty()
259 && self.receipts.is_empty()
260 && self.typing.is_empty()
261 && self.other.is_empty();
262
263 #[cfg(feature = "unstable-msc4308")]
264 {
265 empty = empty && self.thread_subscriptions.is_empty();
266 }
267
268 #[cfg(feature = "unstable-msc4262")]
269 {
270 empty = empty && self.profiles.is_empty();
271 }
272
273 #[cfg(feature = "unstable-msc4480")]
274 {
275 empty = empty && self.sticky_events.is_empty();
276 }
277
278 #[cfg(feature = "unstable-msc4559")]
279 {
280 empty = empty && self.presence.is_empty();
281 }
282
283 empty
284 }
285 }
286
287 #[derive(Clone, Debug, PartialEq)]
289 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
290 pub enum ExtensionRoomConfig {
291 AllSubscribed,
293
294 Room(OwnedRoomId),
296 }
297
298 impl Serialize for ExtensionRoomConfig {
299 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
300 where
301 S: serde::Serializer,
302 {
303 match self {
304 Self::AllSubscribed => serializer.serialize_str("*"),
305 Self::Room(r) => r.serialize(serializer),
306 }
307 }
308 }
309
310 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
311 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
312 where
313 D: serde::de::Deserializer<'de>,
314 {
315 match deserialize_cow_str(deserializer)?.as_ref() {
316 "*" => Ok(Self::AllSubscribed),
317 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?)),
318 }
319 }
320 }
321
322 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
326 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
327 pub struct ToDevice {
328 #[serde(skip_serializing_if = "Option::is_none")]
330 pub enabled: Option<bool>,
331
332 #[serde(skip_serializing_if = "Option::is_none")]
334 pub limit: Option<UInt>,
335
336 #[serde(skip_serializing_if = "Option::is_none")]
338 pub since: Option<String>,
339 }
340
341 impl ToDevice {
342 pub fn is_empty(&self) -> bool {
344 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
345 }
346 }
347
348 #[cfg(feature = "unstable-msc4480")]
352 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
353 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
354 pub struct StickyEvents {
355 #[serde(skip_serializing_if = "Option::is_none")]
357 pub enabled: Option<bool>,
358
359 #[serde(skip_serializing_if = "Option::is_none")]
363 pub limit: Option<UInt>,
364
365 #[serde(skip_serializing_if = "Option::is_none")]
367 pub since: Option<String>,
368 }
369
370 #[cfg(feature = "unstable-msc4480")]
371 impl StickyEvents {
372 pub fn is_empty(&self) -> bool {
374 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
375 }
376 }
377
378 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
382 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
383 pub struct E2EE {
384 #[serde(skip_serializing_if = "Option::is_none")]
386 pub enabled: Option<bool>,
387 }
388
389 impl E2EE {
390 pub fn is_empty(&self) -> bool {
392 self.enabled.is_none()
393 }
394 }
395
396 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
401 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
402 pub struct AccountData {
403 #[serde(skip_serializing_if = "Option::is_none")]
405 pub enabled: Option<bool>,
406
407 #[serde(skip_serializing_if = "Option::is_none")]
414 pub lists: Option<Vec<String>>,
415
416 #[serde(skip_serializing_if = "Option::is_none")]
424 pub rooms: Option<Vec<ExtensionRoomConfig>>,
425 }
426
427 impl AccountData {
428 pub fn is_empty(&self) -> bool {
430 self.enabled.is_none()
431 }
432 }
433
434 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
438 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
439 pub struct Receipts {
440 #[serde(skip_serializing_if = "Option::is_none")]
442 pub enabled: Option<bool>,
443
444 #[serde(skip_serializing_if = "Option::is_none")]
449 pub lists: Option<Vec<String>>,
450
451 #[serde(skip_serializing_if = "Option::is_none")]
457 pub rooms: Option<Vec<ExtensionRoomConfig>>,
458 }
459
460 impl Receipts {
461 pub fn is_empty(&self) -> bool {
463 self.enabled.is_none()
464 }
465 }
466
467 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
472 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
473 pub struct Typing {
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub enabled: Option<bool>,
477
478 #[serde(skip_serializing_if = "Option::is_none")]
483 pub lists: Option<Vec<String>>,
484
485 #[serde(skip_serializing_if = "Option::is_none")]
491 pub rooms: Option<Vec<ExtensionRoomConfig>>,
492 }
493
494 impl Typing {
495 pub fn is_empty(&self) -> bool {
497 self.enabled.is_none()
498 }
499 }
500
501 #[cfg(feature = "unstable-msc4308")]
505 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
506 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
507 pub struct ThreadSubscriptions {
508 #[serde(skip_serializing_if = "Option::is_none")]
510 pub enabled: Option<bool>,
511
512 #[serde(skip_serializing_if = "Option::is_none")]
517 pub limit: Option<UInt>,
518 }
519
520 #[cfg(feature = "unstable-msc4308")]
521 impl ThreadSubscriptions {
522 pub fn is_empty(&self) -> bool {
524 self.enabled.is_none() && self.limit.is_none()
525 }
526 }
527
528 #[cfg(feature = "unstable-msc4262")]
532 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
533 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
534 pub struct Profiles {
535 #[serde(skip_serializing_if = "Option::is_none")]
537 pub enabled: Option<bool>,
538
539 #[serde(skip_serializing_if = "Option::is_none")]
544 pub lists: Option<Vec<String>>,
545
546 #[serde(skip_serializing_if = "Option::is_none")]
552 pub rooms: Option<Vec<ExtensionRoomConfig>>,
553
554 #[serde(skip_serializing_if = "Option::is_none")]
557 pub fields: Option<Vec<ruma_common::profile::ProfileFieldName>>,
558 }
559
560 #[cfg(feature = "unstable-msc4262")]
561 impl Profiles {
562 pub fn is_empty(&self) -> bool {
564 self.enabled.is_none()
565 }
566 }
567
568 #[cfg(feature = "unstable-msc4559")]
572 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
573 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
574 pub struct Presence {
575 #[serde(skip_serializing_if = "Option::is_none")]
577 pub enabled: Option<bool>,
578
579 #[serde(skip_serializing_if = "Option::is_none")]
584 pub lists: Option<Vec<String>>,
585
586 #[serde(skip_serializing_if = "Option::is_none")]
592 pub rooms: Option<Vec<ExtensionRoomConfig>>,
593 }
594
595 #[cfg(feature = "unstable-msc4559")]
596 impl Presence {
597 pub fn is_empty(&self) -> bool {
599 self.enabled.is_none()
600 }
601 }
602}
603
604#[response]
606pub struct Response {
607 #[serde(skip_serializing_if = "Option::is_none")]
609 pub txn_id: Option<String>,
610
611 pub pos: String,
614
615 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
617 pub lists: BTreeMap<String, response::List>,
618
619 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
621 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
622
623 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
625 pub extensions: response::Extensions,
626}
627
628impl Response {
629 pub fn new(pos: String) -> Self {
631 Self {
632 txn_id: None,
633 pos,
634 lists: Default::default(),
635 rooms: Default::default(),
636 extensions: Default::default(),
637 }
638 }
639}
640
641pub mod response {
643 use ruma_common::OneTimeKeyAlgorithm;
644 #[cfg(feature = "unstable-msc4308")]
645 use ruma_common::OwnedEventId;
646 #[cfg(feature = "unstable-msc4559")]
647 use ruma_common::presence::{PresenceState, PresenceStatus};
648 #[cfg(feature = "unstable-msc4262")]
649 use ruma_common::profile::UserProfileUpdate;
650 use ruma_events::{
651 AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
652 AnyToDeviceEvent, receipt::SyncReceiptEvent, typing::SyncTypingEvent,
653 };
654
655 use super::{
656 super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
657 JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
658 UnreadNotificationsCount,
659 };
660 #[cfg(feature = "unstable-msc4308")]
661 use crate::threads::get_thread_subscriptions_changes::unstable::{
662 ThreadSubscription, ThreadUnsubscription,
663 };
664
665 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
668 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
669 pub struct List {
670 pub count: UInt,
672 }
673
674 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
676 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
677 pub struct Room {
678 #[serde(skip_serializing_if = "Option::is_none")]
683 #[cfg_attr(
684 feature = "unstable-compat-lax-syncv5-deser",
685 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
686 )]
687 pub name: Option<String>,
688
689 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
694 #[cfg_attr(
695 feature = "unstable-compat-lax-syncv5-deser",
696 serde(deserialize_with = "ruma_common::serde::default_on_error")
697 )]
698 pub avatar: JsOption<OwnedMxcUri>,
699
700 #[serde(skip_serializing_if = "Option::is_none")]
702 pub initial: Option<bool>,
703
704 #[serde(skip_serializing_if = "Option::is_none")]
706 pub is_dm: Option<bool>,
707
708 #[serde(skip_serializing_if = "Option::is_none")]
711 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
712
713 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
715 pub unread_notifications: UnreadNotificationsCount,
716
717 #[serde(default, skip_serializing_if = "Vec::is_empty")]
719 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
720
721 #[serde(default, skip_serializing_if = "Vec::is_empty")]
723 pub required_state: Vec<Raw<AnySyncStateEvent>>,
724
725 #[serde(skip_serializing_if = "Option::is_none")]
728 pub prev_batch: Option<String>,
729
730 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
733 pub limited: bool,
734
735 #[serde(skip_serializing_if = "Option::is_none")]
738 pub joined_count: Option<UInt>,
739
740 #[serde(skip_serializing_if = "Option::is_none")]
742 pub invited_count: Option<UInt>,
743
744 #[serde(skip_serializing_if = "Option::is_none")]
747 pub num_live: Option<UInt>,
748
749 #[serde(skip_serializing_if = "Option::is_none")]
756 pub bump_stamp: Option<UInt>,
757
758 #[serde(skip_serializing_if = "Option::is_none")]
760 pub heroes: Option<Vec<Hero>>,
761 }
762
763 impl Room {
764 pub fn new() -> Self {
766 Default::default()
767 }
768 }
769
770 #[derive(Clone, Debug, Deserialize, Serialize)]
772 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
773 pub struct Hero {
774 pub user_id: OwnedUserId,
776
777 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
782 #[cfg_attr(
783 feature = "unstable-compat-lax-syncv5-deser",
784 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
785 )]
786 pub name: Option<String>,
787
788 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
793 #[cfg_attr(
794 feature = "unstable-compat-lax-syncv5-deser",
795 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
796 )]
797 pub avatar: Option<OwnedMxcUri>,
798 }
799
800 impl Hero {
801 pub fn new(user_id: OwnedUserId) -> Self {
803 Self { user_id, name: None, avatar: None }
804 }
805 }
806
807 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
809 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
810 pub struct Extensions {
811 #[serde(skip_serializing_if = "Option::is_none")]
813 pub to_device: Option<ToDevice>,
814
815 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
817 pub e2ee: E2EE,
818
819 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
821 pub account_data: AccountData,
822
823 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
825 pub receipts: Receipts,
826
827 #[serde(default, skip_serializing_if = "Typing::is_empty")]
829 pub typing: Typing,
830
831 #[cfg(feature = "unstable-msc4308")]
833 #[serde(
834 default,
835 skip_serializing_if = "ThreadSubscriptions::is_empty",
836 rename = "io.element.msc4308.thread_subscriptions"
837 )]
838 pub thread_subscriptions: ThreadSubscriptions,
839
840 #[cfg(feature = "unstable-msc4262")]
842 #[serde(
843 default,
844 skip_serializing_if = "Profiles::is_empty",
845 rename = "org.matrix.msc4262.profiles"
846 )]
847 pub profiles: Profiles,
848
849 #[cfg(feature = "unstable-msc4480")]
851 #[serde(
852 default,
853 skip_serializing_if = "StickyEvents::is_empty",
854 rename = "org.matrix.msc4354.sticky_events"
855 )]
856 pub sticky_events: StickyEvents,
857
858 #[cfg(feature = "unstable-msc4559")]
860 #[serde(
861 default,
862 skip_serializing_if = "Presence::is_empty",
863 rename = "org.continuwuity.presence_v2.msc4559.presence"
864 )]
865 pub presence: Presence,
866 }
867
868 impl Extensions {
869 pub fn is_empty(&self) -> bool {
873 let mut empty = self.to_device.is_none()
874 && self.e2ee.is_empty()
875 && self.account_data.is_empty()
876 && self.receipts.is_empty()
877 && self.typing.is_empty();
878
879 #[cfg(feature = "unstable-msc4308")]
880 {
881 empty = empty && self.thread_subscriptions.is_empty();
882 }
883
884 #[cfg(feature = "unstable-msc4262")]
885 {
886 empty = empty && self.profiles.is_empty();
887 }
888
889 #[cfg(feature = "unstable-msc4480")]
890 {
891 empty = empty && self.sticky_events.is_empty();
892 }
893
894 #[cfg(feature = "unstable-msc4559")]
895 {
896 empty = empty && self.presence.is_empty();
897 }
898
899 empty
900 }
901 }
902
903 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
907 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
908 pub struct ToDevice {
909 pub next_batch: String,
911
912 #[serde(default, skip_serializing_if = "Vec::is_empty")]
914 pub events: Vec<Raw<AnyToDeviceEvent>>,
915 }
916
917 #[cfg(feature = "unstable-msc4480")]
921 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
922 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
923 pub struct StickyEvents {
924 #[serde(skip_serializing_if = "Option::is_none")]
928 pub next_batch: Option<String>,
929
930 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
932 pub rooms: BTreeMap<OwnedRoomId, StickyEventsRoom>,
933 }
934
935 #[cfg(feature = "unstable-msc4480")]
936 impl StickyEvents {
937 pub fn is_empty(&self) -> bool {
939 self.next_batch.is_none() && self.rooms.is_empty()
940 }
941 }
942
943 #[cfg(feature = "unstable-msc4480")]
945 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
946 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
947 pub struct StickyEventsRoom {
948 #[serde(default, skip_serializing_if = "Vec::is_empty")]
950 pub events: Vec<Raw<AnySyncTimelineEvent>>,
951 }
952
953 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
957 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
958 pub struct E2EE {
959 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
961 pub device_lists: DeviceLists,
962
963 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
966 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
967
968 #[serde(skip_serializing_if = "Option::is_none")]
973 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
974 }
975
976 impl E2EE {
977 pub fn is_empty(&self) -> bool {
979 self.device_lists.is_empty()
980 && self.device_one_time_keys_count.is_empty()
981 && self.device_unused_fallback_key_types.is_none()
982 }
983 }
984
985 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
990 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
991 pub struct AccountData {
992 #[serde(default, skip_serializing_if = "Vec::is_empty")]
994 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
995
996 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
998 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
999 }
1000
1001 impl AccountData {
1002 pub fn is_empty(&self) -> bool {
1004 self.global.is_empty() && self.rooms.is_empty()
1005 }
1006 }
1007
1008 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1012 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1013 pub struct Receipts {
1014 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1016 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
1017 }
1018
1019 impl Receipts {
1020 pub fn is_empty(&self) -> bool {
1022 self.rooms.is_empty()
1023 }
1024 }
1025
1026 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1031 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1032 pub struct Typing {
1033 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1035 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
1036 }
1037
1038 impl Typing {
1039 pub fn is_empty(&self) -> bool {
1041 self.rooms.is_empty()
1042 }
1043 }
1044
1045 #[cfg(feature = "unstable-msc4308")]
1049 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1050 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1051 pub struct ThreadSubscriptions {
1052 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1054 pub subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
1055
1056 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1058 pub unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
1059
1060 #[serde(skip_serializing_if = "Option::is_none")]
1066 pub prev_batch: Option<String>,
1067 }
1068
1069 #[cfg(feature = "unstable-msc4308")]
1070 impl ThreadSubscriptions {
1071 pub fn is_empty(&self) -> bool {
1073 self.subscribed.is_empty() && self.unsubscribed.is_empty() && self.prev_batch.is_none()
1074 }
1075 }
1076
1077 #[cfg(feature = "unstable-msc4262")]
1081 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1082 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1083 pub struct Profiles {
1084 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1086 pub users: BTreeMap<OwnedUserId, UserProfileUpdate>,
1087 }
1088
1089 #[cfg(feature = "unstable-msc4262")]
1090 impl Profiles {
1091 pub fn is_empty(&self) -> bool {
1093 self.users.is_empty()
1094 }
1095 }
1096
1097 #[cfg(feature = "unstable-msc4559")]
1101 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1102 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1103 pub struct PresenceUpdate {
1104 pub presence: PresenceState,
1106
1107 pub status: PresenceStatus,
1111 }
1112
1113 #[cfg(feature = "unstable-msc4559")]
1117 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1118 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1119 pub struct Presence {
1120 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1122 pub user_ids: BTreeMap<OwnedUserId, PresenceUpdate>,
1123 }
1124
1125 #[cfg(feature = "unstable-msc4559")]
1126 impl Presence {
1127 pub fn is_empty(&self) -> bool {
1129 self.user_ids.is_empty()
1130 }
1131 }
1132}
1133
1134#[cfg(test)]
1135mod tests {
1136 use ruma_common::owned_room_id;
1137
1138 use super::request::ExtensionRoomConfig;
1139
1140 #[test]
1141 fn serialize_request_extension_room_config() {
1142 let entry = ExtensionRoomConfig::AllSubscribed;
1143 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
1144
1145 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
1146 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
1147 }
1148
1149 #[test]
1150 fn deserialize_request_extension_room_config() {
1151 assert_eq!(
1152 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
1153 ExtensionRoomConfig::AllSubscribed
1154 );
1155
1156 assert_eq!(
1157 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
1158 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
1159 );
1160 }
1161
1162 #[cfg(feature = "unstable-msc4480")]
1163 #[test]
1164 fn sticky_events_extension_serde() {
1165 use ruma_common::assert_to_canonical_json_eq;
1166
1167 use super::{request, response};
1168
1169 let mut extensions = request::Extensions::default();
1171 extensions.sticky_events =
1172 request::StickyEvents { enabled: Some(true), ..Default::default() };
1173 assert_to_canonical_json_eq!(
1174 extensions,
1175 serde_json::json!({ "org.matrix.msc4354.sticky_events": { "enabled": true } })
1176 );
1177
1178 let response: response::Extensions = serde_json::from_value(serde_json::json!({
1180 "org.matrix.msc4354.sticky_events": {
1181 "next_batch": "s123",
1182 "rooms": {
1183 "!room:example.com": {
1184 "events": [{
1185 "content": { "body": "sticky", "msgtype": "m.text" },
1186 "event_id": "$1:example.com",
1187 "origin_server_ts": 1,
1188 "sender": "@alice:example.com",
1189 "type": "m.room.message",
1190 "msc4354_sticky": {
1191 "duration_ms": 300_000
1192 },
1193 }]
1194 }
1195 }
1196 }
1197 }))
1198 .unwrap();
1199
1200 assert_eq!(response.sticky_events.next_batch.as_deref(), Some("s123"));
1201 assert_eq!(response.sticky_events.rooms.len(), 1);
1202 let room = response.sticky_events.rooms.values().next().unwrap();
1203 assert_eq!(room.events.len(), 1);
1204 }
1205
1206 #[test]
1207 #[cfg(feature = "unstable-compat-lax-syncv5-deser")]
1208 fn deserialize_room_ignores_invalid_string_fields() {
1209 use super::response::Room;
1210
1211 let room: Room = serde_json::from_str(
1212 r#"{
1213 "name": {},
1214 "avatar": {},
1215 "heroes": [{ "user_id": "@alice:localhost", "displayname": {}, "avatar_url": {} }]
1216 }"#,
1217 )
1218 .unwrap();
1219
1220 assert_eq!(room.name, None);
1221 assert!(room.avatar.is_undefined());
1222 let hero = &room.heroes.unwrap()[0];
1223 assert_eq!(hero.name, None);
1224 assert_eq!(hero.avatar, None);
1225
1226 let room: Room = serde_json::from_str(
1228 r#"{ "name": "Room", "avatar": "mxc://localhost/a", "heroes": [{ "user_id": "@alice:localhost", "displayname": "Alice", "avatar_url": "mxc://localhost/b" }] }"#,
1229 )
1230 .unwrap();
1231
1232 assert_eq!(room.name.as_deref(), Some("Room"));
1233 assert_eq!(room.avatar.into_option().unwrap().as_str(), "mxc://localhost/a");
1234 let hero = &room.heroes.unwrap()[0];
1235 assert_eq!(hero.name.as_deref(), Some("Alice"));
1236 assert_eq!(hero.avatar.as_ref().unwrap().as_str(), "mxc://localhost/b");
1237 }
1238}