1use std::{collections::BTreeMap, time::Duration};
6
7use js_int::UInt;
8use ruma_common::{
9 api::{request, response, Metadata},
10 metadata,
11 presence::PresenceState,
12 serde::Raw,
13 OneTimeKeyAlgorithm, OwnedEventId, OwnedRoomId, OwnedUserId,
14};
15use ruma_events::{
16 presence::PresenceEvent, AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent,
17 AnyStrippedStateEvent, AnySyncEphemeralRoomEvent, AnySyncStateEvent, AnySyncTimelineEvent,
18 AnyToDeviceEvent,
19};
20use serde::{Deserialize, Serialize};
21
22use super::{DeviceLists, UnreadNotificationsCount};
23use crate::filter::FilterDefinition;
24
25const METADATA: Metadata = metadata! {
26 method: GET,
27 rate_limited: false,
28 authentication: AccessToken,
29 history: {
30 1.0 => "/_matrix/client/r0/sync",
31 1.1 => "/_matrix/client/v3/sync",
32 }
33};
34
35#[request(error = crate::Error)]
37#[derive(Default)]
38pub struct Request {
39 #[serde(skip_serializing_if = "Option::is_none")]
41 #[ruma_api(query)]
42 pub filter: Option<Filter>,
43
44 #[serde(skip_serializing_if = "Option::is_none")]
49 #[ruma_api(query)]
50 pub since: Option<String>,
51
52 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
54 #[ruma_api(query)]
55 pub full_state: bool,
56
57 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
61 #[ruma_api(query)]
62 pub set_presence: PresenceState,
63
64 #[serde(
66 with = "ruma_common::serde::duration::opt_ms",
67 default,
68 skip_serializing_if = "Option::is_none"
69 )]
70 #[ruma_api(query)]
71 pub timeout: Option<Duration>,
72}
73
74#[response(error = crate::Error)]
76pub struct Response {
77 pub next_batch: String,
79
80 #[serde(default, skip_serializing_if = "Rooms::is_empty")]
82 pub rooms: Rooms,
83
84 #[serde(default, skip_serializing_if = "Presence::is_empty")]
86 pub presence: Presence,
87
88 #[serde(default, skip_serializing_if = "GlobalAccountData::is_empty")]
90 pub account_data: GlobalAccountData,
91
92 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
94 pub to_device: ToDevice,
95
96 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
100 pub device_lists: DeviceLists,
101
102 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
105 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
106
107 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
112}
113
114impl Request {
115 pub fn new() -> Self {
117 Default::default()
118 }
119}
120
121impl Response {
122 pub fn new(next_batch: String) -> Self {
124 Self {
125 next_batch,
126 rooms: Default::default(),
127 presence: Default::default(),
128 account_data: Default::default(),
129 to_device: Default::default(),
130 device_lists: Default::default(),
131 device_one_time_keys_count: BTreeMap::new(),
132 device_unused_fallback_key_types: None,
133 }
134 }
135}
136
137#[derive(Clone, Debug, Deserialize, Serialize)]
139#[allow(clippy::large_enum_variant)]
140#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
141#[serde(untagged)]
142pub enum Filter {
143 #[serde(with = "ruma_common::serde::json_string")]
155 FilterDefinition(FilterDefinition),
156
157 FilterId(String),
159}
160
161impl From<FilterDefinition> for Filter {
162 fn from(def: FilterDefinition) -> Self {
163 Self::FilterDefinition(def)
164 }
165}
166
167impl From<String> for Filter {
168 fn from(id: String) -> Self {
169 Self::FilterId(id)
170 }
171}
172
173#[derive(Clone, Debug, Default, Deserialize, Serialize)]
175#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
176pub struct Rooms {
177 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
179 pub leave: BTreeMap<OwnedRoomId, LeftRoom>,
180
181 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
183 pub join: BTreeMap<OwnedRoomId, JoinedRoom>,
184
185 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
187 pub invite: BTreeMap<OwnedRoomId, InvitedRoom>,
188
189 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
191 pub knock: BTreeMap<OwnedRoomId, KnockedRoom>,
192}
193
194impl Rooms {
195 pub fn new() -> Self {
197 Default::default()
198 }
199
200 pub fn is_empty(&self) -> bool {
202 self.leave.is_empty() && self.join.is_empty() && self.invite.is_empty()
203 }
204}
205
206#[derive(Clone, Debug, Default, Deserialize, Serialize)]
208#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
209pub struct LeftRoom {
210 #[serde(default, skip_serializing_if = "Timeline::is_empty")]
213 pub timeline: Timeline,
214
215 #[serde(default, skip_serializing_if = "State::is_empty")]
217 pub state: State,
218
219 #[serde(default, skip_serializing_if = "RoomAccountData::is_empty")]
221 pub account_data: RoomAccountData,
222}
223
224impl LeftRoom {
225 pub fn new() -> Self {
227 Default::default()
228 }
229
230 pub fn is_empty(&self) -> bool {
232 self.timeline.is_empty() && self.state.is_empty() && self.account_data.is_empty()
233 }
234}
235
236#[derive(Clone, Debug, Default, Deserialize, Serialize)]
238#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
239pub struct JoinedRoom {
240 #[serde(default, skip_serializing_if = "RoomSummary::is_empty")]
243 pub summary: RoomSummary,
244
245 #[serde(default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
253 pub unread_notifications: UnreadNotificationsCount,
254
255 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
264 pub unread_thread_notifications: BTreeMap<OwnedEventId, UnreadNotificationsCount>,
265
266 #[serde(default, skip_serializing_if = "Timeline::is_empty")]
268 pub timeline: Timeline,
269
270 #[serde(default, skip_serializing_if = "State::is_empty")]
274 pub state: State,
275
276 #[serde(default, skip_serializing_if = "RoomAccountData::is_empty")]
278 pub account_data: RoomAccountData,
279
280 #[serde(default, skip_serializing_if = "Ephemeral::is_empty")]
283 pub ephemeral: Ephemeral,
284
285 #[cfg(feature = "unstable-msc2654")]
291 #[serde(rename = "org.matrix.msc2654.unread_count", skip_serializing_if = "Option::is_none")]
292 pub unread_count: Option<UInt>,
293}
294
295impl JoinedRoom {
296 pub fn new() -> Self {
298 Default::default()
299 }
300
301 pub fn is_empty(&self) -> bool {
303 let is_empty = self.summary.is_empty()
304 && self.unread_notifications.is_empty()
305 && self.unread_thread_notifications.is_empty()
306 && self.timeline.is_empty()
307 && self.state.is_empty()
308 && self.account_data.is_empty()
309 && self.ephemeral.is_empty();
310
311 #[cfg(not(feature = "unstable-msc2654"))]
312 return is_empty;
313
314 #[cfg(feature = "unstable-msc2654")]
315 return is_empty && self.unread_count.is_none();
316 }
317}
318
319#[derive(Clone, Debug, Default, Deserialize, Serialize)]
321#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
322pub struct KnockedRoom {
323 #[serde(default, skip_serializing_if = "KnockState::is_empty")]
325 pub knock_state: KnockState,
326}
327
328impl KnockedRoom {
329 pub fn new() -> Self {
331 Default::default()
332 }
333
334 pub fn is_empty(&self) -> bool {
336 self.knock_state.is_empty()
337 }
338}
339
340impl From<KnockState> for KnockedRoom {
341 fn from(knock_state: KnockState) -> Self {
342 KnockedRoom { knock_state, ..Default::default() }
343 }
344}
345
346#[derive(Clone, Debug, Default, Deserialize, Serialize)]
348#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
349pub struct KnockState {
350 #[serde(default, skip_serializing_if = "Vec::is_empty")]
352 pub events: Vec<Raw<AnyStrippedStateEvent>>,
353}
354
355impl KnockState {
356 pub fn new() -> Self {
358 Default::default()
359 }
360
361 pub fn is_empty(&self) -> bool {
363 self.events.is_empty()
364 }
365}
366
367#[derive(Clone, Debug, Default, Deserialize, Serialize)]
369#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
370pub struct Timeline {
371 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
375 pub limited: bool,
376
377 #[serde(skip_serializing_if = "Option::is_none")]
380 pub prev_batch: Option<String>,
381
382 pub events: Vec<Raw<AnySyncTimelineEvent>>,
384}
385
386impl Timeline {
387 pub fn new() -> Self {
389 Default::default()
390 }
391
392 pub fn is_empty(&self) -> bool {
397 !self.limited && self.prev_batch.is_none() && self.events.is_empty()
398 }
399}
400
401#[derive(Clone, Debug, Default, Deserialize, Serialize)]
403#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
404pub struct State {
405 #[serde(default, skip_serializing_if = "Vec::is_empty")]
407 pub events: Vec<Raw<AnySyncStateEvent>>,
408}
409
410impl State {
411 pub fn new() -> Self {
413 Default::default()
414 }
415
416 pub fn is_empty(&self) -> bool {
418 self.events.is_empty()
419 }
420
421 pub fn with_events(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
423 State { events, ..Default::default() }
424 }
425}
426
427impl From<Vec<Raw<AnySyncStateEvent>>> for State {
428 fn from(events: Vec<Raw<AnySyncStateEvent>>) -> Self {
429 State::with_events(events)
430 }
431}
432
433#[derive(Clone, Debug, Default, Deserialize, Serialize)]
435#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
436pub struct GlobalAccountData {
437 #[serde(default, skip_serializing_if = "Vec::is_empty")]
439 pub events: Vec<Raw<AnyGlobalAccountDataEvent>>,
440}
441
442impl GlobalAccountData {
443 pub fn new() -> Self {
445 Default::default()
446 }
447
448 pub fn is_empty(&self) -> bool {
450 self.events.is_empty()
451 }
452}
453
454#[derive(Clone, Debug, Default, Deserialize, Serialize)]
456#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
457pub struct RoomAccountData {
458 #[serde(default, skip_serializing_if = "Vec::is_empty")]
460 pub events: Vec<Raw<AnyRoomAccountDataEvent>>,
461}
462
463impl RoomAccountData {
464 pub fn new() -> Self {
466 Default::default()
467 }
468
469 pub fn is_empty(&self) -> bool {
471 self.events.is_empty()
472 }
473}
474
475#[derive(Clone, Debug, Default, Deserialize, Serialize)]
477#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
478pub struct Ephemeral {
479 #[serde(default, skip_serializing_if = "Vec::is_empty")]
481 pub events: Vec<Raw<AnySyncEphemeralRoomEvent>>,
482}
483
484impl Ephemeral {
485 pub fn new() -> Self {
487 Default::default()
488 }
489
490 pub fn is_empty(&self) -> bool {
492 self.events.is_empty()
493 }
494}
495
496#[derive(Clone, Debug, Default, Deserialize, Serialize)]
498#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
499pub struct RoomSummary {
500 #[serde(rename = "m.heroes", default, skip_serializing_if = "Vec::is_empty")]
504 pub heroes: Vec<OwnedUserId>,
505
506 #[serde(rename = "m.joined_member_count", skip_serializing_if = "Option::is_none")]
510 pub joined_member_count: Option<UInt>,
511
512 #[serde(rename = "m.invited_member_count", skip_serializing_if = "Option::is_none")]
516 pub invited_member_count: Option<UInt>,
517}
518
519impl RoomSummary {
520 pub fn new() -> Self {
522 Default::default()
523 }
524
525 pub fn is_empty(&self) -> bool {
527 self.heroes.is_empty()
528 && self.joined_member_count.is_none()
529 && self.invited_member_count.is_none()
530 }
531}
532
533#[derive(Clone, Debug, Default, Deserialize, Serialize)]
535#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
536pub struct InvitedRoom {
537 #[serde(default, skip_serializing_if = "InviteState::is_empty")]
539 pub invite_state: InviteState,
540}
541
542impl InvitedRoom {
543 pub fn new() -> Self {
545 Default::default()
546 }
547
548 pub fn is_empty(&self) -> bool {
550 self.invite_state.is_empty()
551 }
552}
553
554impl From<InviteState> for InvitedRoom {
555 fn from(invite_state: InviteState) -> Self {
556 InvitedRoom { invite_state, ..Default::default() }
557 }
558}
559
560#[derive(Clone, Debug, Default, Deserialize, Serialize)]
562#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
563pub struct InviteState {
564 #[serde(default, skip_serializing_if = "Vec::is_empty")]
566 pub events: Vec<Raw<AnyStrippedStateEvent>>,
567}
568
569impl InviteState {
570 pub fn new() -> Self {
572 Default::default()
573 }
574
575 pub fn is_empty(&self) -> bool {
577 self.events.is_empty()
578 }
579}
580
581impl From<Vec<Raw<AnyStrippedStateEvent>>> for InviteState {
582 fn from(events: Vec<Raw<AnyStrippedStateEvent>>) -> Self {
583 InviteState { events, ..Default::default() }
584 }
585}
586
587#[derive(Clone, Debug, Default, Deserialize, Serialize)]
589#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
590pub struct Presence {
591 #[serde(default, skip_serializing_if = "Vec::is_empty")]
593 pub events: Vec<Raw<PresenceEvent>>,
594}
595
596impl Presence {
597 pub fn new() -> Self {
599 Default::default()
600 }
601
602 pub fn is_empty(&self) -> bool {
604 self.events.is_empty()
605 }
606}
607
608#[derive(Clone, Debug, Default, Deserialize, Serialize)]
610#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
611pub struct ToDevice {
612 #[serde(default, skip_serializing_if = "Vec::is_empty")]
614 pub events: Vec<Raw<AnyToDeviceEvent>>,
615}
616
617impl ToDevice {
618 pub fn new() -> Self {
620 Default::default()
621 }
622
623 pub fn is_empty(&self) -> bool {
625 self.events.is_empty()
626 }
627}
628
629#[cfg(test)]
630mod tests {
631 use assign::assign;
632 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
633
634 use super::Timeline;
635
636 #[test]
637 fn timeline_serde() {
638 let timeline = assign!(Timeline::new(), { limited: true });
639 let timeline_serialized = json!({ "events": [], "limited": true });
640 assert_eq!(to_json_value(timeline).unwrap(), timeline_serialized);
641
642 let timeline_deserialized = from_json_value::<Timeline>(timeline_serialized).unwrap();
643 assert!(timeline_deserialized.limited);
644
645 let timeline_default = Timeline::default();
646 assert_eq!(to_json_value(timeline_default).unwrap(), json!({ "events": [] }));
647
648 let timeline_default_deserialized =
649 from_json_value::<Timeline>(json!({ "events": [] })).unwrap();
650 assert!(!timeline_default_deserialized.limited);
651 }
652}
653
654#[cfg(all(test, feature = "client"))]
655mod client_tests {
656 use std::time::Duration;
657
658 use ruma_common::api::{MatrixVersion, OutgoingRequest as _, SendAccessToken};
659
660 use super::{Filter, PresenceState, Request};
661
662 #[test]
663 fn serialize_all_params() {
664 let req: http::Request<Vec<u8>> = Request {
665 filter: Some(Filter::FilterId("66696p746572".to_owned())),
666 since: Some("s72594_4483_1934".to_owned()),
667 full_state: true,
668 set_presence: PresenceState::Offline,
669 timeout: Some(Duration::from_millis(30000)),
670 }
671 .try_into_http_request(
672 "https://homeserver.tld",
673 SendAccessToken::IfRequired("auth_tok"),
674 &[MatrixVersion::V1_1],
675 )
676 .unwrap();
677
678 let uri = req.uri();
679 let query = uri.query().unwrap();
680
681 assert_eq!(uri.path(), "/_matrix/client/v3/sync");
682 assert!(query.contains("filter=66696p746572"));
683 assert!(query.contains("since=s72594_4483_1934"));
684 assert!(query.contains("full_state=true"));
685 assert!(query.contains("set_presence=offline"));
686 assert!(query.contains("timeout=30000"));
687 }
688}
689
690#[cfg(all(test, feature = "server"))]
691mod server_tests {
692 use std::time::Duration;
693
694 use assert_matches2::assert_matches;
695 use ruma_common::{api::IncomingRequest as _, presence::PresenceState};
696
697 use super::{Filter, Request};
698
699 #[test]
700 fn deserialize_all_query_params() {
701 let uri = http::Uri::builder()
702 .scheme("https")
703 .authority("matrix.org")
704 .path_and_query(
705 "/_matrix/client/r0/sync\
706 ?filter=myfilter\
707 &since=myts\
708 &full_state=false\
709 &set_presence=offline\
710 &timeout=5000",
711 )
712 .build()
713 .unwrap();
714
715 let req = Request::try_from_http_request(
716 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
717 &[] as &[String],
718 )
719 .unwrap();
720
721 assert_matches!(req.filter, Some(Filter::FilterId(id)));
722 assert_eq!(id, "myfilter");
723 assert_eq!(req.since.as_deref(), Some("myts"));
724 assert!(!req.full_state);
725 assert_eq!(req.set_presence, PresenceState::Offline);
726 assert_eq!(req.timeout, Some(Duration::from_millis(5000)));
727 }
728
729 #[test]
730 fn deserialize_no_query_params() {
731 let uri = http::Uri::builder()
732 .scheme("https")
733 .authority("matrix.org")
734 .path_and_query("/_matrix/client/r0/sync")
735 .build()
736 .unwrap();
737
738 let req = Request::try_from_http_request(
739 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
740 &[] as &[String],
741 )
742 .unwrap();
743
744 assert_matches!(req.filter, None);
745 assert_eq!(req.since, None);
746 assert!(!req.full_state);
747 assert_eq!(req.set_presence, PresenceState::Online);
748 assert_eq!(req.timeout, None);
749 }
750
751 #[test]
752 fn deserialize_some_query_params() {
753 let uri = http::Uri::builder()
754 .scheme("https")
755 .authority("matrix.org")
756 .path_and_query(
757 "/_matrix/client/r0/sync\
758 ?filter=EOKFFmdZYF\
759 &timeout=0",
760 )
761 .build()
762 .unwrap();
763
764 let req = Request::try_from_http_request(
765 http::Request::builder().uri(uri).body(&[] as &[u8]).unwrap(),
766 &[] as &[String],
767 )
768 .unwrap();
769
770 assert_matches!(req.filter, Some(Filter::FilterId(id)));
771 assert_eq!(id, "EOKFFmdZYF");
772 assert_eq!(req.since, None);
773 assert!(!req.full_state);
774 assert_eq!(req.set_presence, PresenceState::Online);
775 assert_eq!(req.timeout, Some(Duration::from_millis(0)));
776 }
777}