1use std::{collections::BTreeMap, time::Duration};
11
12use js_int::UInt;
13use js_option::JsOption;
14use ruma_common::{
15 OwnedMxcUri, OwnedRoomId, OwnedUserId,
16 api::{auth_scheme::AccessToken, request, response},
17 metadata,
18 presence::PresenceState,
19 serde::{Raw, duration::opt_ms},
20};
21use ruma_events::{AnySyncStateEvent, AnySyncTimelineEvent, StateEventType};
22use serde::{Deserialize, Serialize};
23
24use super::UnreadNotificationsCount;
25
26metadata! {
27 method: POST,
28 rate_limited: false,
29 authentication: AccessToken,
30 history: {
31 unstable("org.matrix.simplified_msc3575") => "/_matrix/client/unstable/org.matrix.simplified_msc3575/sync",
32 }
34}
35
36#[request]
38#[derive(Default)]
39pub struct Request {
40 #[serde(skip_serializing_if = "Option::is_none")]
46 #[ruma_api(query)]
47 pub pos: Option<String>,
48
49 #[serde(skip_serializing_if = "Option::is_none")]
60 pub conn_id: Option<String>,
61
62 #[serde(skip_serializing_if = "Option::is_none")]
65 pub txn_id: Option<String>,
66
67 #[serde(with = "opt_ms", default, skip_serializing_if = "Option::is_none")]
71 #[ruma_api(query)]
72 pub timeout: Option<Duration>,
73
74 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
78 #[ruma_api(query)]
79 pub set_presence: PresenceState,
80
81 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
83 pub lists: BTreeMap<String, request::List>,
84
85 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
90 pub room_subscriptions: BTreeMap<OwnedRoomId, request::RoomSubscription>,
91
92 #[serde(default, skip_serializing_if = "request::Extensions::is_empty")]
94 pub extensions: request::Extensions,
95}
96
97impl Request {
98 pub fn new() -> Self {
100 Default::default()
101 }
102}
103
104pub mod request {
106 use ruma_common::{RoomId, directory::RoomTypeFilter, serde::deserialize_cow_str};
107 use serde::de::Error as _;
108
109 use super::{BTreeMap, Deserialize, OwnedRoomId, Serialize, StateEventType, UInt};
110
111 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
113 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
114 pub struct List {
115 pub ranges: Vec<(UInt, UInt)>,
117
118 #[serde(flatten)]
120 pub room_details: RoomDetails,
121
122 #[serde(skip_serializing_if = "Option::is_none")]
124 pub filters: Option<ListFilters>,
125 }
126
127 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
132 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
133 pub struct ListFilters {
134 #[serde(skip_serializing_if = "Option::is_none")]
137 pub is_dm: Option<bool>,
138
139 #[serde(skip_serializing_if = "Option::is_none")]
142 pub is_encrypted: Option<bool>,
143
144 #[serde(skip_serializing_if = "Option::is_none")]
146 pub is_invite: Option<bool>,
147
148 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
153 pub room_types: Vec<RoomTypeFilter>,
154
155 #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
160 pub not_room_types: Vec<RoomTypeFilter>,
161 }
162
163 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
165 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
166 pub struct RoomSubscription {
167 #[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)]
178 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
179 pub struct RoomDetails {
180 #[serde(default, skip_serializing_if = "Vec::is_empty")]
182 pub required_state: Vec<(StateEventType, String)>,
183
184 pub timeline_limit: UInt,
186 }
187
188 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
190 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
191 pub struct Extensions {
192 #[serde(default, skip_serializing_if = "ToDevice::is_empty")]
194 pub to_device: ToDevice,
195
196 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
198 pub e2ee: E2EE,
199
200 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
202 pub account_data: AccountData,
203
204 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
206 pub receipts: Receipts,
207
208 #[serde(default, skip_serializing_if = "Typing::is_empty")]
210 pub typing: Typing,
211
212 #[cfg(feature = "unstable-msc4308")]
214 #[serde(
215 default,
216 skip_serializing_if = "ThreadSubscriptions::is_empty",
217 rename = "io.element.msc4308.thread_subscriptions"
218 )]
219 pub thread_subscriptions: ThreadSubscriptions,
220
221 #[cfg(feature = "unstable-msc4262")]
223 #[serde(default, skip_serializing_if = "Profiles::is_empty")]
224 pub profiles: Profiles,
225
226 #[serde(flatten)]
228 other: BTreeMap<String, serde_json::Value>,
229 }
230
231 impl Extensions {
232 pub fn is_empty(&self) -> bool {
234 let mut empty = self.to_device.is_empty()
235 && self.e2ee.is_empty()
236 && self.account_data.is_empty()
237 && self.receipts.is_empty()
238 && self.typing.is_empty()
239 && self.other.is_empty();
240
241 #[cfg(feature = "unstable-msc4308")]
242 {
243 empty = empty && self.thread_subscriptions.is_empty();
244 }
245
246 #[cfg(feature = "unstable-msc4262")]
247 {
248 empty = empty && self.profiles.is_empty();
249 }
250
251 empty
252 }
253 }
254
255 #[derive(Clone, Debug, PartialEq)]
257 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
258 pub enum ExtensionRoomConfig {
259 AllSubscribed,
261
262 Room(OwnedRoomId),
264 }
265
266 impl Serialize for ExtensionRoomConfig {
267 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
268 where
269 S: serde::Serializer,
270 {
271 match self {
272 Self::AllSubscribed => serializer.serialize_str("*"),
273 Self::Room(r) => r.serialize(serializer),
274 }
275 }
276 }
277
278 impl<'de> Deserialize<'de> for ExtensionRoomConfig {
279 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
280 where
281 D: serde::de::Deserializer<'de>,
282 {
283 match deserialize_cow_str(deserializer)?.as_ref() {
284 "*" => Ok(Self::AllSubscribed),
285 other => Ok(Self::Room(RoomId::parse(other).map_err(D::Error::custom)?)),
286 }
287 }
288 }
289
290 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
294 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
295 pub struct ToDevice {
296 #[serde(skip_serializing_if = "Option::is_none")]
298 pub enabled: Option<bool>,
299
300 #[serde(skip_serializing_if = "Option::is_none")]
302 pub limit: Option<UInt>,
303
304 #[serde(skip_serializing_if = "Option::is_none")]
306 pub since: Option<String>,
307 }
308
309 impl ToDevice {
310 pub fn is_empty(&self) -> bool {
312 self.enabled.is_none() && self.limit.is_none() && self.since.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 E2EE {
322 #[serde(skip_serializing_if = "Option::is_none")]
324 pub enabled: Option<bool>,
325 }
326
327 impl E2EE {
328 pub fn is_empty(&self) -> bool {
330 self.enabled.is_none()
331 }
332 }
333
334 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
339 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
340 pub struct AccountData {
341 #[serde(skip_serializing_if = "Option::is_none")]
343 pub enabled: Option<bool>,
344
345 #[serde(skip_serializing_if = "Option::is_none")]
352 pub lists: Option<Vec<String>>,
353
354 #[serde(skip_serializing_if = "Option::is_none")]
362 pub rooms: Option<Vec<ExtensionRoomConfig>>,
363 }
364
365 impl AccountData {
366 pub fn is_empty(&self) -> bool {
368 self.enabled.is_none()
369 }
370 }
371
372 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
376 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
377 pub struct Receipts {
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 Receipts {
399 pub fn is_empty(&self) -> bool {
401 self.enabled.is_none()
402 }
403 }
404
405 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
410 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
411 pub struct Typing {
412 #[serde(skip_serializing_if = "Option::is_none")]
414 pub enabled: Option<bool>,
415
416 #[serde(skip_serializing_if = "Option::is_none")]
421 pub lists: Option<Vec<String>>,
422
423 #[serde(skip_serializing_if = "Option::is_none")]
429 pub rooms: Option<Vec<ExtensionRoomConfig>>,
430 }
431
432 impl Typing {
433 pub fn is_empty(&self) -> bool {
435 self.enabled.is_none()
436 }
437 }
438
439 #[cfg(feature = "unstable-msc4308")]
443 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
444 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
445 pub struct ThreadSubscriptions {
446 #[serde(skip_serializing_if = "Option::is_none")]
448 pub enabled: Option<bool>,
449
450 #[serde(skip_serializing_if = "Option::is_none")]
455 pub limit: Option<UInt>,
456 }
457
458 #[cfg(feature = "unstable-msc4308")]
459 impl ThreadSubscriptions {
460 pub fn is_empty(&self) -> bool {
462 self.enabled.is_none() && self.limit.is_none()
463 }
464 }
465
466 #[cfg(feature = "unstable-msc4262")]
470 #[derive(Clone, Debug, Default, Serialize, Deserialize, PartialEq)]
471 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
472 pub struct Profiles {
473 #[serde(skip_serializing_if = "Option::is_none")]
475 pub enabled: Option<bool>,
476
477 #[serde(skip_serializing_if = "Option::is_none")]
482 pub lists: Option<Vec<String>>,
483
484 #[serde(skip_serializing_if = "Option::is_none")]
490 pub rooms: Option<Vec<ExtensionRoomConfig>>,
491
492 #[serde(skip_serializing_if = "Option::is_none")]
495 pub fields: Option<Vec<ruma_common::profile::ProfileFieldName>>,
496
497 #[serde(skip_serializing_if = "Option::is_none")]
503 pub include_history: Option<bool>,
504 }
505
506 #[cfg(feature = "unstable-msc4262")]
507 impl Profiles {
508 pub fn is_empty(&self) -> bool {
510 self.enabled.is_none()
511 }
512 }
513}
514
515#[response]
517pub struct Response {
518 #[serde(skip_serializing_if = "Option::is_none")]
520 pub txn_id: Option<String>,
521
522 pub pos: String,
525
526 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
528 pub lists: BTreeMap<String, response::List>,
529
530 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
532 pub rooms: BTreeMap<OwnedRoomId, response::Room>,
533
534 #[serde(default, skip_serializing_if = "response::Extensions::is_empty")]
536 pub extensions: response::Extensions,
537}
538
539impl Response {
540 pub fn new(pos: String) -> Self {
542 Self {
543 txn_id: None,
544 pos,
545 lists: Default::default(),
546 rooms: Default::default(),
547 extensions: Default::default(),
548 }
549 }
550}
551
552pub mod response {
554 use ruma_common::OneTimeKeyAlgorithm;
555 #[cfg(feature = "unstable-msc4308")]
556 use ruma_common::OwnedEventId;
557 #[cfg(feature = "unstable-msc4262")]
558 use ruma_common::profile::UserProfile;
559 use ruma_events::{
560 AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
561 AnyToDeviceEvent, receipt::SyncReceiptEvent, typing::SyncTypingEvent,
562 };
563
564 use super::{
565 super::DeviceLists, AnySyncStateEvent, AnySyncTimelineEvent, BTreeMap, Deserialize,
566 JsOption, OwnedMxcUri, OwnedRoomId, OwnedUserId, Raw, Serialize, UInt,
567 UnreadNotificationsCount,
568 };
569 #[cfg(feature = "unstable-msc4308")]
570 use crate::threads::get_thread_subscriptions_changes::unstable::{
571 ThreadSubscription, ThreadUnsubscription,
572 };
573
574 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
577 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
578 pub struct List {
579 pub count: UInt,
581 }
582
583 #[derive(Clone, Debug, Default, Deserialize, Serialize)]
585 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
586 pub struct Room {
587 #[serde(skip_serializing_if = "Option::is_none")]
592 #[cfg_attr(
593 feature = "unstable-compat-lax-syncv5-deser",
594 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
595 )]
596 pub name: Option<String>,
597
598 #[serde(default, skip_serializing_if = "JsOption::is_undefined")]
603 #[cfg_attr(
604 feature = "unstable-compat-lax-syncv5-deser",
605 serde(deserialize_with = "ruma_common::serde::default_on_error")
606 )]
607 pub avatar: JsOption<OwnedMxcUri>,
608
609 #[serde(skip_serializing_if = "Option::is_none")]
611 pub initial: Option<bool>,
612
613 #[serde(skip_serializing_if = "Option::is_none")]
615 pub is_dm: Option<bool>,
616
617 #[serde(skip_serializing_if = "Option::is_none")]
620 pub invite_state: Option<Vec<Raw<AnyStrippedStateEvent>>>,
621
622 #[serde(flatten, default, skip_serializing_if = "UnreadNotificationsCount::is_empty")]
624 pub unread_notifications: UnreadNotificationsCount,
625
626 #[serde(default, skip_serializing_if = "Vec::is_empty")]
628 pub timeline: Vec<Raw<AnySyncTimelineEvent>>,
629
630 #[serde(default, skip_serializing_if = "Vec::is_empty")]
632 pub required_state: Vec<Raw<AnySyncStateEvent>>,
633
634 #[serde(skip_serializing_if = "Option::is_none")]
637 pub prev_batch: Option<String>,
638
639 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
642 pub limited: bool,
643
644 #[serde(skip_serializing_if = "Option::is_none")]
647 pub joined_count: Option<UInt>,
648
649 #[serde(skip_serializing_if = "Option::is_none")]
651 pub invited_count: Option<UInt>,
652
653 #[serde(skip_serializing_if = "Option::is_none")]
656 pub num_live: Option<UInt>,
657
658 #[serde(skip_serializing_if = "Option::is_none")]
665 pub bump_stamp: Option<UInt>,
666
667 #[serde(skip_serializing_if = "Option::is_none")]
669 pub heroes: Option<Vec<Hero>>,
670 }
671
672 impl Room {
673 pub fn new() -> Self {
675 Default::default()
676 }
677 }
678
679 #[derive(Clone, Debug, Deserialize, Serialize)]
681 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
682 pub struct Hero {
683 pub user_id: OwnedUserId,
685
686 #[serde(rename = "displayname", skip_serializing_if = "Option::is_none")]
691 #[cfg_attr(
692 feature = "unstable-compat-lax-syncv5-deser",
693 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
694 )]
695 pub name: Option<String>,
696
697 #[serde(rename = "avatar_url", skip_serializing_if = "Option::is_none")]
702 #[cfg_attr(
703 feature = "unstable-compat-lax-syncv5-deser",
704 serde(default, deserialize_with = "ruma_common::serde::default_on_error")
705 )]
706 pub avatar: Option<OwnedMxcUri>,
707 }
708
709 impl Hero {
710 pub fn new(user_id: OwnedUserId) -> Self {
712 Self { user_id, name: None, avatar: None }
713 }
714 }
715
716 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
718 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
719 pub struct Extensions {
720 #[serde(skip_serializing_if = "Option::is_none")]
722 pub to_device: Option<ToDevice>,
723
724 #[serde(default, skip_serializing_if = "E2EE::is_empty")]
726 pub e2ee: E2EE,
727
728 #[serde(default, skip_serializing_if = "AccountData::is_empty")]
730 pub account_data: AccountData,
731
732 #[serde(default, skip_serializing_if = "Receipts::is_empty")]
734 pub receipts: Receipts,
735
736 #[serde(default, skip_serializing_if = "Typing::is_empty")]
738 pub typing: Typing,
739
740 #[cfg(feature = "unstable-msc4308")]
742 #[serde(
743 default,
744 skip_serializing_if = "ThreadSubscriptions::is_empty",
745 rename = "io.element.msc4308.thread_subscriptions"
746 )]
747 pub thread_subscriptions: ThreadSubscriptions,
748
749 #[cfg(feature = "unstable-msc4262")]
751 #[serde(default, skip_serializing_if = "BTreeMap::is_empty", rename = "users")]
752 pub profiles: BTreeMap<OwnedUserId, UserProfile>,
753 }
754
755 impl Extensions {
756 pub fn is_empty(&self) -> bool {
760 let mut empty = self.to_device.is_none()
761 && self.e2ee.is_empty()
762 && self.account_data.is_empty()
763 && self.receipts.is_empty()
764 && self.typing.is_empty();
765
766 #[cfg(feature = "unstable-msc4308")]
767 {
768 empty = empty && self.thread_subscriptions.is_empty();
769 }
770
771 #[cfg(feature = "unstable-msc4262")]
772 {
773 empty = empty && self.profiles.is_empty();
774 }
775
776 empty
777 }
778 }
779
780 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
784 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
785 pub struct ToDevice {
786 pub next_batch: String,
788
789 #[serde(default, skip_serializing_if = "Vec::is_empty")]
791 pub events: Vec<Raw<AnyToDeviceEvent>>,
792 }
793
794 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
798 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
799 pub struct E2EE {
800 #[serde(default, skip_serializing_if = "DeviceLists::is_empty")]
802 pub device_lists: DeviceLists,
803
804 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
807 pub device_one_time_keys_count: BTreeMap<OneTimeKeyAlgorithm, UInt>,
808
809 #[serde(skip_serializing_if = "Option::is_none")]
814 pub device_unused_fallback_key_types: Option<Vec<OneTimeKeyAlgorithm>>,
815 }
816
817 impl E2EE {
818 pub fn is_empty(&self) -> bool {
820 self.device_lists.is_empty()
821 && self.device_one_time_keys_count.is_empty()
822 && self.device_unused_fallback_key_types.is_none()
823 }
824 }
825
826 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
831 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
832 pub struct AccountData {
833 #[serde(default, skip_serializing_if = "Vec::is_empty")]
835 pub global: Vec<Raw<AnyGlobalAccountDataEvent>>,
836
837 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
839 pub rooms: BTreeMap<OwnedRoomId, Vec<Raw<AnyRoomAccountDataEvent>>>,
840 }
841
842 impl AccountData {
843 pub fn is_empty(&self) -> bool {
845 self.global.is_empty() && self.rooms.is_empty()
846 }
847 }
848
849 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
853 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
854 pub struct Receipts {
855 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
857 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncReceiptEvent>>,
858 }
859
860 impl Receipts {
861 pub fn is_empty(&self) -> bool {
863 self.rooms.is_empty()
864 }
865 }
866
867 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
872 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
873 pub struct Typing {
874 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
876 pub rooms: BTreeMap<OwnedRoomId, Raw<SyncTypingEvent>>,
877 }
878
879 impl Typing {
880 pub fn is_empty(&self) -> bool {
882 self.rooms.is_empty()
883 }
884 }
885
886 #[cfg(feature = "unstable-msc4308")]
890 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
891 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
892 pub struct ThreadSubscriptions {
893 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
895 pub subscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadSubscription>>,
896
897 #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
899 pub unsubscribed: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, ThreadUnsubscription>>,
900
901 #[serde(skip_serializing_if = "Option::is_none")]
907 pub prev_batch: Option<String>,
908 }
909
910 #[cfg(feature = "unstable-msc4308")]
911 impl ThreadSubscriptions {
912 pub fn is_empty(&self) -> bool {
914 self.subscribed.is_empty() && self.unsubscribed.is_empty() && self.prev_batch.is_none()
915 }
916 }
917}
918
919#[cfg(test)]
920mod tests {
921 use ruma_common::owned_room_id;
922
923 use super::request::ExtensionRoomConfig;
924
925 #[test]
926 fn serialize_request_extension_room_config() {
927 let entry = ExtensionRoomConfig::AllSubscribed;
928 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""*""#);
929
930 let entry = ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"));
931 assert_eq!(serde_json::to_string(&entry).unwrap().as_str(), r#""!foo:bar.baz""#);
932 }
933
934 #[test]
935 fn deserialize_request_extension_room_config() {
936 assert_eq!(
937 serde_json::from_str::<ExtensionRoomConfig>(r#""*""#).unwrap(),
938 ExtensionRoomConfig::AllSubscribed
939 );
940
941 assert_eq!(
942 serde_json::from_str::<ExtensionRoomConfig>(r#""!foo:bar.baz""#).unwrap(),
943 ExtensionRoomConfig::Room(owned_room_id!("!foo:bar.baz"))
944 );
945 }
946
947 #[test]
948 #[cfg(feature = "unstable-compat-lax-syncv5-deser")]
949 fn deserialize_room_ignores_invalid_string_fields() {
950 use super::response::Room;
951
952 let room: Room = serde_json::from_str(
953 r#"{
954 "name": {},
955 "avatar": {},
956 "heroes": [{ "user_id": "@alice:localhost", "displayname": {}, "avatar_url": {} }]
957 }"#,
958 )
959 .unwrap();
960
961 assert_eq!(room.name, None);
962 assert!(room.avatar.is_undefined());
963 let hero = &room.heroes.unwrap()[0];
964 assert_eq!(hero.name, None);
965 assert_eq!(hero.avatar, None);
966
967 let room: Room = serde_json::from_str(
969 r#"{ "name": "Room", "avatar": "mxc://localhost/a", "heroes": [{ "user_id": "@alice:localhost", "displayname": "Alice", "avatar_url": "mxc://localhost/b" }] }"#,
970 )
971 .unwrap();
972
973 assert_eq!(room.name.as_deref(), Some("Room"));
974 assert_eq!(room.avatar.into_option().unwrap().as_str(), "mxc://localhost/a");
975 let hero = &room.heroes.unwrap()[0];
976 assert_eq!(hero.name.as_deref(), Some("Alice"));
977 assert_eq!(hero.avatar.as_ref().unwrap().as_str(), "mxc://localhost/b");
978 }
979}