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
23use super::UnreadNotificationsCount;
24
25const METADATA: Metadata = metadata! {
26 method: POST,
27 rate_limited: false,
28 authentication: AccessToken,
29 history: {
30 unstable => "/_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")]
117 pub include_heroes: Option<bool>,
118
119 #[serde(skip_serializing_if = "Option::is_none")]
121 pub filters: Option<ListFilters>,
122 }
123
124 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
129 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
130 pub struct ListFilters {
131 #[serde(skip_serializing_if = "Option::is_none")]
138 pub is_invite: Option<bool>,
139
140 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
144 pub not_room_types: Vec<RoomTypeFilter>,
145 }
146
147 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
149 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
150 pub struct RoomSubscription {
151 #[serde(default, skip_serializing_if = "Vec::is_empty")]
154 pub required_state: Vec<(StateEventType, String)>,
155
156 pub timeline_limit: UInt,
158
159 #[serde(skip_serializing_if = "Option::is_none")]
161 pub include_heroes: Option<bool>,
162 }
163
164 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
166 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
167 pub struct RoomDetails {
168 #[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, PartialEq)]
178 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
179 pub struct Extensions {
180 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
182 pub to_device: ToDevice,
183
184 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
186 pub e2ee: E2EE,
187
188 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
190 pub account_data: AccountData,
191
192 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
194 pub receipts: Receipts,
195
196 #[serde(default, skip_serializing_if = "Typing::is_empty")]
198 pub typing: Typing,
199
200 #[serde(flatten)]
202 other: BTreeMap<String, serde_json::Value>,
203 }
204
205 impl Extensions {
206 pub fn is_empty(&self) -> bool {
208 self.to_device.is_empty()
209 && self.e2ee.is_empty()
210 && self.account_data.is_empty()
211 && self.receipts.is_empty()
212 && self.typing.is_empty()
213 && self.other.is_empty()
214 }
215 }
216
217 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
221 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
222 pub struct ToDevice {
223 #[serde(skip_serializing_if = "Option::is_none")]
225 pub enabled: Option<bool>,
226
227 #[serde(skip_serializing_if = "Option::is_none")]
229 pub limit: Option<UInt>,
230
231 #[serde(skip_serializing_if = "Option::is_none")]
233 pub since: Option<String>,
234
235 #[serde(skip_serializing_if = "Option::is_none")]
240 pub lists: Option<Vec<String>>,
241
242 #[serde(skip_serializing_if = "Option::is_none")]
248 pub rooms: Option<Vec<OwnedRoomId>>,
249 }
250
251 impl ToDevice {
252 pub fn is_empty(&self) -> bool {
254 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
255 }
256 }
257
258 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
262 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
263 pub struct E2EE {
264 #[serde(skip_serializing_if = "Option::is_none")]
266 pub enabled: Option<bool>,
267 }
268
269 impl E2EE {
270 pub fn is_empty(&self) -> bool {
272 self.enabled.is_none()
273 }
274 }
275
276 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
281 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
282 pub struct AccountData {
283 #[serde(skip_serializing_if = "Option::is_none")]
285 pub enabled: Option<bool>,
286
287 #[serde(skip_serializing_if = "Option::is_none")]
294 pub lists: Option<Vec<String>>,
295
296 #[serde(skip_serializing_if = "Option::is_none")]
304 pub rooms: Option<Vec<OwnedRoomId>>,
305 }
306
307 impl AccountData {
308 pub fn is_empty(&self) -> bool {
310 self.enabled.is_none()
311 }
312 }
313
314 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
318 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
319 pub struct Receipts {
320 #[serde(skip_serializing_if = "Option::is_none")]
322 pub enabled: Option<bool>,
323
324 #[serde(skip_serializing_if = "Option::is_none")]
329 pub lists: Option<Vec<String>>,
330
331 #[serde(skip_serializing_if = "Option::is_none")]
337 pub rooms: Option<Vec<ReceiptsRoom>>,
338 }
339
340 impl Receipts {
341 pub fn is_empty(&self) -> bool {
343 self.enabled.is_none()
344 }
345 }
346
347 #[derive(Clone, Debug, PartialEq)]
350 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
351 pub enum ReceiptsRoom {
352 AllSubscribed,
354
355 Room(OwnedRoomId),
357 }
358
359 impl Serialize for ReceiptsRoom {
360 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
361 where
362 S: serde::Serializer,
363 {
364 match self {
365 Self::AllSubscribed => serializer.serialize_str("*"),
366 Self::Room(r) => r.serialize(serializer),
367 }
368 }
369 }
370
371 impl<'de> Deserialize<'de> for ReceiptsRoom {
372 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
373 where
374 D: serde::de::Deserializer<'de>,
375 {
376 match deserialize_cow_str(deserializer)?.as_ref() {
377 "*" => Ok(Self::AllSubscribed),
378 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
379 }
380 }
381 }
382
383 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
388 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
389 pub struct Typing {
390 #[serde(skip_serializing_if = "Option::is_none")]
392 pub enabled: Option<bool>,
393
394 #[serde(skip_serializing_if = "Option::is_none")]
399 pub lists: Option<Vec<String>>,
400
401 #[serde(skip_serializing_if = "Option::is_none")]
407 pub rooms: Option<Vec<OwnedRoomId>>,
408 }
409
410 impl Typing {
411 pub fn is_empty(&self) -> bool {
413 self.enabled.is_none()
414 }
415 }
416}
417
418#[response(error = crate::Error)]
420pub struct Response {
421 #[serde(skip_serializing_if = "Option::is_none")]
423 pub txn_id: Option<String>,
424
425 pub pos: String,
428
429 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
431 pub lists: BTreeMap<String, response::List>,
432
433 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
435 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
436
437 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
439 pub extensions: response::Extensions,
440}
441
442impl Response {
443 pub fn new(pos: String) -> Self {
445 Self {
446 txn_id: None,
447 pos,
448 lists: Default::default(),
449 rooms: Default::default(),
450 extensions: Default::default(),
451 }
452 }
453}
454
455pub mod response {
457 use ruma_common::OneTimeKeyAlgorithm;
458 use ruma_events::{
459 receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
460 AnyRoomAccountDataEvent, AnyToDeviceEvent,
461 };
462
463 use super::{
464 super::DeviceLists, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
465 BTreeMap, Deserialize, JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize,
466 UInt, UnreadNotificationsCount,
467 };
468
469 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
472 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
473 pub struct List {
474 pub count: UInt,
476 }
477
478 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
480 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
481 pub struct Room {
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub name: Option<String>,
485
486 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
488 pub avatar: JsOption<OwnedMxcUri>,
489
490 #[serde(skip_serializing_if = "Option::is_none")]
492 pub initial: Option<bool>,
493
494 #[serde(skip_serializing_if = "Option::is_none")]
496 pub is_dm: Option<bool>,
497
498 #[serde(skip_serializing_if = "Option::is_none")]
501 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
502
503 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
505 pub unread_notifications: UnreadNotificationsCount,
506
507 #[serde(default, skip_serializing_if = "Vec::is_empty")]
509 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
510
511 #[serde(default, skip_serializing_if = "Vec::is_empty")]
513 pub required_state: Vec<Raw<AnySyncStateEvent>>,
514
515 #[serde(skip_serializing_if = "Option::is_none")]
518 pub prev_batch: Option<String>,
519
520 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
523 pub limited: bool,
524
525 #[serde(skip_serializing_if = "Option::is_none")]
528 pub joined_count: Option<UInt>,
529
530 #[serde(skip_serializing_if = "Option::is_none")]
532 pub invited_count: Option<UInt>,
533
534 #[serde(skip_serializing_if = "Option::is_none")]
537 pub num_live: Option<UInt>,
538
539 #[serde(skip_serializing_if = "Option::is_none")]
546 pub bump_stamp: Option<UInt>,
547
548 #[serde(skip_serializing_if = "Option::is_none")]
550 pub heroes: Option<Vec<Hero>>,
551 }
552
553 impl Room {
554 pub fn new() -> Self {
556 Default::default()
557 }
558 }
559
560 #[derive(Clone, Debug, Deserialize, Serialize)]
562 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
563 pub struct Hero {
564 pub user_id: OwnedUserId,
566
567 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
569 pub name: Option<String>,
570
571 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
573 pub avatar: Option<OwnedMxcUri>,
574 }
575
576 impl Hero {
577 pub fn new(user_id: OwnedUserId) -> Self {
579 Self { user_id, name: None, avatar: None }
580 }
581 }
582
583 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
585 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
586 pub struct Extensions {
587 #[serde(skip_serializing_if = "Option::is_none")]
589 pub to_device: Option<ToDevice>,
590
591 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
593 pub e2ee: E2EE,
594
595 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
597 pub account_data: AccountData,
598
599 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
601 pub receipts: Receipts,
602
603 #[serde(default, skip_serializing_if = "Typing::is_empty")]
605 pub typing: Typing,
606 }
607
608 impl Extensions {
609 pub fn is_empty(&self) -> bool {
613 self.to_device.is_none()
614 && self.e2ee.is_empty()
615 && self.account_data.is_empty()
616 && self.receipts.is_empty()
617 && self.typing.is_empty()
618 }
619 }
620
621 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
625 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
626 pub struct ToDevice {
627 pub next_batch: String,
629
630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub events: Vec<Raw<AnyToDeviceEvent>>,
633 }
634
635 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
639 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
640 pub struct E2EE {
641 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
643 pub device_lists: DeviceLists,
644
645 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
648 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
649
650 #[serde(skip_serializing_if = "Option::is_none")]
655 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
656 }
657
658 impl E2EE {
659 pub fn is_empty(&self) -> bool {
661 self.device_lists.is_empty()
662 && self.device_one_time_keys_count.is_empty()
663 && self.device_unused_fallback_key_types.is_none()
664 }
665 }
666
667 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
672 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
673 pub struct AccountData {
674 #[serde(default, skip_serializing_if = "Vec::is_empty")]
676 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
677
678 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
680 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
681 }
682
683 impl AccountData {
684 pub fn is_empty(&self) -> bool {
686 self.global.is_empty() && self.rooms.is_empty()
687 }
688 }
689
690 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
694 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
695 pub struct Receipts {
696 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
698 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
699 }
700
701 impl Receipts {
702 pub fn is_empty(&self) -> bool {
704 self.rooms.is_empty()
705 }
706 }
707
708 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
713 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
714 pub struct Typing {
715 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
717 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
718 }
719
720 impl Typing {
721 pub fn is_empty(&self) -> bool {
723 self.rooms.is_empty()
724 }
725 }
726}
727
728#[cfg(test)]
729mod tests {
730 use ruma_common::owned_room_id;
731
732 use super::request::ReceiptsRoom;
733
734 #[test]
735 fn serialize_request_receipts_room() {
736 let entry = ReceiptsRoom::AllSubscribed;
737 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
738
739 let entry = ReceiptsRoom::Room(owned_room_id!("!foo:bar.baz"));
740 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
741 }
742
743 #[test]
744 fn deserialize_request_receipts_room() {
745 assert_eq!(
746 serde_json::from_str::<ReceiptsRoom>(r#""*""#).unwrap(),
747 ReceiptsRoom::AllSubscribed
748 );
749
750 assert_eq!(
751 serde_json::from_str::<ReceiptsRoom>(r#""!foo:bar.baz""#).unwrap(),
752 ReceiptsRoom::Room(owned_room_id!("!foo:bar.baz"))
753 );
754 }
755}