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, PartialEq)]
219 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
220 pub enum ExtensionRoomConfig {
221 AllSubscribed,
223
224 Room(OwnedRoomId),
226 }
227
228 impl Serialize for ExtensionRoomConfig {
229 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
230 where
231 S: serde::Serializer,
232 {
233 match self {
234 Self::AllSubscribed => serializer.serialize_str("*"),
235 Self::Room(r) => r.serialize(serializer),
236 }
237 }
238 }
239
240 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
241 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
242 where
243 D: serde::de::Deserializer<'de>,
244 {
245 match deserialize_cow_str(deserializer)?.as_ref() {
246 "*" => Ok(Self::AllSubscribed),
247 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?.to_owned())),
248 }
249 }
250 }
251
252 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
256 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
257 pub struct ToDevice {
258 #[serde(skip_serializing_if = "Option::is_none")]
260 pub enabled: Option<bool>,
261
262 #[serde(skip_serializing_if = "Option::is_none")]
264 pub limit: Option<UInt>,
265
266 #[serde(skip_serializing_if = "Option::is_none")]
268 pub since: Option<String>,
269 }
270
271 impl ToDevice {
272 pub fn is_empty(&self) -> bool {
274 self.enabled.is_none() && self.limit.is_none() && self.since.is_none()
275 }
276 }
277
278 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
282 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
283 pub struct E2EE {
284 #[serde(skip_serializing_if = "Option::is_none")]
286 pub enabled: Option<bool>,
287 }
288
289 impl E2EE {
290 pub fn is_empty(&self) -> bool {
292 self.enabled.is_none()
293 }
294 }
295
296 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
301 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
302 pub struct AccountData {
303 #[serde(skip_serializing_if = "Option::is_none")]
305 pub enabled: Option<bool>,
306
307 #[serde(skip_serializing_if = "Option::is_none")]
314 pub lists: Option<Vec<String>>,
315
316 #[serde(skip_serializing_if = "Option::is_none")]
324 pub rooms: Option<Vec<ExtensionRoomConfig>>,
325 }
326
327 impl AccountData {
328 pub fn is_empty(&self) -> bool {
330 self.enabled.is_none()
331 }
332 }
333
334 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
338 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
339 pub struct Receipts {
340 #[serde(skip_serializing_if = "Option::is_none")]
342 pub enabled: Option<bool>,
343
344 #[serde(skip_serializing_if = "Option::is_none")]
349 pub lists: Option<Vec<String>>,
350
351 #[serde(skip_serializing_if = "Option::is_none")]
357 pub rooms: Option<Vec<ExtensionRoomConfig>>,
358 }
359
360 impl Receipts {
361 pub fn is_empty(&self) -> bool {
363 self.enabled.is_none()
364 }
365 }
366
367 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
372 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
373 pub struct Typing {
374 #[serde(skip_serializing_if = "Option::is_none")]
376 pub enabled: Option<bool>,
377
378 #[serde(skip_serializing_if = "Option::is_none")]
383 pub lists: Option<Vec<String>>,
384
385 #[serde(skip_serializing_if = "Option::is_none")]
391 pub rooms: Option<Vec<ExtensionRoomConfig>>,
392 }
393
394 impl Typing {
395 pub fn is_empty(&self) -> bool {
397 self.enabled.is_none()
398 }
399 }
400}
401
402#[response(error = crate::Error)]
404pub struct Response {
405 #[serde(skip_serializing_if = "Option::is_none")]
407 pub txn_id: Option<String>,
408
409 pub pos: String,
412
413 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
415 pub lists: BTreeMap<String, response::List>,
416
417 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
419 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
420
421 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
423 pub extensions: response::Extensions,
424}
425
426impl Response {
427 pub fn new(pos: String) -> Self {
429 Self {
430 txn_id: None,
431 pos,
432 lists: Default::default(),
433 rooms: Default::default(),
434 extensions: Default::default(),
435 }
436 }
437}
438
439pub mod response {
441 use ruma_common::OneTimeKeyAlgorithm;
442 use ruma_events::{
443 receipt::SyncReceiptEvent, typing::SyncTypingEvent, AnyGlobalAccountDataEvent,
444 AnyRoomAccountDataEvent, AnyToDeviceEvent,
445 };
446
447 use super::{
448 super::DeviceLists, AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent,
449 BTreeMap, Deserialize, JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize,
450 UInt, UnreadNotificationsCount,
451 };
452
453 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
456 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
457 pub struct List {
458 pub count: UInt,
460 }
461
462 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
464 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
465 pub struct Room {
466 #[serde(skip_serializing_if = "Option::is_none")]
468 pub name: Option<String>,
469
470 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
472 pub avatar: JsOption<OwnedMxcUri>,
473
474 #[serde(skip_serializing_if = "Option::is_none")]
476 pub initial: Option<bool>,
477
478 #[serde(skip_serializing_if = "Option::is_none")]
480 pub is_dm: Option<bool>,
481
482 #[serde(skip_serializing_if = "Option::is_none")]
485 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
486
487 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
489 pub unread_notifications: UnreadNotificationsCount,
490
491 #[serde(default, skip_serializing_if = "Vec::is_empty")]
493 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
494
495 #[serde(default, skip_serializing_if = "Vec::is_empty")]
497 pub required_state: Vec<Raw<AnySyncStateEvent>>,
498
499 #[serde(skip_serializing_if = "Option::is_none")]
502 pub prev_batch: Option<String>,
503
504 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
507 pub limited: bool,
508
509 #[serde(skip_serializing_if = "Option::is_none")]
512 pub joined_count: Option<UInt>,
513
514 #[serde(skip_serializing_if = "Option::is_none")]
516 pub invited_count: Option<UInt>,
517
518 #[serde(skip_serializing_if = "Option::is_none")]
521 pub num_live: Option<UInt>,
522
523 #[serde(skip_serializing_if = "Option::is_none")]
530 pub bump_stamp: Option<UInt>,
531
532 #[serde(skip_serializing_if = "Option::is_none")]
534 pub heroes: Option<Vec<Hero>>,
535 }
536
537 impl Room {
538 pub fn new() -> Self {
540 Default::default()
541 }
542 }
543
544 #[derive(Clone, Debug, Deserialize, Serialize)]
546 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
547 pub struct Hero {
548 pub user_id: OwnedUserId,
550
551 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
553 pub name: Option<String>,
554
555 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
557 pub avatar: Option<OwnedMxcUri>,
558 }
559
560 impl Hero {
561 pub fn new(user_id: OwnedUserId) -> Self {
563 Self { user_id, name: None, avatar: None }
564 }
565 }
566
567 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
569 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
570 pub struct Extensions {
571 #[serde(skip_serializing_if = "Option::is_none")]
573 pub to_device: Option<ToDevice>,
574
575 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
577 pub e2ee: E2EE,
578
579 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
581 pub account_data: AccountData,
582
583 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
585 pub receipts: Receipts,
586
587 #[serde(default, skip_serializing_if = "Typing::is_empty")]
589 pub typing: Typing,
590 }
591
592 impl Extensions {
593 pub fn is_empty(&self) -> bool {
597 self.to_device.is_none()
598 && self.e2ee.is_empty()
599 && self.account_data.is_empty()
600 && self.receipts.is_empty()
601 && self.typing.is_empty()
602 }
603 }
604
605 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
609 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
610 pub struct ToDevice {
611 pub next_batch: String,
613
614 #[serde(default, skip_serializing_if = "Vec::is_empty")]
616 pub events: Vec<Raw<AnyToDeviceEvent>>,
617 }
618
619 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
623 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
624 pub struct E2EE {
625 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
627 pub device_lists: DeviceLists,
628
629 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
632 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
633
634 #[serde(skip_serializing_if = "Option::is_none")]
639 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
640 }
641
642 impl E2EE {
643 pub fn is_empty(&self) -> bool {
645 self.device_lists.is_empty()
646 && self.device_one_time_keys_count.is_empty()
647 && self.device_unused_fallback_key_types.is_none()
648 }
649 }
650
651 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
656 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
657 pub struct AccountData {
658 #[serde(default, skip_serializing_if = "Vec::is_empty")]
660 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
661
662 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
664 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
665 }
666
667 impl AccountData {
668 pub fn is_empty(&self) -> bool {
670 self.global.is_empty() && self.rooms.is_empty()
671 }
672 }
673
674 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
678 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
679 pub struct Receipts {
680 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
682 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
683 }
684
685 impl Receipts {
686 pub fn is_empty(&self) -> bool {
688 self.rooms.is_empty()
689 }
690 }
691
692 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
697 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
698 pub struct Typing {
699 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
701 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
702 }
703
704 impl Typing {
705 pub fn is_empty(&self) -> bool {
707 self.rooms.is_empty()
708 }
709 }
710}
711
712#[cfg(test)]
713mod tests {
714 use ruma_common::owned_room_id;
715
716 use super::request::ExtensionRoomConfig;
717
718 #[test]
719 fn serialize_request_extension_room_config() {
720 let entry = ExtensionRoomConfig::AllSubscribed;
721 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
722
723 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
724 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
725 }
726
727 #[test]
728 fn deserialize_request_extension_room_config() {
729 assert_eq!(
730 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
731 ExtensionRoomConfig::AllSubscribed
732 );
733
734 assert_eq!(
735 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
736 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
737 );
738 }
739}