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/latest/client-server-api/#capabilities-negotiation
7
8use std::{borrow::Cow, collections::BTreeMap};
9
10use maplit::btreemap;
11use ruma_common::{serde::StringEnum, RoomVersionId};
12use serde::{Deserialize, Serialize};
13use serde_json::{from_value as from_json_value, to_value as to_json_value, Value as JsonValue};
14
15use self::iter::{CapabilitiesIter, CapabilityRef};
16use crate::PrivOwnedStr;
17
18pub mod iter;
19pub mod v3;
20
21/// Contains information about all the capabilities that the server supports.
22#[derive(Clone, Debug, Default, Serialize, Deserialize)]
23#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
24pub struct Capabilities {
25    /// Capability to indicate if the user can change their password.
26    #[serde(
27        rename = "m.change_password",
28        default,
29        skip_serializing_if = "ChangePasswordCapability::is_default"
30    )]
31    pub change_password: ChangePasswordCapability,
32
33    /// The room versions the server supports.
34    #[serde(
35        rename = "m.room_versions",
36        default,
37        skip_serializing_if = "RoomVersionsCapability::is_default"
38    )]
39    pub room_versions: RoomVersionsCapability,
40
41    /// Capability to indicate if the user can change their display name.
42    #[serde(
43        rename = "m.set_displayname",
44        default,
45        skip_serializing_if = "SetDisplayNameCapability::is_default"
46    )]
47    pub set_displayname: SetDisplayNameCapability,
48
49    /// Capability to indicate if the user can change their avatar.
50    #[serde(
51        rename = "m.set_avatar_url",
52        default,
53        skip_serializing_if = "SetAvatarUrlCapability::is_default"
54    )]
55    pub set_avatar_url: SetAvatarUrlCapability,
56
57    /// Capability to indicate if the user can change the third-party identifiers associated with
58    /// their account.
59    #[serde(
60        rename = "m.3pid_changes",
61        default,
62        skip_serializing_if = "ThirdPartyIdChangesCapability::is_default"
63    )]
64    pub thirdparty_id_changes: ThirdPartyIdChangesCapability,
65
66    /// Capability to indicate if the user can generate tokens to log further clients into their
67    /// account.
68    #[serde(
69        rename = "m.get_login_token",
70        default,
71        skip_serializing_if = "GetLoginTokenCapability::is_default"
72    )]
73    pub get_login_token: GetLoginTokenCapability,
74
75    /// Any other custom capabilities that the server supports outside of the specification,
76    /// labeled using the Java package naming convention and stored as arbitrary JSON values.
77    #[serde(flatten)]
78    custom_capabilities: BTreeMap<String, JsonValue>,
79}
80
81impl Capabilities {
82    /// Creates empty `Capabilities`.
83    pub fn new() -> Self {
84        Default::default()
85    }
86
87    /// Returns the value of the given capability.
88    ///
89    /// Prefer to use the public fields of `Capabilities` where possible; this method is meant to be
90    /// used for unsupported capabilities only.
91    pub fn get(&self, capability: &str) -> Option<Cow<'_, JsonValue>> {
92        fn serialize<T: Serialize>(cap: &T) -> JsonValue {
93            to_json_value(cap).expect("capability serialization to succeed")
94        }
95
96        match capability {
97            "m.change_password" => Some(Cow::Owned(serialize(&self.change_password))),
98            "m.room_versions" => Some(Cow::Owned(serialize(&self.room_versions))),
99            "m.set_displayname" => Some(Cow::Owned(serialize(&self.set_displayname))),
100            "m.set_avatar_url" => Some(Cow::Owned(serialize(&self.set_avatar_url))),
101            "m.3pid_changes" => Some(Cow::Owned(serialize(&self.thirdparty_id_changes))),
102            "m.get_login_token" => Some(Cow::Owned(serialize(&self.get_login_token))),
103            _ => self.custom_capabilities.get(capability).map(Cow::Borrowed),
104        }
105    }
106
107    /// Sets a capability to the given value.
108    ///
109    /// Prefer to use the public fields of `Capabilities` where possible; this method is meant to be
110    /// used for unsupported capabilities only and does not allow setting arbitrary data for
111    /// supported ones.
112    pub fn set(&mut self, capability: &str, value: JsonValue) -> serde_json::Result<()> {
113        match capability {
114            "m.change_password" => self.change_password = from_json_value(value)?,
115            "m.room_versions" => self.room_versions = from_json_value(value)?,
116            "m.set_displayname" => self.set_displayname = from_json_value(value)?,
117            "m.set_avatar_url" => self.set_avatar_url = from_json_value(value)?,
118            "m.3pid_changes" => self.thirdparty_id_changes = from_json_value(value)?,
119            "m.get_login_token" => self.get_login_token = from_json_value(value)?,
120            _ => {
121                self.custom_capabilities.insert(capability.to_owned(), value);
122            }
123        }
124
125        Ok(())
126    }
127
128    /// Returns an iterator over the capabilities.
129    pub fn iter(&self) -> CapabilitiesIter<'_> {
130        CapabilitiesIter::new(self)
131    }
132}
133
134impl<'a> IntoIterator for &'a Capabilities {
135    type Item = CapabilityRef<'a>;
136    type IntoIter = CapabilitiesIter<'a>;
137
138    fn into_iter(self) -> Self::IntoIter {
139        self.iter()
140    }
141}
142
143/// Information about the m.change_password capability
144#[derive(Clone, Debug, Serialize, Deserialize)]
145#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
146pub struct ChangePasswordCapability {
147    /// `true` if the user can change their password, `false` otherwise.
148    pub enabled: bool,
149}
150
151impl ChangePasswordCapability {
152    /// Creates a new `ChangePasswordCapability` with the given enabled flag.
153    pub fn new(enabled: bool) -> Self {
154        Self { enabled }
155    }
156
157    /// Returns whether all fields have their default value.
158    pub fn is_default(&self) -> bool {
159        self.enabled
160    }
161}
162
163impl Default for ChangePasswordCapability {
164    fn default() -> Self {
165        Self { enabled: true }
166    }
167}
168
169/// Information about the m.room_versions capability
170#[derive(Clone, Debug, Serialize, Deserialize)]
171#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
172pub struct RoomVersionsCapability {
173    /// The default room version the server is using for new rooms.
174    pub default: RoomVersionId,
175
176    /// A detailed description of the room versions the server supports.
177    pub available: BTreeMap<RoomVersionId, RoomVersionStability>,
178}
179
180impl RoomVersionsCapability {
181    /// Creates a new `RoomVersionsCapability` with the given default room version ID and room
182    /// version descriptions.
183    pub fn new(
184        default: RoomVersionId,
185        available: BTreeMap<RoomVersionId, RoomVersionStability>,
186    ) -> Self {
187        Self { default, available }
188    }
189
190    /// Returns whether all fields have their default value.
191    pub fn is_default(&self) -> bool {
192        self.default == RoomVersionId::V1
193            && self.available.len() == 1
194            && self
195                .available
196                .get(&RoomVersionId::V1)
197                .map(|stability| *stability == RoomVersionStability::Stable)
198                .unwrap_or(false)
199    }
200}
201
202impl Default for RoomVersionsCapability {
203    fn default() -> Self {
204        Self {
205            default: RoomVersionId::V1,
206            available: btreemap! { RoomVersionId::V1 => RoomVersionStability::Stable },
207        }
208    }
209}
210
211/// The stability of a room version.
212#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
213#[derive(Clone, PartialEq, Eq, StringEnum)]
214#[ruma_enum(rename_all = "lowercase")]
215#[non_exhaustive]
216pub enum RoomVersionStability {
217    /// Support for the given version is stable.
218    Stable,
219
220    /// Support for the given version is unstable.
221    Unstable,
222
223    #[doc(hidden)]
224    _Custom(PrivOwnedStr),
225}
226
227/// Information about the `m.set_displayname` capability
228#[derive(Clone, Debug, Serialize, Deserialize)]
229#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
230pub struct SetDisplayNameCapability {
231    /// `true` if the user can change their display name, `false` otherwise.
232    pub enabled: bool,
233}
234
235impl SetDisplayNameCapability {
236    /// Creates a new `SetDisplayNameCapability` with the given enabled flag.
237    pub fn new(enabled: bool) -> Self {
238        Self { enabled }
239    }
240
241    /// Returns whether all fields have their default value.
242    pub fn is_default(&self) -> bool {
243        self.enabled
244    }
245}
246
247impl Default for SetDisplayNameCapability {
248    fn default() -> Self {
249        Self { enabled: true }
250    }
251}
252
253/// Information about the `m.set_avatar_url` capability
254#[derive(Clone, Debug, Serialize, Deserialize)]
255#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
256pub struct SetAvatarUrlCapability {
257    /// `true` if the user can change their avatar, `false` otherwise.
258    pub enabled: bool,
259}
260
261impl SetAvatarUrlCapability {
262    /// Creates a new `SetAvatarUrlCapability` with the given enabled flag.
263    pub fn new(enabled: bool) -> Self {
264        Self { enabled }
265    }
266
267    /// Returns whether all fields have their default value.
268    pub fn is_default(&self) -> bool {
269        self.enabled
270    }
271}
272
273impl Default for SetAvatarUrlCapability {
274    fn default() -> Self {
275        Self { enabled: true }
276    }
277}
278
279/// Information about the `m.3pid_changes` capability
280#[derive(Clone, Debug, Serialize, Deserialize)]
281#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
282pub struct ThirdPartyIdChangesCapability {
283    /// `true` if the user can change the third-party identifiers associated with their account,
284    /// `false` otherwise.
285    pub enabled: bool,
286}
287
288impl ThirdPartyIdChangesCapability {
289    /// Creates a new `ThirdPartyIdChangesCapability` with the given enabled flag.
290    pub fn new(enabled: bool) -> Self {
291        Self { enabled }
292    }
293
294    /// Returns whether all fields have their default value.
295    pub fn is_default(&self) -> bool {
296        self.enabled
297    }
298}
299
300impl Default for ThirdPartyIdChangesCapability {
301    fn default() -> Self {
302        Self { enabled: true }
303    }
304}
305
306/// Information about the `m.get_login_token` capability.
307#[derive(Clone, Debug, Default, Serialize, Deserialize)]
308#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
309pub struct GetLoginTokenCapability {
310    /// Whether the user can request a login token.
311    pub enabled: bool,
312}
313
314impl GetLoginTokenCapability {
315    /// Creates a new `GetLoginTokenCapability` with the given enabled flag.
316    pub fn new(enabled: bool) -> Self {
317        Self { enabled }
318    }
319
320    /// Returns whether all fields have their default value.
321    pub fn is_default(&self) -> bool {
322        !self.enabled
323    }
324}
325
326#[cfg(test)]
327mod tests {
328    use std::borrow::Cow;
329
330    use assert_matches2::assert_matches;
331    use serde_json::json;
332
333    use super::Capabilities;
334
335    #[test]
336    fn capabilities_iter() -> serde_json::Result<()> {
337        let mut caps = Capabilities::new();
338        let custom_cap = json!({
339            "key": "value",
340        });
341        caps.set("m.some_random_capability", custom_cap)?;
342        let mut caps_iter = caps.iter();
343
344        let iter_res = caps_iter.next().unwrap();
345        assert_eq!(iter_res.name(), "m.change_password");
346        assert_eq!(iter_res.value(), Cow::Borrowed(&json!({ "enabled": true })));
347
348        let iter_res = caps_iter.next().unwrap();
349        assert_eq!(iter_res.name(), "m.room_versions");
350        assert_eq!(
351            iter_res.value(),
352            Cow::Borrowed(&json!({ "available": { "1": "stable" },"default" :"1" }))
353        );
354
355        let iter_res = caps_iter.next().unwrap();
356        assert_eq!(iter_res.name(), "m.set_displayname");
357        assert_eq!(iter_res.value(), Cow::Borrowed(&json!({ "enabled": true })));
358
359        let iter_res = caps_iter.next().unwrap();
360        assert_eq!(iter_res.name(), "m.set_avatar_url");
361        assert_eq!(iter_res.value(), Cow::Borrowed(&json!({ "enabled": true })));
362
363        let iter_res = caps_iter.next().unwrap();
364        assert_eq!(iter_res.name(), "m.3pid_changes");
365        assert_eq!(iter_res.value(), Cow::Borrowed(&json!({ "enabled": true })));
366
367        let iter_res = caps_iter.next().unwrap();
368        assert_eq!(iter_res.name(), "m.some_random_capability");
369        assert_eq!(iter_res.value(), Cow::Borrowed(&json!({ "key": "value" })));
370
371        assert_matches!(caps_iter.next(), None);
372        Ok(())
373    }
374}