1pub mod v3 {
9 #[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, Serialize};
28 use serde_json::{
29 Value as JsonValue, from_value as from_json_value, to_value as to_json_value,
30 };
31
32 use crate::PrivOwnedStr;
33
34 metadata! {
35 method: GET,
36 rate_limited: true,
37 authentication: AccessToken,
38 history: {
39 1.0 => "/_matrix/client/r0/capabilities",
40 1.1 => "/_matrix/client/v3/capabilities",
41 }
42 }
43
44 #[request]
46 #[derive(Default)]
47 pub struct Request {}
48
49 #[response]
51 pub struct Response {
52 pub capabilities: Capabilities,
54 }
55
56 impl Request {
57 pub fn new() -> Self {
59 Self {}
60 }
61 }
62
63 impl Response {
64 pub fn new(capabilities: Capabilities) -> Self {
66 Self { capabilities }
67 }
68 }
69
70 impl From<Capabilities> for Response {
71 fn from(capabilities: Capabilities) -> Self {
72 Self::new(capabilities)
73 }
74 }
75
76 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
78 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
79 #[allow(deprecated)]
80 pub struct Capabilities {
81 #[serde(
83 rename = "m.change_password",
84 default,
85 skip_serializing_if = "ChangePasswordCapability::is_default"
86 )]
87 pub change_password: ChangePasswordCapability,
88
89 #[serde(
91 rename = "m.room_versions",
92 default,
93 skip_serializing_if = "RoomVersionsCapability::is_default"
94 )]
95 pub room_versions: RoomVersionsCapability,
96
97 #[serde(
99 rename = "m.set_displayname",
100 default,
101 skip_serializing_if = "SetDisplayNameCapability::is_default"
102 )]
103 #[deprecated = "Since Matrix 1.16, prefer profile_fields if it is set."]
104 pub set_displayname: SetDisplayNameCapability,
105
106 #[serde(
108 rename = "m.set_avatar_url",
109 default,
110 skip_serializing_if = "SetAvatarUrlCapability::is_default"
111 )]
112 #[deprecated = "Since Matrix 1.16, prefer profile_fields if it is set."]
113 pub set_avatar_url: SetAvatarUrlCapability,
114
115 #[serde(
118 rename = "m.3pid_changes",
119 default,
120 skip_serializing_if = "ThirdPartyIdChangesCapability::is_default"
121 )]
122 pub thirdparty_id_changes: ThirdPartyIdChangesCapability,
123
124 #[serde(
127 rename = "m.get_login_token",
128 default,
129 skip_serializing_if = "GetLoginTokenCapability::is_default"
130 )]
131 pub get_login_token: GetLoginTokenCapability,
132
133 #[serde(
135 rename = "m.profile_fields",
136 alias = "uk.tcpip.msc4133.profile_fields",
137 skip_serializing_if = "Option::is_none"
138 )]
139 pub profile_fields: Option<ProfileFieldsCapability>,
140
141 #[serde(
143 rename = "m.forget_forced_upon_leave",
144 default,
145 skip_serializing_if = "ForgetForcedUponLeaveCapability::is_default"
146 )]
147 pub forget_forced_upon_leave: ForgetForcedUponLeaveCapability,
148
149 #[serde(
154 rename = "m.account_moderation",
155 default,
156 skip_serializing_if = "AccountModerationCapability::is_default"
157 )]
158 pub account_moderation: AccountModerationCapability,
159
160 #[cfg(feature = "unstable-msc4540")]
164 #[serde(
165 rename = "org.continuwuity.msc4540.admin",
166 default,
167 skip_serializing_if = "AdminCapability::is_default"
168 )]
169 pub admin: AdminCapability,
170
171 #[serde(flatten)]
174 custom_capabilities: BTreeMap<String, JsonValue>,
175 }
176
177 impl Capabilities {
178 pub fn new() -> Self {
180 Default::default()
181 }
182
183 pub fn get(&self, capability: &str) -> Option<Cow<'_, JsonValue>> {
188 fn serialize<T: Serialize>(cap: &T) -> JsonValue {
189 to_json_value(cap).expect("capability serialization to succeed")
190 }
191
192 match capability {
193 "m.change_password" => Some(Cow::Owned(serialize(&self.change_password))),
194 "m.room_versions" => Some(Cow::Owned(serialize(&self.room_versions))),
195 #[allow(deprecated)]
196 "m.set_displayname" => Some(Cow::Owned(serialize(&self.set_displayname))),
197 #[allow(deprecated)]
198 "m.set_avatar_url" => Some(Cow::Owned(serialize(&self.set_avatar_url))),
199 "m.3pid_changes" => Some(Cow::Owned(serialize(&self.thirdparty_id_changes))),
200 "m.get_login_token" => Some(Cow::Owned(serialize(&self.get_login_token))),
201 "m.profile_fields" | "uk.tcpip.msc4133.profile_fields" => {
202 self.profile_fields.as_ref().map(|cap| Cow::Owned(serialize(cap)))
203 }
204 "m.forget_forced_upon_leave" => {
205 Some(Cow::Owned(serialize(&self.forget_forced_upon_leave)))
206 }
207 "m.account_moderation" => Some(Cow::Owned(serialize(&self.account_moderation))),
208 #[cfg(feature = "unstable-msc4540")]
209 "org.continuwuity.msc4540.admin" => Some(Cow::Owned(serialize(&self.admin))),
210 _ => self.custom_capabilities.get(capability).map(Cow::Borrowed),
211 }
212 }
213
214 pub fn set(&mut self, capability: &str, value: JsonValue) -> serde_json::Result<()> {
220 match capability {
221 "m.change_password" => self.change_password = from_json_value(value)?,
222 "m.room_versions" => self.room_versions = from_json_value(value)?,
223 #[allow(deprecated)]
224 "m.set_displayname" => self.set_displayname = from_json_value(value)?,
225 #[allow(deprecated)]
226 "m.set_avatar_url" => self.set_avatar_url = from_json_value(value)?,
227 "m.3pid_changes" => self.thirdparty_id_changes = from_json_value(value)?,
228 "m.get_login_token" => self.get_login_token = from_json_value(value)?,
229 "m.profile_fields" | "uk.tcpip.msc4133.profile_fields" => {
230 self.profile_fields = from_json_value(value)?;
231 }
232 "m.forget_forced_upon_leave" => {
233 self.forget_forced_upon_leave = from_json_value(value)?;
234 }
235 "m.account_moderation" => {
236 self.account_moderation = from_json_value(value)?;
237 }
238 #[cfg(feature = "unstable-msc4540")]
239 "org.continuwuity.msc4540.admin" => self.admin = from_json_value(value)?,
240 _ => {
241 self.custom_capabilities.insert(capability.to_owned(), value);
242 }
243 }
244
245 Ok(())
246 }
247 }
248
249 #[derive(Clone, Debug, Serialize, Deserialize)]
251 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
252 pub struct ChangePasswordCapability {
253 pub enabled: bool,
255 }
256
257 impl ChangePasswordCapability {
258 pub fn new(enabled: bool) -> Self {
260 Self { enabled }
261 }
262
263 pub fn is_default(&self) -> bool {
265 self.enabled
266 }
267 }
268
269 impl Default for ChangePasswordCapability {
270 fn default() -> Self {
271 Self { enabled: true }
272 }
273 }
274
275 #[derive(Clone, Debug, Serialize, Deserialize)]
277 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
278 pub struct RoomVersionsCapability {
279 pub default: RoomVersionId,
281
282 pub available: BTreeMap<RoomVersionId, RoomVersionStability>,
284 }
285
286 impl RoomVersionsCapability {
287 pub fn new(
290 default: RoomVersionId,
291 available: BTreeMap<RoomVersionId, RoomVersionStability>,
292 ) -> Self {
293 Self { default, available }
294 }
295
296 pub fn is_default(&self) -> bool {
298 self.default == RoomVersionId::V1
299 && self.available.len() == 1
300 && self
301 .available
302 .get(&RoomVersionId::V1)
303 .map(|stability| *stability == RoomVersionStability::Stable)
304 .unwrap_or(false)
305 }
306 }
307
308 impl Default for RoomVersionsCapability {
309 fn default() -> Self {
310 Self {
311 default: RoomVersionId::V1,
312 available: btreemap! { RoomVersionId::V1 => RoomVersionStability::Stable },
313 }
314 }
315 }
316
317 #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
319 #[derive(Clone, StringEnum)]
320 #[ruma_enum(rename_all = "lowercase")]
321 #[non_exhaustive]
322 pub enum RoomVersionStability {
323 Stable,
325
326 Unstable,
328
329 #[doc(hidden)]
330 _Custom(PrivOwnedStr),
331 }
332
333 #[derive(Clone, Debug, Serialize, Deserialize)]
335 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
336 #[deprecated = "Since Matrix 1.16, prefer ProfileFieldsCapability instead."]
337 pub struct SetDisplayNameCapability {
338 pub enabled: bool,
340 }
341
342 #[allow(deprecated)]
343 impl SetDisplayNameCapability {
344 pub fn new(enabled: bool) -> Self {
346 Self { enabled }
347 }
348
349 pub fn is_default(&self) -> bool {
351 self.enabled
352 }
353 }
354
355 #[allow(deprecated)]
356 impl Default for SetDisplayNameCapability {
357 fn default() -> Self {
358 Self { enabled: true }
359 }
360 }
361
362 #[derive(Clone, Debug, Serialize, Deserialize)]
364 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
365 #[deprecated = "Since Matrix 1.16, prefer ProfileFieldsCapability instead."]
366 pub struct SetAvatarUrlCapability {
367 pub enabled: bool,
369 }
370
371 #[allow(deprecated)]
372 impl SetAvatarUrlCapability {
373 pub fn new(enabled: bool) -> Self {
375 Self { enabled }
376 }
377
378 pub fn is_default(&self) -> bool {
380 self.enabled
381 }
382 }
383
384 #[allow(deprecated)]
385 impl Default for SetAvatarUrlCapability {
386 fn default() -> Self {
387 Self { enabled: true }
388 }
389 }
390
391 #[derive(Clone, Debug, Serialize, Deserialize)]
393 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
394 pub struct ThirdPartyIdChangesCapability {
395 pub enabled: bool,
398 }
399
400 impl ThirdPartyIdChangesCapability {
401 pub fn new(enabled: bool) -> Self {
403 Self { enabled }
404 }
405
406 pub fn is_default(&self) -> bool {
408 self.enabled
409 }
410 }
411
412 impl Default for ThirdPartyIdChangesCapability {
413 fn default() -> Self {
414 Self { enabled: true }
415 }
416 }
417
418 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
420 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
421 pub struct GetLoginTokenCapability {
422 pub enabled: bool,
424 }
425
426 impl GetLoginTokenCapability {
427 pub fn new(enabled: bool) -> Self {
429 Self { enabled }
430 }
431
432 pub fn is_default(&self) -> bool {
434 !self.enabled
435 }
436 }
437
438 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
440 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
441 pub struct ProfileFieldsCapability {
442 pub enabled: bool,
444
445 #[serde(skip_serializing_if = "Option::is_none")]
447 pub allowed: Option<Vec<ProfileFieldName>>,
448
449 #[serde(skip_serializing_if = "Option::is_none")]
453 pub disallowed: Option<Vec<ProfileFieldName>>,
454 }
455
456 impl ProfileFieldsCapability {
457 pub fn new(enabled: bool) -> Self {
459 Self { enabled, allowed: None, disallowed: None }
460 }
461
462 pub fn can_set_field(&self, field: &ProfileFieldName) -> bool {
464 if !self.enabled {
465 return false;
466 }
467
468 if let Some(allowed) = &self.allowed {
469 allowed.contains(field)
470 } else if let Some(disallowed) = &self.disallowed {
471 !disallowed.contains(field)
472 } else {
473 true
475 }
476 }
477 }
478
479 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
483 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
484 pub struct ForgetForcedUponLeaveCapability {
485 pub enabled: bool,
490 }
491
492 impl ForgetForcedUponLeaveCapability {
493 pub fn new(enabled: bool) -> Self {
495 Self { enabled }
496 }
497
498 pub fn is_default(&self) -> bool {
500 !self.enabled
501 }
502 }
503
504 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
506 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
507 pub struct AccountModerationCapability {
508 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
510 pub suspend: bool,
511
512 #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
514 pub lock: bool,
515 }
516
517 impl AccountModerationCapability {
518 pub fn new(suspend: bool, lock: bool) -> Self {
521 Self { suspend, lock }
522 }
523
524 pub fn is_default(&self) -> bool {
526 !self.suspend && !self.lock
527 }
528 }
529
530 #[cfg(feature = "unstable-msc4540")]
534 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
535 #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
536 pub struct AdminCapability {
537 pub allowed_scopes: BTreeSet<OAuthClientScope>,
539 }
540
541 #[cfg(feature = "unstable-msc4540")]
542 impl AdminCapability {
543 pub fn new(allowed_scopes: BTreeSet<OAuthClientScope>) -> Self {
545 Self { allowed_scopes }
546 }
547
548 pub fn is_admin(&self) -> bool {
551 !self.allowed_scopes.is_empty()
552 }
553
554 pub fn is_scope_allowed(&self, scope: &OAuthClientScope) -> bool {
557 self.allowed_scopes.contains(scope)
558 }
559
560 fn is_default(&self) -> bool {
562 let Self { allowed_scopes } = self;
563
564 allowed_scopes.is_empty()
565 }
566 }
567
568 #[cfg(test)]
569 mod tests {
570 use ruma_common::RoomVersionId;
571 #[cfg(feature = "unstable-msc4540")]
572 use ruma_common::api::OAuthClientScope;
573 use serde_json::to_value as to_json_value;
574
575 #[allow(deprecated)]
576 use super::{
577 AccountModerationCapability, Capabilities, ChangePasswordCapability,
578 ForgetForcedUponLeaveCapability, GetLoginTokenCapability, ProfileFieldsCapability,
579 RoomVersionStability, RoomVersionsCapability, SetAvatarUrlCapability,
580 SetDisplayNameCapability, ThirdPartyIdChangesCapability,
581 };
582
583 #[allow(deprecated)]
585 fn non_default_capabilities() -> Capabilities {
586 Capabilities {
587 change_password: ChangePasswordCapability::new(false),
588 room_versions: RoomVersionsCapability::new(
589 RoomVersionId::V11,
590 [(RoomVersionId::V11, RoomVersionStability::Stable)].into(),
591 ),
592 set_displayname: SetDisplayNameCapability::new(false),
593 set_avatar_url: SetAvatarUrlCapability::new(false),
594 thirdparty_id_changes: ThirdPartyIdChangesCapability::new(false),
595 get_login_token: GetLoginTokenCapability::new(true),
596 profile_fields: Some(ProfileFieldsCapability::new(true)),
597 forget_forced_upon_leave: ForgetForcedUponLeaveCapability::new(true),
598 account_moderation: AccountModerationCapability::new(true, true),
599 #[cfg(feature = "unstable-msc4540")]
600 admin: super::AdminCapability::new([OAuthClientScope::ApiFullAccess].into()),
601 custom_capabilities: Default::default(),
602 }
603 }
604
605 #[test]
606 fn get_and_set_cover_typed_fields() {
607 let caps = non_default_capabilities();
608 let serialized = to_json_value(&caps).unwrap();
609 let serialized = serialized.as_object().unwrap();
610
611 let mut copy = Capabilities::new();
612 for (name, value) in serialized {
613 assert_eq!(caps.get(name).as_deref(), Some(value), "get({name})");
614 copy.set(name, value.clone()).unwrap();
615 }
616
617 assert!(copy.custom_capabilities.is_empty(), "{:?}", copy.custom_capabilities);
618 assert_eq!(to_json_value(©).unwrap(), to_json_value(&caps).unwrap());
619 }
620 }
621}