Skip to main content

ruma_client_api/discovery/
get_capabilities.rs

1//! `GET /_matrix/client/*/capabilities`
2//!
3//! Get information about the server's supported feature set and other relevant capabilities
4//! ([spec]).
5//!
6//! [spec]: https://spec.matrix.org/v1.19/client-server-api/#capabilities-negotiation
7
8pub mod v3 {
9    //! `/v3/` ([spec])
10    //!
11    //! [spec]: https://spec.matrix.org/v1.19/client-server-api/#get_matrixclientv3capabilities
12
13    #[cfg(feature = "unstable-msc4540")]
14    use std::collections::BTreeSet;
15    use std::{borrow::Cow, collections::BTreeMap};
16
17    use maplit::btreemap;
18    #[cfg(feature = "unstable-msc4540")]
19    use ruma_common::api::OAuthClientScope;
20    use ruma_common::{
21        RoomVersionId,
22        api::{auth_scheme::AccessToken, request, response},
23        metadata,
24        profile::ProfileFieldName,
25        serde::StringEnum,
26    };
27    use serde::{Deserialize, Deserializer, Serialize, de};
28    use serde_json::{
29        Map as JsonMap, Value as JsonValue, from_value as from_json_value,
30        to_value as to_json_value,
31    };
32
33    use crate::PrivOwnedStr;
34
35    metadata! {
36        method: GET,
37        rate_limited: true,
38        authentication: AccessToken,
39        history: {
40            1.0 => "/_matrix/client/r0/capabilities",
41            1.1 => "/_matrix/client/v3/capabilities",
42        }
43    }
44
45    /// Request type for the `get_capabilities` endpoint.
46    #[request]
47    #[derive(Default)]
48    pub struct Request {}
49
50    /// Response type for the `get_capabilities` endpoint.
51    #[response]
52    pub struct Response {
53        /// The capabilities the server supports
54        pub capabilities: Capabilities,
55    }
56
57    impl Request {
58        /// Creates an empty `Request`.
59        pub fn new() -> Self {
60            Self {}
61        }
62    }
63
64    impl Response {
65        /// Creates a new `Response` with the given capabilities.
66        pub fn new(capabilities: Capabilities) -> Self {
67            Self { capabilities }
68        }
69    }
70
71    impl From<Capabilities> for Response {
72        fn from(capabilities: Capabilities) -> Self {
73            Self::new(capabilities)
74        }
75    }
76
77    /// Contains information about all the capabilities that the server supports.
78    #[derive(Clone, Debug, Default, Serialize)]
79    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
80    #[allow(deprecated)]
81    pub struct Capabilities {
82        /// Capability to indicate if the user can change their password.
83        #[serde(
84            rename = "m.change_password",
85            default,
86            skip_serializing_if = "ChangePasswordCapability::is_default"
87        )]
88        pub change_password: ChangePasswordCapability,
89
90        /// The room versions the server supports.
91        #[serde(
92            rename = "m.room_versions",
93            default,
94            skip_serializing_if = "RoomVersionsCapability::is_default"
95        )]
96        pub room_versions: RoomVersionsCapability,
97
98        /// Capability to indicate if the user can change their display name.
99        #[serde(
100            rename = "m.set_displayname",
101            default,
102            skip_serializing_if = "SetDisplayNameCapability::is_default"
103        )]
104        #[deprecated = "Since Matrix 1.16, prefer profile_fields if it is set."]
105        pub set_displayname: SetDisplayNameCapability,
106
107        /// Capability to indicate if the user can change their avatar.
108        #[serde(
109            rename = "m.set_avatar_url",
110            default,
111            skip_serializing_if = "SetAvatarUrlCapability::is_default"
112        )]
113        #[deprecated = "Since Matrix 1.16, prefer profile_fields if it is set."]
114        pub set_avatar_url: SetAvatarUrlCapability,
115
116        /// Capability to indicate if the user can change the third-party identifiers associated
117        /// with their account.
118        #[serde(
119            rename = "m.3pid_changes",
120            default,
121            skip_serializing_if = "ThirdPartyIdChangesCapability::is_default"
122        )]
123        pub thirdparty_id_changes: ThirdPartyIdChangesCapability,
124
125        /// Capability to indicate if the user can generate tokens to log further clients into
126        /// their account.
127        #[serde(
128            rename = "m.get_login_token",
129            default,
130            skip_serializing_if = "GetLoginTokenCapability::is_default"
131        )]
132        pub get_login_token: GetLoginTokenCapability,
133
134        /// Capability to indicate if the user can set extended profile fields.
135        #[serde(rename = "m.profile_fields", skip_serializing_if = "Option::is_none")]
136        pub profile_fields: Option<ProfileFieldsCapability>,
137
138        /// Capability to indicate if the server automatically forgets rooms that the user leaves.
139        #[serde(
140            rename = "m.forget_forced_upon_leave",
141            default,
142            skip_serializing_if = "ForgetForcedUponLeaveCapability::is_default"
143        )]
144        pub forget_forced_upon_leave: ForgetForcedUponLeaveCapability,
145
146        /// Capability to indicate if the user can perform account moderation actions via [server
147        /// administration] endpoints.
148        ///
149        /// [server administration]: https://spec.matrix.org/v1.19/client-server-api/#server-administration
150        #[serde(
151            rename = "m.account_moderation",
152            default,
153            skip_serializing_if = "AccountModerationCapability::is_default"
154        )]
155        pub account_moderation: AccountModerationCapability,
156
157        /// Capability to indicate if the user can perform administrative actions. ([MSC4540])
158        ///
159        /// [MSC4540]: https://github.com/matrix-org/matrix-spec-proposals/pull/4540
160        #[cfg(feature = "unstable-msc4540")]
161        #[serde(
162            rename = "org.continuwuity.msc4540.admin",
163            default,
164            skip_serializing_if = "AdminCapability::is_default"
165        )]
166        pub admin: AdminCapability,
167
168        /// Any other custom capabilities that the server supports outside of the specification,
169        /// labeled using the Java package naming convention and stored as arbitrary JSON values.
170        #[serde(flatten)]
171        custom_capabilities: BTreeMap<String, JsonValue>,
172    }
173
174    impl<'de> Deserialize<'de> for Capabilities {
175        fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
176            #[derive(Deserialize)]
177            #[allow(deprecated)]
178            struct CapabilitiesDeHelper {
179                #[serde(rename = "m.change_password", default)]
180                change_password: ChangePasswordCapability,
181                #[serde(rename = "m.room_versions", default)]
182                room_versions: RoomVersionsCapability,
183                #[serde(rename = "m.set_displayname", default)]
184                set_displayname: SetDisplayNameCapability,
185                #[serde(rename = "m.set_avatar_url", default)]
186                set_avatar_url: SetAvatarUrlCapability,
187                #[serde(rename = "m.3pid_changes", default)]
188                thirdparty_id_changes: ThirdPartyIdChangesCapability,
189                #[serde(rename = "m.get_login_token", default)]
190                get_login_token: GetLoginTokenCapability,
191                #[serde(rename = "m.profile_fields", alias = "uk.tcpip.msc4133.profile_fields")]
192                profile_fields: Option<ProfileFieldsCapability>,
193                #[serde(rename = "m.forget_forced_upon_leave", default)]
194                forget_forced_upon_leave: ForgetForcedUponLeaveCapability,
195                #[serde(rename = "m.account_moderation", default)]
196                account_moderation: AccountModerationCapability,
197                #[cfg(feature = "unstable-msc4540")]
198                #[serde(rename = "org.continuwuity.msc4540.admin", default)]
199                admin: AdminCapability,
200                #[serde(flatten)]
201                custom_capabilities: BTreeMap<String, JsonValue>,
202            }
203
204            let mut capabilities = JsonMap::deserialize(deserializer)?;
205            // Servers may send both names, which serde rejects as a duplicate field.
206            if capabilities.contains_key("m.profile_fields") {
207                capabilities.remove("uk.tcpip.msc4133.profile_fields");
208            }
209            let helper = CapabilitiesDeHelper::deserialize(JsonValue::Object(capabilities))
210                .map_err(de::Error::custom)?;
211
212            #[allow(deprecated)]
213            Ok(Self {
214                change_password: helper.change_password,
215                room_versions: helper.room_versions,
216                set_displayname: helper.set_displayname,
217                set_avatar_url: helper.set_avatar_url,
218                thirdparty_id_changes: helper.thirdparty_id_changes,
219                get_login_token: helper.get_login_token,
220                profile_fields: helper.profile_fields,
221                forget_forced_upon_leave: helper.forget_forced_upon_leave,
222                account_moderation: helper.account_moderation,
223                #[cfg(feature = "unstable-msc4540")]
224                admin: helper.admin,
225                custom_capabilities: helper.custom_capabilities,
226            })
227        }
228    }
229
230    impl Capabilities {
231        /// Creates empty `Capabilities`.
232        pub fn new() -> Self {
233            Default::default()
234        }
235
236        /// Returns the value of the given capability.
237        ///
238        /// Prefer to use the public fields of `Capabilities` where possible; this method is meant
239        /// to be used for unsupported capabilities only.
240        pub fn get(&self, capability: &str) -> Option<Cow<'_, JsonValue>> {
241            fn serialize<T: Serialize>(cap: &T) -> JsonValue {
242                to_json_value(cap).expect("capability serialization to succeed")
243            }
244
245            match capability {
246                "m.change_password" => Some(Cow::Owned(serialize(&self.change_password))),
247                "m.room_versions" => Some(Cow::Owned(serialize(&self.room_versions))),
248                #[allow(deprecated)]
249                "m.set_displayname" => Some(Cow::Owned(serialize(&self.set_displayname))),
250                #[allow(deprecated)]
251                "m.set_avatar_url" => Some(Cow::Owned(serialize(&self.set_avatar_url))),
252                "m.3pid_changes" => Some(Cow::Owned(serialize(&self.thirdparty_id_changes))),
253                "m.get_login_token" => Some(Cow::Owned(serialize(&self.get_login_token))),
254                "m.profile_fields" | "uk.tcpip.msc4133.profile_fields" => {
255                    self.profile_fields.as_ref().map(|cap| Cow::Owned(serialize(cap)))
256                }
257                "m.forget_forced_upon_leave" => {
258                    Some(Cow::Owned(serialize(&self.forget_forced_upon_leave)))
259                }
260                "m.account_moderation" => Some(Cow::Owned(serialize(&self.account_moderation))),
261                #[cfg(feature = "unstable-msc4540")]
262                "org.continuwuity.msc4540.admin" => Some(Cow::Owned(serialize(&self.admin))),
263                _ => self.custom_capabilities.get(capability).map(Cow::Borrowed),
264            }
265        }
266
267        /// Sets a capability to the given value.
268        ///
269        /// Prefer to use the public fields of `Capabilities` where possible; this method is meant
270        /// to be used for unsupported capabilities only and does not allow setting
271        /// arbitrary data for supported ones.
272        pub fn set(&mut self, capability: &str, value: JsonValue) -> serde_json::Result<()> {
273            match capability {
274                "m.change_password" => self.change_password = from_json_value(value)?,
275                "m.room_versions" => self.room_versions = from_json_value(value)?,
276                #[allow(deprecated)]
277                "m.set_displayname" => self.set_displayname = from_json_value(value)?,
278                #[allow(deprecated)]
279                "m.set_avatar_url" => self.set_avatar_url = from_json_value(value)?,
280                "m.3pid_changes" => self.thirdparty_id_changes = from_json_value(value)?,
281                "m.get_login_token" => self.get_login_token = from_json_value(value)?,
282                "m.profile_fields" | "uk.tcpip.msc4133.profile_fields" => {
283                    self.profile_fields = from_json_value(value)?;
284                }
285                "m.forget_forced_upon_leave" => {
286                    self.forget_forced_upon_leave = from_json_value(value)?;
287                }
288                "m.account_moderation" => {
289                    self.account_moderation = from_json_value(value)?;
290                }
291                #[cfg(feature = "unstable-msc4540")]
292                "org.continuwuity.msc4540.admin" => self.admin = from_json_value(value)?,
293                _ => {
294                    self.custom_capabilities.insert(capability.to_owned(), value);
295                }
296            }
297
298            Ok(())
299        }
300    }
301
302    /// Information about the m.change_password capability
303    #[derive(Clone, Debug, Serialize, Deserialize)]
304    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
305    pub struct ChangePasswordCapability {
306        /// `true` if the user can change their password, `false` otherwise.
307        pub enabled: bool,
308    }
309
310    impl ChangePasswordCapability {
311        /// Creates a new `ChangePasswordCapability` with the given enabled flag.
312        pub fn new(enabled: bool) -> Self {
313            Self { enabled }
314        }
315
316        /// Returns whether all fields have their default value.
317        pub fn is_default(&self) -> bool {
318            self.enabled
319        }
320    }
321
322    impl Default for ChangePasswordCapability {
323        fn default() -> Self {
324            Self { enabled: true }
325        }
326    }
327
328    /// Information about the m.room_versions capability
329    #[derive(Clone, Debug, Serialize, Deserialize)]
330    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
331    pub struct RoomVersionsCapability {
332        /// The default room version the server is using for new rooms.
333        pub default: RoomVersionId,
334
335        /// A detailed description of the room versions the server supports.
336        pub available: BTreeMap<RoomVersionId, RoomVersionStability>,
337    }
338
339    impl RoomVersionsCapability {
340        /// Creates a new `RoomVersionsCapability` with the given default room version ID and room
341        /// version descriptions.
342        pub fn new(
343            default: RoomVersionId,
344            available: BTreeMap<RoomVersionId, RoomVersionStability>,
345        ) -> Self {
346            Self { default, available }
347        }
348
349        /// Returns whether all fields have their default value.
350        pub fn is_default(&self) -> bool {
351            self.default == RoomVersionId::V1
352                && self.available.len() == 1
353                && self
354                    .available
355                    .get(&RoomVersionId::V1)
356                    .map(|stability| *stability == RoomVersionStability::Stable)
357                    .unwrap_or(false)
358        }
359    }
360
361    impl Default for RoomVersionsCapability {
362        fn default() -> Self {
363            Self {
364                default: RoomVersionId::V1,
365                available: btreemap! { RoomVersionId::V1 => RoomVersionStability::Stable },
366            }
367        }
368    }
369
370    /// The stability of a room version.
371    #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
372    #[derive(Clone, StringEnum)]
373    #[ruma_enum(rename_all = "lowercase")]
374    #[non_exhaustive]
375    pub enum RoomVersionStability {
376        /// Support for the given version is stable.
377        Stable,
378
379        /// Support for the given version is unstable.
380        Unstable,
381
382        #[doc(hidden)]
383        _Custom(PrivOwnedStr),
384    }
385
386    /// Information about the `m.set_displayname` capability
387    #[derive(Clone, Debug, Serialize, Deserialize)]
388    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
389    #[deprecated = "Since Matrix 1.16, prefer ProfileFieldsCapability instead."]
390    pub struct SetDisplayNameCapability {
391        /// `true` if the user can change their display name, `false` otherwise.
392        pub enabled: bool,
393    }
394
395    #[allow(deprecated)]
396    impl SetDisplayNameCapability {
397        /// Creates a new `SetDisplayNameCapability` with the given enabled flag.
398        pub fn new(enabled: bool) -> Self {
399            Self { enabled }
400        }
401
402        /// Returns whether all fields have their default value.
403        pub fn is_default(&self) -> bool {
404            self.enabled
405        }
406    }
407
408    #[allow(deprecated)]
409    impl Default for SetDisplayNameCapability {
410        fn default() -> Self {
411            Self { enabled: true }
412        }
413    }
414
415    /// Information about the `m.set_avatar_url` capability
416    #[derive(Clone, Debug, Serialize, Deserialize)]
417    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
418    #[deprecated = "Since Matrix 1.16, prefer ProfileFieldsCapability instead."]
419    pub struct SetAvatarUrlCapability {
420        /// `true` if the user can change their avatar, `false` otherwise.
421        pub enabled: bool,
422    }
423
424    #[allow(deprecated)]
425    impl SetAvatarUrlCapability {
426        /// Creates a new `SetAvatarUrlCapability` with the given enabled flag.
427        pub fn new(enabled: bool) -> Self {
428            Self { enabled }
429        }
430
431        /// Returns whether all fields have their default value.
432        pub fn is_default(&self) -> bool {
433            self.enabled
434        }
435    }
436
437    #[allow(deprecated)]
438    impl Default for SetAvatarUrlCapability {
439        fn default() -> Self {
440            Self { enabled: true }
441        }
442    }
443
444    /// Information about the `m.3pid_changes` capability
445    #[derive(Clone, Debug, Serialize, Deserialize)]
446    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
447    pub struct ThirdPartyIdChangesCapability {
448        /// `true` if the user can change the third-party identifiers associated with their
449        /// account, `false` otherwise.
450        pub enabled: bool,
451    }
452
453    impl ThirdPartyIdChangesCapability {
454        /// Creates a new `ThirdPartyIdChangesCapability` with the given enabled flag.
455        pub fn new(enabled: bool) -> Self {
456            Self { enabled }
457        }
458
459        /// Returns whether all fields have their default value.
460        pub fn is_default(&self) -> bool {
461            self.enabled
462        }
463    }
464
465    impl Default for ThirdPartyIdChangesCapability {
466        fn default() -> Self {
467            Self { enabled: true }
468        }
469    }
470
471    /// Information about the `m.get_login_token` capability.
472    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
473    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
474    pub struct GetLoginTokenCapability {
475        /// Whether the user can request a login token.
476        pub enabled: bool,
477    }
478
479    impl GetLoginTokenCapability {
480        /// Creates a new `GetLoginTokenCapability` with the given enabled flag.
481        pub fn new(enabled: bool) -> Self {
482            Self { enabled }
483        }
484
485        /// Returns whether all fields have their default value.
486        pub fn is_default(&self) -> bool {
487            !self.enabled
488        }
489    }
490
491    /// Information about the `m.profile_fields` capability.
492    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
493    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
494    pub struct ProfileFieldsCapability {
495        /// Whether the user can set extended profile fields.
496        pub enabled: bool,
497
498        /// The fields that can be set by the user.
499        #[serde(skip_serializing_if = "Option::is_none")]
500        pub allowed: Option<Vec<ProfileFieldName>>,
501
502        /// The fields that cannot be set by the user.
503        ///
504        /// This list is ignored if `allowed` is provided.
505        #[serde(skip_serializing_if = "Option::is_none")]
506        pub disallowed: Option<Vec<ProfileFieldName>>,
507    }
508
509    impl ProfileFieldsCapability {
510        /// Creates a new `ProfileFieldsCapability` with the given enabled flag.
511        pub fn new(enabled: bool) -> Self {
512            Self { enabled, allowed: None, disallowed: None }
513        }
514
515        /// Whether the server advertises that the field with the given name can be set.
516        pub fn can_set_field(&self, field: &ProfileFieldName) -> bool {
517            if !self.enabled {
518                return false;
519            }
520
521            if let Some(allowed) = &self.allowed {
522                allowed.contains(field)
523            } else if let Some(disallowed) = &self.disallowed {
524                !disallowed.contains(field)
525            } else {
526                // The default is that any field is allowed.
527                true
528            }
529        }
530    }
531
532    /// Information about the [`m.forget_forced_upon_leave`] capability.
533    ///
534    /// [`m.forget_forced_upon_leave`]: https://spec.matrix.org/v1.19/client-server-api/#mforget_forced_upon_leave-capability
535    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
536    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
537    pub struct ForgetForcedUponLeaveCapability {
538        /// Whether the server will automatically forget any room that the user leaves.
539        ///
540        /// This behavior applies irrespective of whether the user has left the room on their own
541        /// or has been kicked or banned from the room by another user.
542        pub enabled: bool,
543    }
544
545    impl ForgetForcedUponLeaveCapability {
546        /// Creates a new `ForgetForcedUponLeaveCapability` with the given enabled flag.
547        pub fn new(enabled: bool) -> Self {
548            Self { enabled }
549        }
550
551        /// Returns whether all fields have their default value.
552        pub fn is_default(&self) -> bool {
553            !self.enabled
554        }
555    }
556
557    /// Information about the `m.account_moderation` capability.
558    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
559    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
560    pub struct AccountModerationCapability {
561        /// Whether the user can suspend a user via `PUT /admin/suspend/{userId}`.
562        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
563        pub suspend: bool,
564
565        /// Whether the user can lock a user via `PUT /admin/lock/{userId}`.
566        #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
567        pub lock: bool,
568    }
569
570    impl AccountModerationCapability {
571        /// Creates a new `AccountModerationCapability` with the given suspend and lock
572        /// capabilities.
573        pub fn new(suspend: bool, lock: bool) -> Self {
574            Self { suspend, lock }
575        }
576
577        /// Returns whether all fields have their default value.
578        pub fn is_default(&self) -> bool {
579            !self.suspend && !self.lock
580        }
581    }
582
583    /// Information about the `m.admin` capability. ([MSC4540])
584    ///
585    /// [MSC4540]: https://github.com/matrix-org/matrix-spec-proposals/pull/4540
586    #[cfg(feature = "unstable-msc4540")]
587    #[derive(Clone, Debug, Default, Serialize, Deserialize)]
588    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
589    pub struct AdminCapability {
590        /// The scopes the device may request to access administrative functionality.
591        pub allowed_scopes: BTreeSet<OAuthClientScope>,
592    }
593
594    #[cfg(feature = "unstable-msc4540")]
595    impl AdminCapability {
596        /// Create a new [`AdminCapability`].
597        pub fn new(allowed_scopes: BTreeSet<OAuthClientScope>) -> Self {
598            Self { allowed_scopes }
599        }
600
601        /// Returns whether the capability indicates that the authenticated user
602        /// is able to access some administrative functionality.
603        pub fn is_admin(&self) -> bool {
604            !self.allowed_scopes.is_empty()
605        }
606
607        /// Returns whether the admin functionality gated by a particular scope
608        /// is available to the authenticated user.
609        pub fn is_scope_allowed(&self, scope: &OAuthClientScope) -> bool {
610            self.allowed_scopes.contains(scope)
611        }
612
613        /// Returns whether all fields have their default value.
614        fn is_default(&self) -> bool {
615            let Self { allowed_scopes } = self;
616
617            allowed_scopes.is_empty()
618        }
619    }
620
621    #[cfg(test)]
622    mod tests {
623        use ruma_common::RoomVersionId;
624        #[cfg(feature = "unstable-msc4540")]
625        use ruma_common::api::OAuthClientScope;
626        use serde_json::to_value as to_json_value;
627
628        #[allow(deprecated)]
629        use super::{
630            AccountModerationCapability, Capabilities, ChangePasswordCapability,
631            ForgetForcedUponLeaveCapability, GetLoginTokenCapability, ProfileFieldsCapability,
632            RoomVersionStability, RoomVersionsCapability, SetAvatarUrlCapability,
633            SetDisplayNameCapability, ThirdPartyIdChangesCapability,
634        };
635
636        /// Capabilities with every typed field set to a non-default value.
637        #[allow(deprecated)]
638        fn non_default_capabilities() -> Capabilities {
639            Capabilities {
640                change_password: ChangePasswordCapability::new(false),
641                room_versions: RoomVersionsCapability::new(
642                    RoomVersionId::V11,
643                    [(RoomVersionId::V11, RoomVersionStability::Stable)].into(),
644                ),
645                set_displayname: SetDisplayNameCapability::new(false),
646                set_avatar_url: SetAvatarUrlCapability::new(false),
647                thirdparty_id_changes: ThirdPartyIdChangesCapability::new(false),
648                get_login_token: GetLoginTokenCapability::new(true),
649                profile_fields: Some(ProfileFieldsCapability::new(true)),
650                forget_forced_upon_leave: ForgetForcedUponLeaveCapability::new(true),
651                account_moderation: AccountModerationCapability::new(true, true),
652                #[cfg(feature = "unstable-msc4540")]
653                admin: super::AdminCapability::new([OAuthClientScope::ApiFullAccess].into()),
654                custom_capabilities: Default::default(),
655            }
656        }
657
658        #[test]
659        fn get_and_set_cover_typed_fields() {
660            let caps = non_default_capabilities();
661            let serialized = to_json_value(&caps).unwrap();
662            let serialized = serialized.as_object().unwrap();
663
664            let mut copy = Capabilities::new();
665            for (name, value) in serialized {
666                assert_eq!(caps.get(name).as_deref(), Some(value), "get({name})");
667                copy.set(name, value.clone()).unwrap();
668            }
669
670            assert!(copy.custom_capabilities.is_empty(), "{:?}", copy.custom_capabilities);
671            assert_eq!(to_json_value(&copy).unwrap(), to_json_value(&caps).unwrap());
672        }
673
674        #[test]
675        fn serialize_deserialize_capabilities() {
676            for mut caps in [Capabilities::new(), non_default_capabilities()] {
677                caps.set("com.example.custom", serde_json::json!({ "enabled": true })).unwrap();
678                let serialized = to_json_value(&caps).unwrap();
679                let deserialized: Capabilities =
680                    serde_json::from_value(serialized.clone()).unwrap();
681
682                assert_eq!(to_json_value(&deserialized).unwrap(), serialized);
683            }
684        }
685
686        #[test]
687        fn deserialize_stable_and_unstable_profile_fields() {
688            let caps: Capabilities = serde_json::from_value(serde_json::json!({
689                "m.profile_fields": { "enabled": true },
690                "uk.tcpip.msc4133.profile_fields": { "enabled": false },
691                "m.room_versions": { "default": "11", "available": { "11": "stable" } },
692            }))
693            .unwrap();
694
695            assert!(caps.profile_fields.unwrap().enabled);
696            assert_eq!(caps.room_versions.default, RoomVersionId::V11);
697            assert!(caps.custom_capabilities.is_empty());
698        }
699
700        #[test]
701        fn deserialize_unstable_profile_fields() {
702            let caps: Capabilities = serde_json::from_value(serde_json::json!({
703                "uk.tcpip.msc4133.profile_fields": { "enabled": true },
704            }))
705            .unwrap();
706
707            assert!(caps.profile_fields.unwrap().enabled);
708        }
709    }
710}