1use std::{collections::BTreeMap, time::Duration};
11
12use js_int::UInt;
13use js_option::JsOption;
14use ruma_common::{
15 api::{request, response},
16 metadata,
17 presence::PresenceState,
18 serde::{duration::opt_ms, Raw},
19 OwnedMxcUri, OwnedRoomId, OwnedUserId,
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(error = crate::Error)]
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::{directory::RoomTypeFilter, serde::deserialize_cow_str, RoomId};
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 #[serde(flatten)]
223 other: BTreeMap<String, serde_json::Value>,
224 }
225
226 impl Extensions {
227 pub fn is_empty(&self) -> bool {
229 self.to_device.is_empty()
230 && self.e2ee.is_empty()
231 && self.account_data.is_empty()
232 && self.receipts.is_empty()
233 && self.typing.is_empty()
234 && self.other.is_empty()
235 }
236 }
237
238 #[derive(Clone, Debug, PartialEq)]
240 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
241 pub enum ExtensionRoomConfig {
242 AllSubscribed,
244
245 Room(OwnedRoomId),
247 }
248
249 impl Serialize for ExtensionRoomConfig {
250 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
251 where
252 S: serde::Serializer,
253 {
254 match self {
255 Self::AllSubscribed => serializer.serialize_str("*"),
256 Self::Room(r) => r.serialize(serializer),
257 }
258 }
259 }
260
261 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
262 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
263 where
264 D: serde::de::Deserializer<'de>,
265 {
266 match deserialize_cow_str(deserializer)?.as_ref() {
267 "*" => Ok(Self::AllSubscribed),
268 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
269 }
270 }
271 }
272
273 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
277 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
278 pub struct ToDevice {
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub enabled: Option<bool>,
282
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub limit: Option<UInt>,
286
287 #[serde(skip_serializing_if = "Option::is_none")]
289 pub since: Option<String>,
290 }
291
292 impl ToDevice {
293 pub fn is_empty(&self) -> bool {
295 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
296 }
297 }
298
299 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
303 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
304 pub struct E2EE {
305 #[serde(skip_serializing_if = "Option::is_none")]
307 pub enabled: Option<bool>,
308 }
309
310 impl E2EE {
311 pub fn is_empty(&self) -> bool {
313 self.enabled.is_none()
314 }
315 }
316
317 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
322 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
323 pub struct AccountData {
324 #[serde(skip_serializing_if = "Option::is_none")]
326 pub enabled: Option<bool>,
327
328 #[serde(skip_serializing_if = "Option::is_none")]
335 pub lists: Option<Vec<String>>,
336
337 #[serde(skip_serializing_if = "Option::is_none")]
345 pub rooms: Option<Vec<ExtensionRoomConfig>>,
346 }
347
348 impl AccountData {
349 pub fn is_empty(&self) -> bool {
351 self.enabled.is_none()
352 }
353 }
354
355 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
359 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
360 pub struct Receipts {
361 #[serde(skip_serializing_if = "Option::is_none")]
363 pub enabled: Option<bool>,
364
365 #[serde(skip_serializing_if = "Option::is_none")]
370 pub lists: Option<Vec<String>>,
371
372 #[serde(skip_serializing_if = "Option::is_none")]
378 pub rooms: Option<Vec<ExtensionRoomConfig>>,
379 }
380
381 impl Receipts {
382 pub fn is_empty(&self) -> bool {
384 self.enabled.is_none()
385 }
386 }
387
388 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
393 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
394 pub struct Typing {
395 #[serde(skip_serializing_if = "Option::is_none")]
397 pub enabled: Option<bool>,
398
399 #[serde(skip_serializing_if = "Option::is_none")]
404 pub lists: Option<Vec<String>>,
405
406 #[serde(skip_serializing_if = "Option::is_none")]
412 pub rooms: Option<Vec<ExtensionRoomConfig>>,
413 }
414
415 impl Typing {
416 pub fn is_empty(&self) -> bool {
418 self.enabled.is_none()
419 }
420 }
421
422 #[cfg(feature = "unstable-msc4308")]
426 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
427 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
428 pub struct ThreadSubscriptions {
429 #[serde(skip_serializing_if = "Option::is_none")]
431 pub enabled: Option<bool>,
432
433 #[serde(skip_serializing_if = "Option::is_none")]
438 pub limit: Option<UInt>,
439 }
440
441 #[cfg(feature = "unstable-msc4308")]
442 impl ThreadSubscriptions {
443 pub fn is_empty(&self) -> bool {
445 self.enabled.is_none() && self.limit.is_none()
446 }
447 }
448}
449
450#[response(error = crate::Error)]
452pub struct Response {
453 #[serde(skip_serializing_if = "Option::is_none")]
455 pub txn_id: Option<String>,
456
457 pub pos: String,
460
461 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
463 pub lists: BTreeMap<String, response::List>,
464
465 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
467 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
468
469 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
471 pub extensions: response::Extensions,
472}
473
474impl Response {
475 pub fn new(pos: String) -> Self {
477 Self {
478 txn_id: None,
479 pos,
480 lists: Default::default(),
481 rooms: Default::default(),
482 extensions: Default::default(),
483 }
484 }
485}
486
487pub mod response {
489 use ruma_common::OneTimeKeyAlgorithm;
490 #[cfg(feature = "unstable-msc4308")]
491 use ruma_common::OwnedEventId;
492 use ruma_events::{
493 receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
494 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnyToDeviceEvent,
495 };
496
497 use super::{
498 super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
499 JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
500 UnreadNotificationsCount,
501 };
502 #[cfg(feature = "unstable-msc4308")]
503 use crate::threads::get_thread_subscriptions_changes::unstable::{
504 ThreadSubscription, ThreadUnsubscription,
505 };
506
507 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
510 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
511 pub struct List {
512 pub count: UInt,
514 }
515
516 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
518 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
519 pub struct Room {
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub name: Option<String>,
523
524 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
526 pub avatar: JsOption<OwnedMxcUri>,
527
528 #[serde(skip_serializing_if = "Option::is_none")]
530 pub initial: Option<bool>,
531
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub is_dm: Option<bool>,
535
536 #[serde(skip_serializing_if = "Option::is_none")]
539 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
540
541 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
543 pub unread_notifications: UnreadNotificationsCount,
544
545 #[serde(default, skip_serializing_if = "Vec::is_empty")]
547 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
548
549 #[serde(default, skip_serializing_if = "Vec::is_empty")]
551 pub required_state: Vec<Raw<AnySyncStateEvent>>,
552
553 #[serde(skip_serializing_if = "Option::is_none")]
556 pub prev_batch: Option<String>,
557
558 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
561 pub limited: bool,
562
563 #[serde(skip_serializing_if = "Option::is_none")]
566 pub joined_count: Option<UInt>,
567
568 #[serde(skip_serializing_if = "Option::is_none")]
570 pub invited_count: Option<UInt>,
571
572 #[serde(skip_serializing_if = "Option::is_none")]
575 pub num_live: Option<UInt>,
576
577 #[serde(skip_serializing_if = "Option::is_none")]
584 pub bump_stamp: Option<UInt>,
585
586 #[serde(skip_serializing_if = "Option::is_none")]
588 pub heroes: Option<Vec<Hero>>,
589 }
590
591 impl Room {
592 pub fn new() -> Self {
594 Default::default()
595 }
596 }
597
598 #[derive(Clone, Debug, Deserialize, Serialize)]
600 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
601 pub struct Hero {
602 pub user_id: OwnedUserId,
604
605 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
607 pub name: Option<String>,
608
609 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
611 pub avatar: Option<OwnedMxcUri>,
612 }
613
614 impl Hero {
615 pub fn new(user_id: OwnedUserId) -> Self {
617 Self { user_id, name: None, avatar: None }
618 }
619 }
620
621 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
623 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
624 pub struct Extensions {
625 #[serde(skip_serializing_if = "Option::is_none")]
627 pub to_device: Option<ToDevice>,
628
629 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
631 pub e2ee: E2EE,
632
633 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
635 pub account_data: AccountData,
636
637 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
639 pub receipts: Receipts,
640
641 #[serde(default, skip_serializing_if = "Typing::is_empty")]
643 pub typing: Typing,
644
645 #[cfg(feature = "unstable-msc4308")]
647 #[serde(
648 default,
649 skip_serializing_if = "ThreadSubscriptions::is_empty",
650 rename = "io.element.msc4308.thread_subscriptions"
651 )]
652 pub thread_subscriptions: ThreadSubscriptions,
653 }
654
655 impl Extensions {
656 pub fn is_empty(&self) -> bool {
660 self.to_device.is_none()
661 && self.e2ee.is_empty()
662 && self.account_data.is_empty()
663 && self.receipts.is_empty()
664 && self.typing.is_empty()
665 }
666 }
667
668 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
672 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
673 pub struct ToDevice {
674 pub next_batch: String,
676
677 #[serde(default, skip_serializing_if = "Vec::is_empty")]
679 pub events: Vec<Raw<AnyToDeviceEvent>>,
680 }
681
682 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
686 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
687 pub struct E2EE {
688 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
690 pub device_lists: DeviceLists,
691
692 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
695 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
696
697 #[serde(skip_serializing_if = "Option::is_none")]
702 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
703 }
704
705 impl E2EE {
706 pub fn is_empty(&self) -> bool {
708 self.device_lists.is_empty()
709 && self.device_one_time_keys_count.is_empty()
710 && self.device_unused_fallback_key_types.is_none()
711 }
712 }
713
714 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
719 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
720 pub struct AccountData {
721 #[serde(default, skip_serializing_if = "Vec::is_empty")]
723 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
724
725 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
727 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
728 }
729
730 impl AccountData {
731 pub fn is_empty(&self) -> bool {
733 self.global.is_empty() && self.rooms.is_empty()
734 }
735 }
736
737 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
741 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
742 pub struct Receipts {
743 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
745 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
746 }
747
748 impl Receipts {
749 pub fn is_empty(&self) -> bool {
751 self.rooms.is_empty()
752 }
753 }
754
755 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
760 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
761 pub struct Typing {
762 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
764 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
765 }
766
767 impl Typing {
768 pub fn is_empty(&self) -> bool {
770 self.rooms.is_empty()
771 }
772 }
773
774 #[cfg(feature = "unstable-msc4308")]
778 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
779 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
780 pub struct ThreadSubscriptions {
781 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
783 pub subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
784
785 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
787 pub unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
788
789 #[serde(skip_serializing_if = "Option::is_none")]
795 pub prev_batch: Option<String>,
796 }
797
798 #[cfg(feature = "unstable-msc4308")]
799 impl ThreadSubscriptions {
800 pub fn is_empty(&self) -> bool {
802 self.subscribed.is_empty() && self.unsubscribed.is_empty() && self.prev_batch.is_none()
803 }
804 }
805}
806
807#[cfg(test)]
808mod tests {
809 use ruma_common::owned_room_id;
810
811 use super::request::ExtensionRoomConfig;
812
813 #[test]
814 fn serialize_request_extension_room_config() {
815 let entry = ExtensionRoomConfig::AllSubscribed;
816 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
817
818 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
819 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
820 }
821
822 #[test]
823 fn deserialize_request_extension_room_config() {
824 assert_eq!(
825 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
826 ExtensionRoomConfig::AllSubscribed
827 );
828
829 assert_eq!(
830 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
831 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
832 );
833 }
834}