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 #[serde(flatten)]
241 other: BTreeMap<String, serde_json::Value>,
242 }
243
244 impl Extensions {
245 pub fn is_empty(&self) -> bool {
247 let mut empty = self.to_device.is_empty()
248 && self.e2ee.is_empty()
249 && self.account_data.is_empty()
250 && self.receipts.is_empty()
251 && self.typing.is_empty()
252 && self.other.is_empty();
253
254 #[cfg(feature = "unstable-msc4308")]
255 {
256 empty = empty && self.thread_subscriptions.is_empty();
257 }
258
259 #[cfg(feature = "unstable-msc4262")]
260 {
261 empty = empty && self.profiles.is_empty();
262 }
263
264 #[cfg(feature = "unstable-msc4480")]
265 {
266 empty = empty && self.sticky_events.is_empty();
267 }
268
269 empty
270 }
271 }
272
273 #[derive(Clone, Debug, PartialEq)]
275 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
276 pub enum ExtensionRoomConfig {
277 AllSubscribed,
279
280 Room(OwnedRoomId),
282 }
283
284 impl Serialize for ExtensionRoomConfig {
285 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
286 where
287 S: serde::Serializer,
288 {
289 match self {
290 Self::AllSubscribed => serializer.serialize_str("*"),
291 Self::Room(r) => r.serialize(serializer),
292 }
293 }
294 }
295
296 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
297 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
298 where
299 D: serde::de::Deserializer<'de>,
300 {
301 match deserialize_cow_str(deserializer)?.as_ref() {
302 "*" => Ok(Self::AllSubscribed),
303 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?)),
304 }
305 }
306 }
307
308 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
312 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
313 pub struct ToDevice {
314 #[serde(skip_serializing_if = "Option::is_none")]
316 pub enabled: Option<bool>,
317
318 #[serde(skip_serializing_if = "Option::is_none")]
320 pub limit: Option<UInt>,
321
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub since: Option<String>,
325 }
326
327 impl ToDevice {
328 pub fn is_empty(&self) -> bool {
330 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
331 }
332 }
333
334 #[cfg(feature = "unstable-msc4480")]
338 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
339 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
340 pub struct StickyEvents {
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub enabled: Option<bool>,
344
345 #[serde(skip_serializing_if = "Option::is_none")]
349 pub limit: Option<UInt>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
353 pub since: Option<String>,
354 }
355
356 #[cfg(feature = "unstable-msc4480")]
357 impl StickyEvents {
358 pub fn is_empty(&self) -> bool {
360 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
361 }
362 }
363
364 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
368 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
369 pub struct E2EE {
370 #[serde(skip_serializing_if = "Option::is_none")]
372 pub enabled: Option<bool>,
373 }
374
375 impl E2EE {
376 pub fn is_empty(&self) -> bool {
378 self.enabled.is_none()
379 }
380 }
381
382 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
387 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
388 pub struct AccountData {
389 #[serde(skip_serializing_if = "Option::is_none")]
391 pub enabled: Option<bool>,
392
393 #[serde(skip_serializing_if = "Option::is_none")]
400 pub lists: Option<Vec<String>>,
401
402 #[serde(skip_serializing_if = "Option::is_none")]
410 pub rooms: Option<Vec<ExtensionRoomConfig>>,
411 }
412
413 impl AccountData {
414 pub fn is_empty(&self) -> bool {
416 self.enabled.is_none()
417 }
418 }
419
420 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
424 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
425 pub struct Receipts {
426 #[serde(skip_serializing_if = "Option::is_none")]
428 pub enabled: Option<bool>,
429
430 #[serde(skip_serializing_if = "Option::is_none")]
435 pub lists: Option<Vec<String>>,
436
437 #[serde(skip_serializing_if = "Option::is_none")]
443 pub rooms: Option<Vec<ExtensionRoomConfig>>,
444 }
445
446 impl Receipts {
447 pub fn is_empty(&self) -> bool {
449 self.enabled.is_none()
450 }
451 }
452
453 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
458 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
459 pub struct Typing {
460 #[serde(skip_serializing_if = "Option::is_none")]
462 pub enabled: Option<bool>,
463
464 #[serde(skip_serializing_if = "Option::is_none")]
469 pub lists: Option<Vec<String>>,
470
471 #[serde(skip_serializing_if = "Option::is_none")]
477 pub rooms: Option<Vec<ExtensionRoomConfig>>,
478 }
479
480 impl Typing {
481 pub fn is_empty(&self) -> bool {
483 self.enabled.is_none()
484 }
485 }
486
487 #[cfg(feature = "unstable-msc4308")]
491 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
492 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
493 pub struct ThreadSubscriptions {
494 #[serde(skip_serializing_if = "Option::is_none")]
496 pub enabled: Option<bool>,
497
498 #[serde(skip_serializing_if = "Option::is_none")]
503 pub limit: Option<UInt>,
504 }
505
506 #[cfg(feature = "unstable-msc4308")]
507 impl ThreadSubscriptions {
508 pub fn is_empty(&self) -> bool {
510 self.enabled.is_none() && self.limit.is_none()
511 }
512 }
513
514 #[cfg(feature = "unstable-msc4262")]
518 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
519 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
520 pub struct Profiles {
521 #[serde(skip_serializing_if = "Option::is_none")]
523 pub enabled: Option<bool>,
524
525 #[serde(skip_serializing_if = "Option::is_none")]
530 pub lists: Option<Vec<String>>,
531
532 #[serde(skip_serializing_if = "Option::is_none")]
538 pub rooms: Option<Vec<ExtensionRoomConfig>>,
539
540 #[serde(skip_serializing_if = "Option::is_none")]
543 pub fields: Option<Vec<ruma_common::profile::ProfileFieldName>>,
544 }
545
546 #[cfg(feature = "unstable-msc4262")]
547 impl Profiles {
548 pub fn is_empty(&self) -> bool {
550 self.enabled.is_none()
551 }
552 }
553}
554
555#[response]
557pub struct Response {
558 #[serde(skip_serializing_if = "Option::is_none")]
560 pub txn_id: Option<String>,
561
562 pub pos: String,
565
566 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
568 pub lists: BTreeMap<String, response::List>,
569
570 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
572 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
573
574 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
576 pub extensions: response::Extensions,
577}
578
579impl Response {
580 pub fn new(pos: String) -> Self {
582 Self {
583 txn_id: None,
584 pos,
585 lists: Default::default(),
586 rooms: Default::default(),
587 extensions: Default::default(),
588 }
589 }
590}
591
592pub mod response {
594 use ruma_common::OneTimeKeyAlgorithm;
595 #[cfg(feature = "unstable-msc4308")]
596 use ruma_common::OwnedEventId;
597 #[cfg(feature = "unstable-msc4262")]
598 use ruma_common::profile::UserProfileUpdate;
599 use ruma_events::{
600 AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
601 AnyToDeviceEvent, receipt::SyncReceiptEvent, typing::SyncTypingEvent,
602 };
603
604 use super::{
605 super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
606 JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
607 UnreadNotificationsCount,
608 };
609 #[cfg(feature = "unstable-msc4308")]
610 use crate::threads::get_thread_subscriptions_changes::unstable::{
611 ThreadSubscription, ThreadUnsubscription,
612 };
613
614 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
617 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
618 pub struct List {
619 pub count: UInt,
621 }
622
623 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
625 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
626 pub struct Room {
627 #[serde(skip_serializing_if = "Option::is_none")]
632 #[cfg_attr(
633 feature = "unstable-compat-lax-syncv5-deser",
634 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
635 )]
636 pub name: Option<String>,
637
638 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
643 #[cfg_attr(
644 feature = "unstable-compat-lax-syncv5-deser",
645 serde(deserialize_with = "ruma_common::serde::default_on_error")
646 )]
647 pub avatar: JsOption<OwnedMxcUri>,
648
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub initial: Option<bool>,
652
653 #[serde(skip_serializing_if = "Option::is_none")]
655 pub is_dm: Option<bool>,
656
657 #[serde(skip_serializing_if = "Option::is_none")]
660 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
661
662 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
664 pub unread_notifications: UnreadNotificationsCount,
665
666 #[serde(default, skip_serializing_if = "Vec::is_empty")]
668 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
669
670 #[serde(default, skip_serializing_if = "Vec::is_empty")]
672 pub required_state: Vec<Raw<AnySyncStateEvent>>,
673
674 #[serde(skip_serializing_if = "Option::is_none")]
677 pub prev_batch: Option<String>,
678
679 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
682 pub limited: bool,
683
684 #[serde(skip_serializing_if = "Option::is_none")]
687 pub joined_count: Option<UInt>,
688
689 #[serde(skip_serializing_if = "Option::is_none")]
691 pub invited_count: Option<UInt>,
692
693 #[serde(skip_serializing_if = "Option::is_none")]
696 pub num_live: Option<UInt>,
697
698 #[serde(skip_serializing_if = "Option::is_none")]
705 pub bump_stamp: Option<UInt>,
706
707 #[serde(skip_serializing_if = "Option::is_none")]
709 pub heroes: Option<Vec<Hero>>,
710 }
711
712 impl Room {
713 pub fn new() -> Self {
715 Default::default()
716 }
717 }
718
719 #[derive(Clone, Debug, Deserialize, Serialize)]
721 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
722 pub struct Hero {
723 pub user_id: OwnedUserId,
725
726 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
731 #[cfg_attr(
732 feature = "unstable-compat-lax-syncv5-deser",
733 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
734 )]
735 pub name: Option<String>,
736
737 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
742 #[cfg_attr(
743 feature = "unstable-compat-lax-syncv5-deser",
744 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
745 )]
746 pub avatar: Option<OwnedMxcUri>,
747 }
748
749 impl Hero {
750 pub fn new(user_id: OwnedUserId) -> Self {
752 Self { user_id, name: None, avatar: None }
753 }
754 }
755
756 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
758 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
759 pub struct Extensions {
760 #[serde(skip_serializing_if = "Option::is_none")]
762 pub to_device: Option<ToDevice>,
763
764 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
766 pub e2ee: E2EE,
767
768 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
770 pub account_data: AccountData,
771
772 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
774 pub receipts: Receipts,
775
776 #[serde(default, skip_serializing_if = "Typing::is_empty")]
778 pub typing: Typing,
779
780 #[cfg(feature = "unstable-msc4308")]
782 #[serde(
783 default,
784 skip_serializing_if = "ThreadSubscriptions::is_empty",
785 rename = "io.element.msc4308.thread_subscriptions"
786 )]
787 pub thread_subscriptions: ThreadSubscriptions,
788
789 #[cfg(feature = "unstable-msc4262")]
791 #[serde(
792 default,
793 skip_serializing_if = "Profiles::is_empty",
794 rename = "org.matrix.msc4262.profiles"
795 )]
796 pub profiles: Profiles,
797
798 #[cfg(feature = "unstable-msc4480")]
800 #[serde(
801 default,
802 skip_serializing_if = "StickyEvents::is_empty",
803 rename = "org.matrix.msc4354.sticky_events"
804 )]
805 pub sticky_events: StickyEvents,
806 }
807
808 impl Extensions {
809 pub fn is_empty(&self) -> bool {
813 let mut empty = self.to_device.is_none()
814 && self.e2ee.is_empty()
815 && self.account_data.is_empty()
816 && self.receipts.is_empty()
817 && self.typing.is_empty();
818
819 #[cfg(feature = "unstable-msc4308")]
820 {
821 empty = empty && self.thread_subscriptions.is_empty();
822 }
823
824 #[cfg(feature = "unstable-msc4262")]
825 {
826 empty = empty && self.profiles.is_empty();
827 }
828
829 #[cfg(feature = "unstable-msc4480")]
830 {
831 empty = empty && self.sticky_events.is_empty();
832 }
833
834 empty
835 }
836 }
837
838 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
842 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
843 pub struct ToDevice {
844 pub next_batch: String,
846
847 #[serde(default, skip_serializing_if = "Vec::is_empty")]
849 pub events: Vec<Raw<AnyToDeviceEvent>>,
850 }
851
852 #[cfg(feature = "unstable-msc4480")]
856 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
857 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
858 pub struct StickyEvents {
859 #[serde(skip_serializing_if = "Option::is_none")]
863 pub next_batch: Option<String>,
864
865 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
867 pub rooms: BTreeMap<OwnedRoomId, StickyEventsRoom>,
868 }
869
870 #[cfg(feature = "unstable-msc4480")]
871 impl StickyEvents {
872 pub fn is_empty(&self) -> bool {
874 self.next_batch.is_none() && self.rooms.is_empty()
875 }
876 }
877
878 #[cfg(feature = "unstable-msc4480")]
880 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
881 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
882 pub struct StickyEventsRoom {
883 #[serde(default, skip_serializing_if = "Vec::is_empty")]
885 pub events: Vec<Raw<AnySyncTimelineEvent>>,
886 }
887
888 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
892 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
893 pub struct E2EE {
894 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
896 pub device_lists: DeviceLists,
897
898 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
901 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
902
903 #[serde(skip_serializing_if = "Option::is_none")]
908 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
909 }
910
911 impl E2EE {
912 pub fn is_empty(&self) -> bool {
914 self.device_lists.is_empty()
915 && self.device_one_time_keys_count.is_empty()
916 && self.device_unused_fallback_key_types.is_none()
917 }
918 }
919
920 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
925 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
926 pub struct AccountData {
927 #[serde(default, skip_serializing_if = "Vec::is_empty")]
929 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
930
931 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
933 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
934 }
935
936 impl AccountData {
937 pub fn is_empty(&self) -> bool {
939 self.global.is_empty() && self.rooms.is_empty()
940 }
941 }
942
943 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
947 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
948 pub struct Receipts {
949 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
951 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
952 }
953
954 impl Receipts {
955 pub fn is_empty(&self) -> bool {
957 self.rooms.is_empty()
958 }
959 }
960
961 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
966 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
967 pub struct Typing {
968 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
970 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
971 }
972
973 impl Typing {
974 pub fn is_empty(&self) -> bool {
976 self.rooms.is_empty()
977 }
978 }
979
980 #[cfg(feature = "unstable-msc4308")]
984 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
985 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
986 pub struct ThreadSubscriptions {
987 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
989 pub subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
990
991 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
993 pub unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
994
995 #[serde(skip_serializing_if = "Option::is_none")]
1001 pub prev_batch: Option<String>,
1002 }
1003
1004 #[cfg(feature = "unstable-msc4308")]
1005 impl ThreadSubscriptions {
1006 pub fn is_empty(&self) -> bool {
1008 self.subscribed.is_empty() && self.unsubscribed.is_empty() && self.prev_batch.is_none()
1009 }
1010 }
1011
1012 #[cfg(feature = "unstable-msc4262")]
1016 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
1017 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
1018 pub struct Profiles {
1019 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
1021 pub users: BTreeMap<OwnedUserId, UserProfileUpdate>,
1022 }
1023
1024 #[cfg(feature = "unstable-msc4262")]
1025 impl Profiles {
1026 pub fn is_empty(&self) -> bool {
1028 self.users.is_empty()
1029 }
1030 }
1031}
1032
1033#[cfg(test)]
1034mod tests {
1035 use ruma_common::owned_room_id;
1036
1037 use super::request::ExtensionRoomConfig;
1038
1039 #[test]
1040 fn serialize_request_extension_room_config() {
1041 let entry = ExtensionRoomConfig::AllSubscribed;
1042 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
1043
1044 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
1045 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
1046 }
1047
1048 #[test]
1049 fn deserialize_request_extension_room_config() {
1050 assert_eq!(
1051 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
1052 ExtensionRoomConfig::AllSubscribed
1053 );
1054
1055 assert_eq!(
1056 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
1057 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
1058 );
1059 }
1060
1061 #[cfg(feature = "unstable-msc4480")]
1062 #[test]
1063 fn sticky_events_extension_serde() {
1064 use ruma_common::assert_to_canonical_json_eq;
1065
1066 use super::{request, response};
1067
1068 let mut extensions = request::Extensions::default();
1070 extensions.sticky_events =
1071 request::StickyEvents { enabled: Some(true), ..Default::default() };
1072 assert_to_canonical_json_eq!(
1073 extensions,
1074 serde_json::json!({ "org.matrix.msc4354.sticky_events": { "enabled": true } })
1075 );
1076
1077 let response: response::Extensions = serde_json::from_value(serde_json::json!({
1079 "org.matrix.msc4354.sticky_events": {
1080 "next_batch": "s123",
1081 "rooms": {
1082 "!room:example.com": {
1083 "events": [{
1084 "content": { "body": "sticky", "msgtype": "m.text" },
1085 "event_id": "$1:example.com",
1086 "origin_server_ts": 1,
1087 "sender": "@alice:example.com",
1088 "type": "m.room.message",
1089 "msc4354_sticky": {
1090 "duration_ms": 300_000
1091 },
1092 }]
1093 }
1094 }
1095 }
1096 }))
1097 .unwrap();
1098
1099 assert_eq!(response.sticky_events.next_batch.as_deref(), Some("s123"));
1100 assert_eq!(response.sticky_events.rooms.len(), 1);
1101 let room = response.sticky_events.rooms.values().next().unwrap();
1102 assert_eq!(room.events.len(), 1);
1103 }
1104
1105 #[test]
1106 #[cfg(feature = "unstable-compat-lax-syncv5-deser")]
1107 fn deserialize_room_ignores_invalid_string_fields() {
1108 use super::response::Room;
1109
1110 let room: Room = serde_json::from_str(
1111 r#"{
1112 "name": {},
1113 "avatar": {},
1114 "heroes": [{ "user_id": "@alice:localhost", "displayname": {}, "avatar_url": {} }]
1115 }"#,
1116 )
1117 .unwrap();
1118
1119 assert_eq!(room.name, None);
1120 assert!(room.avatar.is_undefined());
1121 let hero = &room.heroes.unwrap()[0];
1122 assert_eq!(hero.name, None);
1123 assert_eq!(hero.avatar, None);
1124
1125 let room: Room = serde_json::from_str(
1127 r#"{ "name": "Room", "avatar": "mxc://localhost/a", "heroes": [{ "user_id": "@alice:localhost", "displayname": "Alice", "avatar_url": "mxc://localhost/b" }] }"#,
1128 )
1129 .unwrap();
1130
1131 assert_eq!(room.name.as_deref(), Some("Room"));
1132 assert_eq!(room.avatar.into_option().unwrap().as_str(), "mxc://localhost/a");
1133 let hero = &room.heroes.unwrap()[0];
1134 assert_eq!(hero.name.as_deref(), Some("Alice"));
1135 assert_eq!(hero.avatar.as_ref().unwrap().as_str(), "mxc://localhost/b");
1136 }
1137}