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 #[serde(flatten)]
206 other: BTreeMap<String, serde_json::Value>,
207 }
208
209 impl Extensions {
210 pub fn is_empty(&self) -> bool {
212 self.to_device.is_empty()
213 && self.e2ee.is_empty()
214 && self.account_data.is_empty()
215 && self.receipts.is_empty()
216 && self.typing.is_empty()
217 && self.other.is_empty()
218 }
219 }
220
221 #[derive(Clone, Debug, PartialEq)]
223 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
224 pub enum ExtensionRoomConfig {
225 AllSubscribed,
227
228 Room(OwnedRoomId),
230 }
231
232 impl Serialize for ExtensionRoomConfig {
233 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
234 where
235 S: serde::Serializer,
236 {
237 match self {
238 Self::AllSubscribed => serializer.serialize_str("*"),
239 Self::Room(r) => r.serialize(serializer),
240 }
241 }
242 }
243
244 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
245 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
246 where
247 D: serde::de::Deserializer<'de>,
248 {
249 match deserialize_cow_str(deserializer)?.as_ref() {
250 "*" => Ok(Self::AllSubscribed),
251 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
252 }
253 }
254 }
255
256 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
260 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
261 pub struct ToDevice {
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub enabled: Option<bool>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub limit: Option<UInt>,
269
270 #[serde(skip_serializing_if = "Option::is_none")]
272 pub since: Option<String>,
273 }
274
275 impl ToDevice {
276 pub fn is_empty(&self) -> bool {
278 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
279 }
280 }
281
282 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
286 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
287 pub struct E2EE {
288 #[serde(skip_serializing_if = "Option::is_none")]
290 pub enabled: Option<bool>,
291 }
292
293 impl E2EE {
294 pub fn is_empty(&self) -> bool {
296 self.enabled.is_none()
297 }
298 }
299
300 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
305 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
306 pub struct AccountData {
307 #[serde(skip_serializing_if = "Option::is_none")]
309 pub enabled: Option<bool>,
310
311 #[serde(skip_serializing_if = "Option::is_none")]
318 pub lists: Option<Vec<String>>,
319
320 #[serde(skip_serializing_if = "Option::is_none")]
328 pub rooms: Option<Vec<ExtensionRoomConfig>>,
329 }
330
331 impl AccountData {
332 pub fn is_empty(&self) -> bool {
334 self.enabled.is_none()
335 }
336 }
337
338 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
342 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
343 pub struct Receipts {
344 #[serde(skip_serializing_if = "Option::is_none")]
346 pub enabled: Option<bool>,
347
348 #[serde(skip_serializing_if = "Option::is_none")]
353 pub lists: Option<Vec<String>>,
354
355 #[serde(skip_serializing_if = "Option::is_none")]
361 pub rooms: Option<Vec<ExtensionRoomConfig>>,
362 }
363
364 impl Receipts {
365 pub fn is_empty(&self) -> bool {
367 self.enabled.is_none()
368 }
369 }
370
371 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
376 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
377 pub struct Typing {
378 #[serde(skip_serializing_if = "Option::is_none")]
380 pub enabled: Option<bool>,
381
382 #[serde(skip_serializing_if = "Option::is_none")]
387 pub lists: Option<Vec<String>>,
388
389 #[serde(skip_serializing_if = "Option::is_none")]
395 pub rooms: Option<Vec<ExtensionRoomConfig>>,
396 }
397
398 impl Typing {
399 pub fn is_empty(&self) -> bool {
401 self.enabled.is_none()
402 }
403 }
404}
405
406#[response(error = crate::Error)]
408pub struct Response {
409 #[serde(skip_serializing_if = "Option::is_none")]
411 pub txn_id: Option<String>,
412
413 pub pos: String,
416
417 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
419 pub lists: BTreeMap<String, response::List>,
420
421 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
423 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
424
425 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
427 pub extensions: response::Extensions,
428}
429
430impl Response {
431 pub fn new(pos: String) -> Self {
433 Self {
434 txn_id: None,
435 pos,
436 lists: Default::default(),
437 rooms: Default::default(),
438 extensions: Default::default(),
439 }
440 }
441}
442
443pub mod response {
445 use ruma_common::OneTimeKeyAlgorithm;
446 use ruma_events::{
447 receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
448 AnyRoomAccountDataEvent, AnyStrippedStateEvent, AnyToDeviceEvent,
449 };
450
451 use super::{
452 super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
453 JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
454 UnreadNotificationsCount,
455 };
456
457 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
460 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
461 pub struct List {
462 pub count: UInt,
464 }
465
466 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
468 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
469 pub struct Room {
470 #[serde(skip_serializing_if = "Option::is_none")]
472 pub name: Option<String>,
473
474 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
476 pub avatar: JsOption<OwnedMxcUri>,
477
478 #[serde(skip_serializing_if = "Option::is_none")]
480 pub initial: Option<bool>,
481
482 #[serde(skip_serializing_if = "Option::is_none")]
484 pub is_dm: Option<bool>,
485
486 #[serde(skip_serializing_if = "Option::is_none")]
489 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
490
491 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
493 pub unread_notifications: UnreadNotificationsCount,
494
495 #[serde(default, skip_serializing_if = "Vec::is_empty")]
497 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
498
499 #[serde(default, skip_serializing_if = "Vec::is_empty")]
501 pub required_state: Vec<Raw<AnySyncStateEvent>>,
502
503 #[serde(skip_serializing_if = "Option::is_none")]
506 pub prev_batch: Option<String>,
507
508 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
511 pub limited: bool,
512
513 #[serde(skip_serializing_if = "Option::is_none")]
516 pub joined_count: Option<UInt>,
517
518 #[serde(skip_serializing_if = "Option::is_none")]
520 pub invited_count: Option<UInt>,
521
522 #[serde(skip_serializing_if = "Option::is_none")]
525 pub num_live: Option<UInt>,
526
527 #[serde(skip_serializing_if = "Option::is_none")]
534 pub bump_stamp: Option<UInt>,
535
536 #[serde(skip_serializing_if = "Option::is_none")]
538 pub heroes: Option<Vec<Hero>>,
539 }
540
541 impl Room {
542 pub fn new() -> Self {
544 Default::default()
545 }
546 }
547
548 #[derive(Clone, Debug, Deserialize, Serialize)]
550 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
551 pub struct Hero {
552 pub user_id: OwnedUserId,
554
555 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
557 pub name: Option<String>,
558
559 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
561 pub avatar: Option<OwnedMxcUri>,
562 }
563
564 impl Hero {
565 pub fn new(user_id: OwnedUserId) -> Self {
567 Self { user_id, name: None, avatar: None }
568 }
569 }
570
571 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
573 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
574 pub struct Extensions {
575 #[serde(skip_serializing_if = "Option::is_none")]
577 pub to_device: Option<ToDevice>,
578
579 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
581 pub e2ee: E2EE,
582
583 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
585 pub account_data: AccountData,
586
587 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
589 pub receipts: Receipts,
590
591 #[serde(default, skip_serializing_if = "Typing::is_empty")]
593 pub typing: Typing,
594 }
595
596 impl Extensions {
597 pub fn is_empty(&self) -> bool {
601 self.to_device.is_none()
602 && self.e2ee.is_empty()
603 && self.account_data.is_empty()
604 && self.receipts.is_empty()
605 && self.typing.is_empty()
606 }
607 }
608
609 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
613 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
614 pub struct ToDevice {
615 pub next_batch: String,
617
618 #[serde(default, skip_serializing_if = "Vec::is_empty")]
620 pub events: Vec<Raw<AnyToDeviceEvent>>,
621 }
622
623 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
627 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
628 pub struct E2EE {
629 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
631 pub device_lists: DeviceLists,
632
633 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
636 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
637
638 #[serde(skip_serializing_if = "Option::is_none")]
643 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
644 }
645
646 impl E2EE {
647 pub fn is_empty(&self) -> bool {
649 self.device_lists.is_empty()
650 && self.device_one_time_keys_count.is_empty()
651 && self.device_unused_fallback_key_types.is_none()
652 }
653 }
654
655 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
660 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
661 pub struct AccountData {
662 #[serde(default, skip_serializing_if = "Vec::is_empty")]
664 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
665
666 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
668 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
669 }
670
671 impl AccountData {
672 pub fn is_empty(&self) -> bool {
674 self.global.is_empty() && self.rooms.is_empty()
675 }
676 }
677
678 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
682 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
683 pub struct Receipts {
684 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
686 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
687 }
688
689 impl Receipts {
690 pub fn is_empty(&self) -> bool {
692 self.rooms.is_empty()
693 }
694 }
695
696 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
701 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
702 pub struct Typing {
703 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
705 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
706 }
707
708 impl Typing {
709 pub fn is_empty(&self) -> bool {
711 self.rooms.is_empty()
712 }
713 }
714}
715
716#[cfg(test)]
717mod tests {
718 use ruma_common::owned_room_id;
719
720 use super::request::ExtensionRoomConfig;
721
722 #[test]
723 fn serialize_request_extension_room_config() {
724 let entry = ExtensionRoomConfig::AllSubscribed;
725 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
726
727 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
728 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
729 }
730
731 #[test]
732 fn deserialize_request_extension_room_config() {
733 assert_eq!(
734 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
735 ExtensionRoomConfig::AllSubscribed
736 );
737
738 assert_eq!(
739 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
740 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
741 );
742 }
743}