1use std::{collections::BTreeMap, time::Duration};
11
12use js_int::UInt;
13use js_option::JsOption;
14use ruma_common::{
15 api::{request, response, Metadata},
16 metadata,
17 serde::{duration::opt_ms, Raw},
18 OwnedMxcUri, OwnedRoomId, OwnedUserId,
19};
20use ruma_events::{AnySyncStateEvent, AnySyncTimelineEvent, StateEventType};
21use serde::{Deserialize, Serialize};
22
23use super::UnreadNotificationsCount;
24
25const METADATA: Metadata = metadata! {
26 method: POST,
27 rate_limited: false,
28 authentication: AccessToken,
29 history: {
30 unstable("org.matrix.simplified_msc3575") => "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync",
31 }
33};
34
35#[request(error = crate::Error)]
37#[derive(Default)]
38pub struct Request {
39 #[serde(skip_serializing_if = "Option::is_none")]
45 #[ruma_api(query)]
46 pub pos: Option<String>,
47
48 #[serde(skip_serializing_if = "Option::is_none")]
59 pub conn_id: Option<String>,
60
61 #[serde(skip_serializing_if = "Option::is_none")]
64 pub txn_id: Option<String>,
65
66 #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")]
70 #[ruma_api(query)]
71 pub timeout: Option<Duration>,
72
73 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
75 pub lists: BTreeMap<String, request::List>,
76
77 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
82 pub room_subscriptions: BTreeMap<OwnedRoomId, request::RoomSubscription>,
83
84 #[serde(default, skip_serializing_if = "request::Extensions::is_empty")]
86 pub extensions: request::Extensions,
87}
88
89impl Request {
90 pub fn new() -> Self {
92 Default::default()
93 }
94}
95
96pub mod request {
98 use ruma_common::{directory::RoomTypeFilter, serde::deserialize_cow_str, RoomId};
99 use serde::de::Error as _;
100
101 use super::{BTreeMap, Deserialize, OwnedRoomId, Serialize, StateEventType, UInt};
102
103 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
105 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
106 pub struct List {
107 pub ranges: Vec<(UInt, UInt)>,
109
110 #[serde(flatten)]
112 pub room_details: RoomDetails,
113
114 #[serde(skip_serializing_if = "Option::is_none")]
116 pub filters: Option<ListFilters>,
117 }
118
119 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
124 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
125 pub struct ListFilters {
126 #[serde(skip_serializing_if = "Option::is_none")]
129 pub is_dm: Option<bool>,
130
131 #[serde(skip_serializing_if = "Option::is_none")]
134 pub is_encrypted: Option<bool>,
135
136 #[serde(skip_serializing_if = "Option::is_none")]
138 pub is_invite: Option<bool>,
139
140 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
145 pub room_types: Vec<RoomTypeFilter>,
146
147 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
152 pub not_room_types: Vec<RoomTypeFilter>,
153 }
154
155 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
157 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
158 pub struct RoomSubscription {
159 #[serde(default, skip_serializing_if = "Vec::is_empty")]
162 pub required_state: Vec<(StateEventType, String)>,
163
164 pub timeline_limit: UInt,
166 }
167
168 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
170 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
171 pub struct RoomDetails {
172 #[serde(default, skip_serializing_if = "Vec::is_empty")]
174 pub required_state: Vec<(StateEventType, String)>,
175
176 pub timeline_limit: UInt,
178 }
179
180 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
182 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
183 pub struct Extensions {
184 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
186 pub to_device: ToDevice,
187
188 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
190 pub e2ee: E2EE,
191
192 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
194 pub account_data: AccountData,
195
196 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
198 pub receipts: Receipts,
199
200 #[serde(default, skip_serializing_if = "Typing::is_empty")]
202 pub typing: Typing,
203
204 #[cfg(feature = "unstable-msc4308")]
206 #[serde(
207 default,
208 skip_serializing_if = "ThreadSubscriptions::is_empty",
209 rename = "io.element.msc4308.thread_subscriptions"
210 )]
211 pub thread_subscriptions: ThreadSubscriptions,
212
213 #[serde(flatten)]
215 other: BTreeMap<String, serde_json::Value>,
216 }
217
218 impl Extensions {
219 pub fn is_empty(&self) -> bool {
221 self.to_device.is_empty()
222 && self.e2ee.is_empty()
223 && self.account_data.is_empty()
224 && self.receipts.is_empty()
225 && self.typing.is_empty()
226 && self.other.is_empty()
227 }
228 }
229
230 #[derive(Clone, Debug, PartialEq)]
232 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
233 pub enum ExtensionRoomConfig {
234 AllSubscribed,
236
237 Room(OwnedRoomId),
239 }
240
241 impl Serialize for ExtensionRoomConfig {
242 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
243 where
244 S: serde::Serializer,
245 {
246 match self {
247 Self::AllSubscribed => serializer.serialize_str("*"),
248 Self::Room(r) => r.serialize(serializer),
249 }
250 }
251 }
252
253 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
254 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
255 where
256 D: serde::de::Deserializer<'de>,
257 {
258 match deserialize_cow_str(deserializer)?.as_ref() {
259 "*" => Ok(Self::AllSubscribed),
260 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
261 }
262 }
263 }
264
265 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
269 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
270 pub struct ToDevice {
271 #[serde(skip_serializing_if = "Option::is_none")]
273 pub enabled: Option<bool>,
274
275 #[serde(skip_serializing_if = "Option::is_none")]
277 pub limit: Option<UInt>,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub since: Option<String>,
282 }
283
284 impl ToDevice {
285 pub fn is_empty(&self) -> bool {
287 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
288 }
289 }
290
291 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
295 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
296 pub struct E2EE {
297 #[serde(skip_serializing_if = "Option::is_none")]
299 pub enabled: Option<bool>,
300 }
301
302 impl E2EE {
303 pub fn is_empty(&self) -> bool {
305 self.enabled.is_none()
306 }
307 }
308
309 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
314 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
315 pub struct AccountData {
316 #[serde(skip_serializing_if = "Option::is_none")]
318 pub enabled: Option<bool>,
319
320 #[serde(skip_serializing_if = "Option::is_none")]
327 pub lists: Option<Vec<String>>,
328
329 #[serde(skip_serializing_if = "Option::is_none")]
337 pub rooms: Option<Vec<ExtensionRoomConfig>>,
338 }
339
340 impl AccountData {
341 pub fn is_empty(&self) -> bool {
343 self.enabled.is_none()
344 }
345 }
346
347 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
351 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
352 pub struct Receipts {
353 #[serde(skip_serializing_if = "Option::is_none")]
355 pub enabled: Option<bool>,
356
357 #[serde(skip_serializing_if = "Option::is_none")]
362 pub lists: Option<Vec<String>>,
363
364 #[serde(skip_serializing_if = "Option::is_none")]
370 pub rooms: Option<Vec<ExtensionRoomConfig>>,
371 }
372
373 impl Receipts {
374 pub fn is_empty(&self) -> bool {
376 self.enabled.is_none()
377 }
378 }
379
380 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
385 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
386 pub struct Typing {
387 #[serde(skip_serializing_if = "Option::is_none")]
389 pub enabled: Option<bool>,
390
391 #[serde(skip_serializing_if = "Option::is_none")]
396 pub lists: Option<Vec<String>>,
397
398 #[serde(skip_serializing_if = "Option::is_none")]
404 pub rooms: Option<Vec<ExtensionRoomConfig>>,
405 }
406
407 impl Typing {
408 pub fn is_empty(&self) -> bool {
410 self.enabled.is_none()
411 }
412 }
413
414 #[cfg(feature = "unstable-msc4308")]
418 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
419 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
420 pub struct ThreadSubscriptions {
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub enabled: Option<bool>,
424
425 #[serde(skip_serializing_if = "Option::is_none")]
430 pub limit: Option<UInt>,
431 }
432
433 #[cfg(feature = "unstable-msc4308")]
434 impl ThreadSubscriptions {
435 pub fn is_empty(&self) -> bool {
437 self.enabled.is_none() && self.limit.is_none()
438 }
439 }
440}
441
442#[response(error = crate::Error)]
444pub struct Response {
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub txn_id: Option<String>,
448
449 pub pos: String,
452
453 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
455 pub lists: BTreeMap<String, response::List>,
456
457 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
459 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
460
461 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
463 pub extensions: response::Extensions,
464}
465
466impl Response {
467 pub fn new(pos: String) -> Self {
469 Self {
470 txn_id: None,
471 pos,
472 lists: Default::default(),
473 rooms: Default::default(),
474 extensions: Default::default(),
475 }
476 }
477}
478
479pub mod response {
481 use ruma_common::OneTimeKeyAlgorithm;
482 #[cfg(feature = "unstable-msc4308")]
483 use ruma_common::OwnedEventId;
484 use ruma_events::{
485 receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
486 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnyToDeviceEvent,
487 };
488
489 use super::{
490 super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
491 JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
492 UnreadNotificationsCount,
493 };
494 #[cfg(feature = "unstable-msc4308")]
495 use crate::threads::get_thread_subscriptions_changes::unstable::{
496 ThreadSubscription, ThreadUnsubscription,
497 };
498
499 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
502 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
503 pub struct List {
504 pub count: UInt,
506 }
507
508 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
510 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
511 pub struct Room {
512 #[serde(skip_serializing_if = "Option::is_none")]
514 pub name: Option<String>,
515
516 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
518 pub avatar: JsOption<OwnedMxcUri>,
519
520 #[serde(skip_serializing_if = "Option::is_none")]
522 pub initial: Option<bool>,
523
524 #[serde(skip_serializing_if = "Option::is_none")]
526 pub is_dm: Option<bool>,
527
528 #[serde(skip_serializing_if = "Option::is_none")]
531 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
532
533 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
535 pub unread_notifications: UnreadNotificationsCount,
536
537 #[serde(default, skip_serializing_if = "Vec::is_empty")]
539 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
540
541 #[serde(default, skip_serializing_if = "Vec::is_empty")]
543 pub required_state: Vec<Raw<AnySyncStateEvent>>,
544
545 #[serde(skip_serializing_if = "Option::is_none")]
548 pub prev_batch: Option<String>,
549
550 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
553 pub limited: bool,
554
555 #[serde(skip_serializing_if = "Option::is_none")]
558 pub joined_count: Option<UInt>,
559
560 #[serde(skip_serializing_if = "Option::is_none")]
562 pub invited_count: Option<UInt>,
563
564 #[serde(skip_serializing_if = "Option::is_none")]
567 pub num_live: Option<UInt>,
568
569 #[serde(skip_serializing_if = "Option::is_none")]
576 pub bump_stamp: Option<UInt>,
577
578 #[serde(skip_serializing_if = "Option::is_none")]
580 pub heroes: Option<Vec<Hero>>,
581 }
582
583 impl Room {
584 pub fn new() -> Self {
586 Default::default()
587 }
588 }
589
590 #[derive(Clone, Debug, Deserialize, Serialize)]
592 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
593 pub struct Hero {
594 pub user_id: OwnedUserId,
596
597 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
599 pub name: Option<String>,
600
601 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
603 pub avatar: Option<OwnedMxcUri>,
604 }
605
606 impl Hero {
607 pub fn new(user_id: OwnedUserId) -> Self {
609 Self { user_id, name: None, avatar: None }
610 }
611 }
612
613 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
615 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
616 pub struct Extensions {
617 #[serde(skip_serializing_if = "Option::is_none")]
619 pub to_device: Option<ToDevice>,
620
621 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
623 pub e2ee: E2EE,
624
625 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
627 pub account_data: AccountData,
628
629 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
631 pub receipts: Receipts,
632
633 #[serde(default, skip_serializing_if = "Typing::is_empty")]
635 pub typing: Typing,
636
637 #[cfg(feature = "unstable-msc4308")]
639 #[serde(
640 default,
641 skip_serializing_if = "ThreadSubscriptions::is_empty",
642 rename = "io.element.msc4308.thread_subscriptions"
643 )]
644 pub thread_subscriptions: ThreadSubscriptions,
645 }
646
647 impl Extensions {
648 pub fn is_empty(&self) -> bool {
652 self.to_device.is_none()
653 && self.e2ee.is_empty()
654 && self.account_data.is_empty()
655 && self.receipts.is_empty()
656 && self.typing.is_empty()
657 }
658 }
659
660 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
664 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
665 pub struct ToDevice {
666 pub next_batch: String,
668
669 #[serde(default, skip_serializing_if = "Vec::is_empty")]
671 pub events: Vec<Raw<AnyToDeviceEvent>>,
672 }
673
674 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
678 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
679 pub struct E2EE {
680 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
682 pub device_lists: DeviceLists,
683
684 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
687 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
688
689 #[serde(skip_serializing_if = "Option::is_none")]
694 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
695 }
696
697 impl E2EE {
698 pub fn is_empty(&self) -> bool {
700 self.device_lists.is_empty()
701 && self.device_one_time_keys_count.is_empty()
702 && self.device_unused_fallback_key_types.is_none()
703 }
704 }
705
706 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
711 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
712 pub struct AccountData {
713 #[serde(default, skip_serializing_if = "Vec::is_empty")]
715 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
716
717 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
719 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
720 }
721
722 impl AccountData {
723 pub fn is_empty(&self) -> bool {
725 self.global.is_empty() && self.rooms.is_empty()
726 }
727 }
728
729 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
733 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
734 pub struct Receipts {
735 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
737 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
738 }
739
740 impl Receipts {
741 pub fn is_empty(&self) -> bool {
743 self.rooms.is_empty()
744 }
745 }
746
747 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
752 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
753 pub struct Typing {
754 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
756 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
757 }
758
759 impl Typing {
760 pub fn is_empty(&self) -> bool {
762 self.rooms.is_empty()
763 }
764 }
765
766 #[cfg(feature = "unstable-msc4308")]
770 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
771 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
772 pub struct ThreadSubscriptions {
773 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
775 pub subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
776
777 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
779 pub unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
780
781 #[serde(skip_serializing_if = "Option::is_none")]
787 pub prev_batch: Option<String>,
788 }
789
790 #[cfg(feature = "unstable-msc4308")]
791 impl ThreadSubscriptions {
792 pub fn is_empty(&self) -> bool {
794 self.subscribed.is_empty() && self.unsubscribed.is_empty() && self.prev_batch.is_none()
795 }
796 }
797}
798
799#[cfg(test)]
800mod tests {
801 use ruma_common::owned_room_id;
802
803 use super::request::ExtensionRoomConfig;
804
805 #[test]
806 fn serialize_request_extension_room_config() {
807 let entry = ExtensionRoomConfig::AllSubscribed;
808 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
809
810 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
811 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
812 }
813
814 #[test]
815 fn deserialize_request_extension_room_config() {
816 assert_eq!(
817 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
818 ExtensionRoomConfig::AllSubscribed
819 );
820
821 assert_eq!(
822 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
823 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
824 );
825 }
826}