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::{AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, StateEventType};
21use serde::{Deserialize, Serialize};
22
23#[cfg(feature = "unstable-msc3575")]
24use super::v4;
25use super::UnreadNotificationsCount;
26
27const METADATA: Metadata = metadata! {
28    method: POST,
29    rate_limited: false,
30    authentication: AccessToken,
31    history: {
32        unstable => "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync",
33        }
35};
36
37#[request(error = crate::Error)]
39#[derive(Default)]
40pub struct Request {
41    #[serde(skip_serializing_if = "Option::is_none")]
47    #[ruma_api(query)]
48    pub pos: Option<String>,
49
50    #[serde(skip_serializing_if = "Option::is_none")]
61    pub conn_id: Option<String>,
62
63    #[serde(skip_serializing_if = "Option::is_none")]
66    pub txn_id: Option<String>,
67
68    #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")]
72    #[ruma_api(query)]
73    pub timeout: Option<Duration>,
74
75    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
77    pub lists: BTreeMap<String, request::List>,
78
79    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
84    pub room_subscriptions: BTreeMap<OwnedRoomId, request::RoomSubscription>,
85
86    #[serde(default, skip_serializing_if = "request::Extensions::is_empty")]
88    pub extensions: request::Extensions,
89}
90
91impl Request {
92    pub fn new() -> Self {
94        Default::default()
95    }
96}
97
98pub mod request {
100    use ruma_common::{directory::RoomTypeFilter, serde::deserialize_cow_str, RoomId};
101    use serde::de::Error as _;
102
103    use super::{BTreeMap, Deserialize, OwnedRoomId, Serialize, StateEventType, UInt};
104
105    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
107    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
108    pub struct List {
109        pub ranges: Vec<(UInt, UInt)>,
111
112        #[serde(flatten)]
114        pub room_details: RoomDetails,
115
116        #[serde(skip_serializing_if = "Option::is_none")]
119        pub include_heroes: Option<bool>,
120
121        #[serde(skip_serializing_if = "Option::is_none")]
123        pub filters: Option<ListFilters>,
124    }
125
126    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
131    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
132    pub struct ListFilters {
133        #[serde(skip_serializing_if = "Option::is_none")]
140        pub is_invite: Option<bool>,
141
142        #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
146        pub not_room_types: Vec<RoomTypeFilter>,
147    }
148
149    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
151    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
152    pub struct RoomSubscription {
153        #[serde(default, skip_serializing_if = "Vec::is_empty")]
156        pub required_state: Vec<(StateEventType, String)>,
157
158        pub timeline_limit: UInt,
160
161        #[serde(skip_serializing_if = "Option::is_none")]
163        pub include_heroes: Option<bool>,
164    }
165
166    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
168    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
169    pub struct RoomDetails {
170        #[serde(default, skip_serializing_if = "Vec::is_empty")]
172        pub required_state: Vec<(StateEventType, String)>,
173
174        pub timeline_limit: UInt,
176    }
177
178    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
180    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
181    pub struct Extensions {
182        #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
184        pub to_device: ToDevice,
185
186        #[serde(default, skip_serializing_if = "E2EE::is_empty")]
188        pub e2ee: E2EE,
189
190        #[serde(default, skip_serializing_if = "AccountData::is_empty")]
192        pub account_data: AccountData,
193
194        #[serde(default, skip_serializing_if = "Receipts::is_empty")]
196        pub receipts: Receipts,
197
198        #[serde(default, skip_serializing_if = "Typing::is_empty")]
200        pub typing: Typing,
201
202        #[serde(flatten)]
204        other: BTreeMap<String, serde_json::Value>,
205    }
206
207    impl Extensions {
208        pub fn is_empty(&self) -> bool {
210            self.to_device.is_empty()
211                && self.e2ee.is_empty()
212                && self.account_data.is_empty()
213                && self.receipts.is_empty()
214                && self.typing.is_empty()
215                && self.other.is_empty()
216        }
217    }
218
219    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
223    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
224    pub struct ToDevice {
225        #[serde(skip_serializing_if = "Option::is_none")]
227        pub enabled: Option<bool>,
228
229        #[serde(skip_serializing_if = "Option::is_none")]
231        pub limit: Option<UInt>,
232
233        #[serde(skip_serializing_if = "Option::is_none")]
235        pub since: Option<String>,
236
237        #[serde(skip_serializing_if = "Option::is_none")]
242        pub lists: Option<Vec<String>>,
243
244        #[serde(skip_serializing_if = "Option::is_none")]
250        pub rooms: Option<Vec<OwnedRoomId>>,
251    }
252
253    impl ToDevice {
254        pub fn is_empty(&self) -> bool {
256            self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
257        }
258    }
259
260    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
264    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
265    pub struct E2EE {
266        #[serde(skip_serializing_if = "Option::is_none")]
268        pub enabled: Option<bool>,
269    }
270
271    impl E2EE {
272        pub fn is_empty(&self) -> bool {
274            self.enabled.is_none()
275        }
276    }
277
278    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
283    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
284    pub struct AccountData {
285        #[serde(skip_serializing_if = "Option::is_none")]
287        pub enabled: Option<bool>,
288
289        #[serde(skip_serializing_if = "Option::is_none")]
296        pub lists: Option<Vec<String>>,
297
298        #[serde(skip_serializing_if = "Option::is_none")]
306        pub rooms: Option<Vec<OwnedRoomId>>,
307    }
308
309    impl AccountData {
310        pub fn is_empty(&self) -> bool {
312            self.enabled.is_none()
313        }
314    }
315
316    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
320    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
321    pub struct Receipts {
322        #[serde(skip_serializing_if = "Option::is_none")]
324        pub enabled: Option<bool>,
325
326        #[serde(skip_serializing_if = "Option::is_none")]
331        pub lists: Option<Vec<String>>,
332
333        #[serde(skip_serializing_if = "Option::is_none")]
339        pub rooms: Option<Vec<ReceiptsRoom>>,
340    }
341
342    impl Receipts {
343        pub fn is_empty(&self) -> bool {
345            self.enabled.is_none()
346        }
347    }
348
349    #[derive(Clone, Debug, PartialEq)]
352    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
353    pub enum ReceiptsRoom {
354        AllSubscribed,
356
357        Room(OwnedRoomId),
359    }
360
361    impl Serialize for ReceiptsRoom {
362        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
363        where
364            S: serde::Serializer,
365        {
366            match self {
367                Self::AllSubscribed => serializer.serialize_str("*"),
368                Self::Room(r) => r.serialize(serializer),
369            }
370        }
371    }
372
373    impl<'de> Deserialize<'de> for ReceiptsRoom {
374        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
375        where
376            D: serde::de::Deserializer<'de>,
377        {
378            match deserialize_cow_str(deserializer)?.as_ref() {
379                "*" => Ok(Self::AllSubscribed),
380                other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
381            }
382        }
383    }
384
385    #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
390    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
391    pub struct Typing {
392        #[serde(skip_serializing_if = "Option::is_none")]
394        pub enabled: Option<bool>,
395
396        #[serde(skip_serializing_if = "Option::is_none")]
401        pub lists: Option<Vec<String>>,
402
403        #[serde(skip_serializing_if = "Option::is_none")]
409        pub rooms: Option<Vec<OwnedRoomId>>,
410    }
411
412    impl Typing {
413        pub fn is_empty(&self) -> bool {
415            self.enabled.is_none()
416        }
417    }
418}
419
420#[response(error = crate::Error)]
422pub struct Response {
423    #[serde(skip_serializing_if = "Option::is_none")]
425    pub txn_id: Option<String>,
426
427    pub pos: String,
430
431    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
433    pub lists: BTreeMap<String, response::List>,
434
435    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
437    pub rooms: BTreeMap<OwnedRoomId, response::Room>,
438
439    #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
441    pub extensions: response::Extensions,
442}
443
444impl Response {
445    pub fn new(pos: String) -> Self {
447        Self {
448            txn_id: None,
449            pos,
450            lists: Default::default(),
451            rooms: Default::default(),
452            extensions: Default::default(),
453        }
454    }
455}
456
457pub mod response {
459    use ruma_common::OneTimeKeyAlgorithm;
460    use ruma_events::{
461        receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
462        AnyRoomAccountDataEvent, AnyToDeviceEvent,
463    };
464
465    use super::{
466        super::DeviceLists, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
467        BTreeMap, Deserialize, JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize,
468        UInt, UnreadNotificationsCount,
469    };
470
471    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
474    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
475    pub struct List {
476        pub count: UInt,
478    }
479
480    #[derive(Clone, Debug, Default, Deserialize, Serialize)]
482    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
483    pub struct Room {
484        #[serde(skip_serializing_if = "Option::is_none")]
486        pub name: Option<String>,
487
488        #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
490        pub avatar: JsOption<OwnedMxcUri>,
491
492        #[serde(skip_serializing_if = "Option::is_none")]
494        pub initial: Option<bool>,
495
496        #[serde(skip_serializing_if = "Option::is_none")]
498        pub is_dm: Option<bool>,
499
500        #[serde(skip_serializing_if = "Option::is_none")]
503        pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
504
505        #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
507        pub unread_notifications: UnreadNotificationsCount,
508
509        #[serde(default, skip_serializing_if = "Vec::is_empty")]
511        pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
512
513        #[serde(default, skip_serializing_if = "Vec::is_empty")]
515        pub required_state: Vec<Raw<AnySyncStateEvent>>,
516
517        #[serde(skip_serializing_if = "Option::is_none")]
520        pub prev_batch: Option<String>,
521
522        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
525        pub limited: bool,
526
527        #[serde(skip_serializing_if = "Option::is_none")]
530        pub joined_count: Option<UInt>,
531
532        #[serde(skip_serializing_if = "Option::is_none")]
534        pub invited_count: Option<UInt>,
535
536        #[serde(skip_serializing_if = "Option::is_none")]
539        pub num_live: Option<UInt>,
540
541        #[serde(skip_serializing_if = "Option::is_none")]
548        pub bump_stamp: Option<UInt>,
549
550        #[serde(skip_serializing_if = "Option::is_none")]
552        pub heroes: Option<Vec<Hero>>,
553    }
554
555    impl Room {
556        pub fn new() -> Self {
558            Default::default()
559        }
560    }
561
562    #[derive(Clone, Debug, Deserialize, Serialize)]
564    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
565    pub struct Hero {
566        pub user_id: OwnedUserId,
568
569        #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
571        pub name: Option<String>,
572
573        #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
575        pub avatar: Option<OwnedMxcUri>,
576    }
577
578    impl Hero {
579        pub fn new(user_id: OwnedUserId) -> Self {
581            Self { user_id, name: None, avatar: None }
582        }
583    }
584
585    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
587    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
588    pub struct Extensions {
589        #[serde(skip_serializing_if = "Option::is_none")]
591        pub to_device: Option<ToDevice>,
592
593        #[serde(default, skip_serializing_if = "E2EE::is_empty")]
595        pub e2ee: E2EE,
596
597        #[serde(default, skip_serializing_if = "AccountData::is_empty")]
599        pub account_data: AccountData,
600
601        #[serde(default, skip_serializing_if = "Receipts::is_empty")]
603        pub receipts: Receipts,
604
605        #[serde(default, skip_serializing_if = "Typing::is_empty")]
607        pub typing: Typing,
608    }
609
610    impl Extensions {
611        pub fn is_empty(&self) -> bool {
615            self.to_device.is_none()
616                && self.e2ee.is_empty()
617                && self.account_data.is_empty()
618                && self.receipts.is_empty()
619                && self.typing.is_empty()
620        }
621    }
622
623    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
627    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
628    pub struct ToDevice {
629        pub next_batch: String,
631
632        #[serde(default, skip_serializing_if = "Vec::is_empty")]
634        pub events: Vec<Raw<AnyToDeviceEvent>>,
635    }
636
637    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
641    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
642    pub struct E2EE {
643        #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
645        pub device_lists: DeviceLists,
646
647        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
650        pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
651
652        #[serde(skip_serializing_if = "Option::is_none")]
657        pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
658    }
659
660    impl E2EE {
661        pub fn is_empty(&self) -> bool {
663            self.device_lists.is_empty()
664                && self.device_one_time_keys_count.is_empty()
665                && self.device_unused_fallback_key_types.is_none()
666        }
667    }
668
669    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
674    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
675    pub struct AccountData {
676        #[serde(default, skip_serializing_if = "Vec::is_empty")]
678        pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
679
680        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
682        pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
683    }
684
685    impl AccountData {
686        pub fn is_empty(&self) -> bool {
688            self.global.is_empty() && self.rooms.is_empty()
689        }
690    }
691
692    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
696    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
697    pub struct Receipts {
698        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
700        pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
701    }
702
703    impl Receipts {
704        pub fn is_empty(&self) -> bool {
706            self.rooms.is_empty()
707        }
708    }
709
710    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
715    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
716    pub struct Typing {
717        #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
719        pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
720    }
721
722    impl Typing {
723        pub fn is_empty(&self) -> bool {
725            self.rooms.is_empty()
726        }
727    }
728}
729
730#[cfg(feature = "unstable-msc3575")]
731impl From<v4::Response> for Response {
732    fn from(value: v4::Response) -> Self {
733        Self {
734            pos: value.pos,
735            txn_id: value.txn_id,
736            lists: value.lists.into_iter().map(|(room_id, list)| (room_id, list.into())).collect(),
737            rooms: value.rooms.into_iter().map(|(room_id, room)| (room_id, room.into())).collect(),
738            extensions: value.extensions.into(),
739        }
740    }
741}
742
743#[cfg(feature = "unstable-msc3575")]
744impl From<v4::SyncList> for response::List {
745    fn from(value: v4::SyncList) -> Self {
746        Self { count: value.count }
747    }
748}
749
750#[cfg(feature = "unstable-msc3575")]
751impl From<v4::SlidingSyncRoom> for response::Room {
752    fn from(value: v4::SlidingSyncRoom) -> Self {
753        Self {
754            name: value.name,
755            avatar: value.avatar,
756            initial: value.initial,
757            is_dm: value.is_dm,
758            invite_state: value.invite_state,
759            unread_notifications: value.unread_notifications,
760            timeline: value.timeline,
761            required_state: value.required_state,
762            prev_batch: value.prev_batch,
763            limited: value.limited,
764            joined_count: value.joined_count,
765            invited_count: value.invited_count,
766            num_live: value.num_live,
767            bump_stamp: value.timestamp.map(|t| t.0),
768            heroes: value.heroes.map(|heroes| heroes.into_iter().map(Into::into).collect()),
769        }
770    }
771}
772
773#[cfg(feature = "unstable-msc3575")]
774impl From<v4::SlidingSyncRoomHero> for response::Hero {
775    fn from(value: v4::SlidingSyncRoomHero) -> Self {
776        Self { user_id: value.user_id, name: value.name, avatar: value.avatar }
777    }
778}
779
780#[cfg(feature = "unstable-msc3575")]
781impl From<v4::Extensions> for response::Extensions {
782    fn from(value: v4::Extensions) -> Self {
783        Self {
784            to_device: value.to_device.map(Into::into),
785            e2ee: value.e2ee.into(),
786            account_data: value.account_data.into(),
787            receipts: value.receipts.into(),
788            typing: value.typing.into(),
789        }
790    }
791}
792
793#[cfg(feature = "unstable-msc3575")]
794impl From<v4::ToDevice> for response::ToDevice {
795    fn from(value: v4::ToDevice) -> Self {
796        Self { next_batch: value.next_batch, events: value.events }
797    }
798}
799
800#[cfg(feature = "unstable-msc3575")]
801impl From<v4::E2EE> for response::E2EE {
802    fn from(value: v4::E2EE) -> Self {
803        Self {
804            device_lists: value.device_lists,
805            device_one_time_keys_count: value.device_one_time_keys_count,
806            device_unused_fallback_key_types: value.device_unused_fallback_key_types,
807        }
808    }
809}
810
811#[cfg(feature = "unstable-msc3575")]
812impl From<v4::AccountData> for response::AccountData {
813    fn from(value: v4::AccountData) -> Self {
814        Self { global: value.global, rooms: value.rooms }
815    }
816}
817
818#[cfg(feature = "unstable-msc3575")]
819impl From<v4::Receipts> for response::Receipts {
820    fn from(value: v4::Receipts) -> Self {
821        Self { rooms: value.rooms }
822    }
823}
824
825#[cfg(feature = "unstable-msc3575")]
826impl From<v4::Typing> for response::Typing {
827    fn from(value: v4::Typing) -> Self {
828        Self { rooms: value.rooms }
829    }
830}
831
832#[cfg(test)]
833mod tests {
834    use ruma_common::owned_room_id;
835
836    use super::request::ReceiptsRoom;
837
838    #[test]
839    fn serialize_request_receipts_room() {
840        let entry = ReceiptsRoom::AllSubscribed;
841        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
842
843        let entry = ReceiptsRoom::Room(owned_room_id!("!foo:bar.baz"));
844        assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
845    }
846
847    #[test]
848    fn deserialize_request_receipts_room() {
849        assert_eq!(
850            serde_json::from_str::<ReceiptsRoom>(r#""*""#).unwrap(),
851            ReceiptsRoom::AllSubscribed
852        );
853
854        assert_eq!(
855            serde_json::from_str::<ReceiptsRoom>(r#""!foo:bar.baz""#).unwrap(),
856            ReceiptsRoom::Room(owned_room_id!("!foo:bar.baz"))
857        );
858    }
859}