feat: Improve getting homeserver capabilities

This extracts the `/capabilities` logic to its own `HomeserverCapabilities` component in the SDK that can be manually asked to fetch, cache locally and return these capabilities.
This commit is contained in:
Jorge Martín
2026-03-25 13:07:58 +01:00
committed by Jorge Martin Espinosa
parent ca91b6a278
commit 5d2eab119d
13 changed files with 434 additions and 33 deletions
+2
View File
@@ -47,6 +47,8 @@ All notable changes to this project will be documented in this file.
accepts a `SyncListenerV2` callback that receives a `SyncResponseV2`
after each successful sync.
([#6359](https://github.com/matrix-org/matrix-rust-sdk/pull/6359))
- Added `HomeserverCapabilities` and `Client::homeserver_capabilities()` to get the capabilities
of the homeserver. ([#6371](https://github.com/matrix-org/matrix-rust-sdk/pull/6371))
- Expose `Room.send_state_event_raw()` for sending arbitrary state events
through the FFI layer.
([#6350](https://github.com/matrix-org/matrix-rust-sdk/pull/6350))
+72
View File
@@ -2154,6 +2154,10 @@ impl Client {
}
}))))
}
pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
HomeserverCapabilities::new(self.inner.homeserver_capabilities())
}
}
#[cfg(feature = "experimental-element-recent-emojis")]
@@ -3039,6 +3043,74 @@ impl From<matrix_sdk::StoreSizes> for StoreSizes {
}
}
#[derive(uniffi::Object)]
pub struct HomeserverCapabilities {
inner: matrix_sdk::HomeserverCapabilities,
}
impl HomeserverCapabilities {
pub(crate) fn new(capabilities: matrix_sdk::HomeserverCapabilities) -> Self {
Self { inner: capabilities }
}
}
#[matrix_sdk_ffi_macros::export]
impl HomeserverCapabilities {
pub async fn refresh(&self) -> Result<(), ClientError> {
Ok(self.inner.refresh().await?)
}
pub async fn can_change_password(&self) -> Result<bool, ClientError> {
Ok(self.inner.can_change_password().await?)
}
pub async fn can_change_displayname(&self) -> Result<bool, ClientError> {
Ok(self.inner.can_change_displayname().await?)
}
pub async fn can_change_avatar(&self) -> Result<bool, ClientError> {
Ok(self.inner.can_change_avatar().await?)
}
pub async fn can_change_thirdparty_ids(&self) -> Result<bool, ClientError> {
Ok(self.inner.can_change_thirdparty_ids().await?)
}
pub async fn can_get_login_token(&self) -> Result<bool, ClientError> {
Ok(self.inner.can_get_login_token().await?)
}
pub async fn extended_profile_fields(&self) -> Result<ExtendedProfileFields, ClientError> {
let profile_fields = self.inner.extended_profile_fields().await?;
Ok(ExtendedProfileFields {
enabled: profile_fields.enabled,
allowed: profile_fields
.allowed
.unwrap_or_default()
.iter()
.map(ToString::to_string)
.collect(),
disallowed: profile_fields
.disallowed
.unwrap_or_default()
.iter()
.map(ToString::to_string)
.collect(),
})
}
pub async fn forgets_room_when_leaving(&self) -> Result<bool, ClientError> {
Ok(self.inner.forgets_room_when_leaving().await?)
}
}
#[derive(uniffi::Record)]
pub struct ExtendedProfileFields {
pub enabled: bool,
pub allowed: Vec<String>,
pub disallowed: Vec<String>,
}
#[cfg(test)]
mod tests {
use ruma::{
@@ -24,6 +24,7 @@ use matrix_sdk_common::{ROOM_VERSION_FALLBACK, ROOM_VERSION_RULES_FALLBACK};
use ruma::{
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri,
OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
api::client::discovery::get_capabilities::v3::Capabilities,
canonical_json::{RedactedBecause, redact},
events::{
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnyStrippedStateEvent,
@@ -92,6 +93,7 @@ struct MemoryStoreInner {
seen_knock_requests: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, OwnedUserId>>,
thread_subscriptions: BTreeMap<OwnedRoomId, BTreeMap<OwnedEventId, StoredThreadSubscription>>,
thread_subscriptions_catchup_tokens: Option<Vec<ThreadSubscriptionCatchupToken>>,
homeserver_capabilities: Option<Capabilities>,
}
/// In-memory, non-persistent implementation of the `StateStore`.
@@ -195,6 +197,10 @@ impl StateStore for MemoryStore {
.thread_subscriptions_catchup_tokens
.clone()
.map(StateStoreDataValue::ThreadSubscriptionsCatchupTokens),
StateStoreDataKey::HomeserverCapabilities => inner
.homeserver_capabilities
.clone()
.map(StateStoreDataValue::HomeserverCapabilities),
})
}
@@ -270,6 +276,13 @@ impl StateStore for MemoryStore {
"Session data is not a list of thread subscription catchup tokens",
));
}
StateStoreDataKey::HomeserverCapabilities => {
inner.homeserver_capabilities = Some(
value
.into_homeserver_capabilities()
.expect("Session data is not a homeserver capabilities"),
);
}
}
Ok(())
@@ -304,6 +317,7 @@ impl StateStore for MemoryStore {
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
inner.thread_subscriptions_catchup_tokens = None;
}
StateStoreDataKey::HomeserverCapabilities => inner.homeserver_capabilities = None,
}
Ok(())
}
+20 -2
View File
@@ -28,8 +28,11 @@ use ruma::{
OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
api::{
SupportedVersions,
client::discovery::discover_homeserver::{
self, HomeserverInfo, IdentityServerInfo, RtcFocusInfo, TileServerInfo,
client::discovery::{
discover_homeserver::{
self, HomeserverInfo, IdentityServerInfo, RtcFocusInfo, TileServerInfo,
},
get_capabilities::v3::Capabilities,
},
},
events::{
@@ -1172,6 +1175,9 @@ pub enum StateStoreDataValue {
/// See documentation of [`ThreadSubscriptionCatchupToken`] for more
/// details.
ThreadSubscriptionsCatchupTokens(Vec<ThreadSubscriptionCatchupToken>),
/// The capabilities the homeserver supports or disables.
HomeserverCapabilities(Capabilities),
}
/// Tokens to use when catching up on thread subscriptions.
@@ -1373,6 +1379,12 @@ impl StateStoreDataValue {
) -> Option<Vec<ThreadSubscriptionCatchupToken>> {
as_variant!(self, Self::ThreadSubscriptionsCatchupTokens)
}
/// Get this value if it is the data for the capabilities the homeserver
/// supports or disables.
pub fn into_homeserver_capabilities(self) -> Option<Capabilities> {
as_variant!(self, Self::HomeserverCapabilities)
}
}
/// A key for key-value data.
@@ -1415,6 +1427,9 @@ pub enum StateStoreDataKey<'a> {
/// A list of thread subscriptions catchup tokens.
ThreadSubscriptionsCatchupTokens,
/// A list of capabilities that the homeserver supports.
HomeserverCapabilities,
}
impl StateStoreDataKey<'_> {
@@ -1460,6 +1475,9 @@ impl StateStoreDataKey<'_> {
/// [`ThreadSubscriptionsCatchupTokens`][Self::ThreadSubscriptionsCatchupTokens] variant.
pub const THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS: &'static str =
"thread_subscriptions_catchup_tokens";
/// Key prefix to use for the homeserver's [`Capabilities`].
pub const HOMESERVER_CAPABILITIES: &'static str = "homeserver_capabilities";
}
/// Compare two thread subscription changes bump stamps, given a fixed room and
@@ -42,6 +42,7 @@ use matrix_sdk_store_encryption::{Error as EncryptionError, StoreCipher};
use ruma::{
CanonicalJsonObject, EventId, MilliSecondsSinceUnixEpoch, OwnedEventId, OwnedMxcUri,
OwnedRoomId, OwnedTransactionId, OwnedUserId, RoomId, TransactionId, UserId,
api::client::discovery::get_capabilities::v3::Capabilities,
canonical_json::{RedactedBecause, redact},
events::{
AnyGlobalAccountDataEvent, AnyRoomAccountDataEvent, AnySyncStateEvent,
@@ -506,6 +507,9 @@ impl IndexeddbStateStore {
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
self.encode_key(keys::KV, StateStoreDataKey::THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS)
}
StateStoreDataKey::HomeserverCapabilities => {
self.encode_key(keys::KV, StateStoreDataKey::HOMESERVER_CAPABILITIES)
}
}
}
}
@@ -668,6 +672,10 @@ impl_state_store!({
.map(|f| self.deserialize_value::<Vec<ThreadSubscriptionCatchupToken>>(&f))
.transpose()?
.map(StateStoreDataValue::ThreadSubscriptionsCatchupTokens),
StateStoreDataKey::HomeserverCapabilities => value
.map(|f| self.deserialize_value::<Capabilities>(&f))
.transpose()?
.map(StateStoreDataValue::HomeserverCapabilities),
};
Ok(value)
@@ -719,6 +727,11 @@ impl_state_store!({
.into_thread_subscriptions_catchup_tokens()
.expect("Session data is not a list of thread subscription catchup tokens"),
),
StateStoreDataKey::HomeserverCapabilities => self.serialize_value(
&value
.into_homeserver_capabilities()
.expect("Session data is not a homeserver capabilities"),
),
};
let tx = self.inner.transaction(keys::KV).with_mode(TransactionMode::Readwrite).build()?;
@@ -515,6 +515,9 @@ impl SqliteStateStore {
StateStoreDataKey::ThreadSubscriptionsCatchupTokens => {
Cow::Borrowed(StateStoreDataKey::THREAD_SUBSCRIPTIONS_CATCHUP_TOKENS)
}
StateStoreDataKey::HomeserverCapabilities => {
Cow::Borrowed(StateStoreDataKey::HOMESERVER_CAPABILITIES)
}
};
self.encode_key(keys::KV_BLOB, &*key_s)
@@ -1161,6 +1164,9 @@ impl StateStore for SqliteStateStore {
self.deserialize_value(&data)?,
)
}
StateStoreDataKey::HomeserverCapabilities => {
StateStoreDataValue::HomeserverCapabilities(self.deserialize_value(&data)?)
}
})
})
.transpose()
@@ -1211,6 +1217,11 @@ impl StateStore for SqliteStateStore {
.into_thread_subscriptions_catchup_tokens()
.expect("Session data is not a list of thread subscription catchup tokens"),
)?,
StateStoreDataKey::HomeserverCapabilities => self.serialize_value(
&value
.into_homeserver_capabilities()
.expect("Session data is not the homeserver capabilities"),
)?,
};
self.write()
+3
View File
@@ -8,6 +8,9 @@ All notable changes to this project will be documented in this file.
### Features
- [**breaking**] Added `HomeserverCapabilities` and `Client::homeserver_capabilities()` to get the capabilities
of the homeserver. This replaces `Client::get_capabilities()`.
([#6371](https://github.com/matrix-org/matrix-rust-sdk/pull/6371))
- [**breaking**] `matrix_sdk::error::Error` has a new variant `Timeout` which occurs when
a cross-signing reset does not succeed after some period of time.
([#6325](https://github.com/matrix-org/matrix-rust-sdk/pull/6325))
+4 -4
View File
@@ -437,8 +437,8 @@ impl Account {
/// Set the given field of our own user's profile.
///
/// [`Client::get_capabilities()`] should be called first to check it the
/// field can be set on the homeserver.
/// [`Client::homeserver_capabilities()`] should be called first to check it
/// the field can be set on the homeserver.
///
/// # Arguments
///
@@ -457,8 +457,8 @@ impl Account {
/// Delete the given field of our own user's profile.
///
/// [`Client::get_capabilities()`] should be called first to check it the
/// field can be modified on the homeserver.
/// [`Client::homeserver_capabilities()`] should be called first to check it
/// the field can be modified on the homeserver.
///
/// # Arguments
///
@@ -0,0 +1,258 @@
use matrix_sdk_base::{StateStoreDataKey, StateStoreDataValue};
use ruma::api::client::{
discovery::{
get_capabilities,
get_capabilities::v3::{Capabilities, ProfileFieldsCapability},
},
profile::ProfileFieldName,
};
use tracing::warn;
use crate::{Client, HttpResult};
/// Helper to check what [`Capabilities`] are supported by the homeserver.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#capabilities-negotiation>
#[derive(Debug, Clone)]
pub struct HomeserverCapabilities {
client: Client,
}
impl HomeserverCapabilities {
/// Creates a new [`HomeserverCapabilities`] instance.
pub fn new(client: Client) -> Self {
Self { client }
}
/// Forces a refresh of the cached value using the `/capabilities` endpoint.
pub async fn refresh(&self) -> crate::Result<()> {
self.get_and_cache_remote_capabilities().await?;
Ok(())
}
/// Returns whether the user can change their password or not.
pub async fn can_change_password(&self) -> crate::Result<bool> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
Ok(capabilities.change_password.enabled)
}
/// Returns whether the user can change their display name or not.
///
/// This will first check the `m.profile_fields` capability and use it if
/// present, or fall back to `m.set_displayname` otherwise.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mset_displayname-capability>
pub async fn can_change_displayname(&self) -> crate::Result<bool> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
if let Some(profile_fields) = capabilities.profile_fields
&& profile_fields.enabled
{
let allowed = profile_fields.allowed.unwrap_or_default();
let disallowed = profile_fields.disallowed.unwrap_or_default();
return Ok(allowed.contains(&ProfileFieldName::DisplayName)
|| !disallowed.contains(&ProfileFieldName::DisplayName));
}
#[allow(deprecated)]
Ok(capabilities.set_displayname.enabled)
}
/// Returns whether the user can change their avatar or not.
///
/// This will first check the `m.profile_fields` capability and use it if
/// present, or fall back to `m.set_avatar_url` otherwise.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mset_avatar_url-capability>
pub async fn can_change_avatar(&self) -> crate::Result<bool> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
if let Some(profile_fields) = capabilities.profile_fields
&& profile_fields.enabled
{
let allowed = profile_fields.allowed.unwrap_or_default();
let disallowed = profile_fields.disallowed.unwrap_or_default();
return Ok(allowed.contains(&ProfileFieldName::AvatarUrl)
|| !disallowed.contains(&ProfileFieldName::AvatarUrl));
}
#[allow(deprecated)]
Ok(capabilities.set_avatar_url.enabled)
}
/// Returns whether the user can add, remove, or change 3PID associations on
/// their account.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#m3pid_changes-capability>
pub async fn can_change_thirdparty_ids(&self) -> crate::Result<bool> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
Ok(capabilities.thirdparty_id_changes.enabled)
}
/// Returns whether the user is able to use `POST /login/get_token` to
/// generate single-use, time-limited tokens to log unauthenticated
/// clients into their account.
///
/// When not listed, clients SHOULD assume the user is unable to generate
/// tokens.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mget_login_token-capability>
pub async fn can_get_login_token(&self) -> crate::Result<bool> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
Ok(capabilities.get_login_token.enabled)
}
/// Returns which profile fields the user is able to change.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mprofile_fields-capability>
pub async fn extended_profile_fields(&self) -> crate::Result<ProfileFieldsCapability> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
if let Some(profile_fields) = capabilities.profile_fields {
return Ok(profile_fields);
}
Ok(ProfileFieldsCapability::new(false))
}
/// Returns whether or not the server automatically forgets rooms which the
/// user has left.
///
/// Spec: <https://spec.matrix.org/latest/client-server-api/#mforget_forced_upon_leave-capability>
pub async fn forgets_room_when_leaving(&self) -> crate::Result<bool> {
let capabilities = self.load_or_fetch_homeserver_capabilities().await?;
Ok(capabilities.forget_forced_upon_leave.enabled)
}
/// Gets the supported [`Capabilities`] either from the local cache or from
/// the homeserver using the `/capabilities` endpoint if the data is not
/// cached.
///
/// To ensure you get updated values, you should call [`Self::refresh`]
/// instead.
async fn load_or_fetch_homeserver_capabilities(&self) -> crate::Result<Capabilities> {
match self.client.state_store().get_kv_data(StateStoreDataKey::HomeserverCapabilities).await
{
Ok(Some(stored)) => {
if let Some(capabilities) = stored.into_homeserver_capabilities() {
return Ok(capabilities);
}
}
Ok(None) => {
// fallthrough: cache is empty
}
Err(err) => {
warn!("error when loading cached homeserver capabilities: {err}");
// fallthrough to network.
}
}
Ok(self.get_and_cache_remote_capabilities().await?)
}
/// Gets and caches the capabilities of the homeserver.
async fn get_and_cache_remote_capabilities(&self) -> HttpResult<Capabilities> {
let res = self.client.send(get_capabilities::v3::Request::new()).await?;
if let Err(err) = self
.client
.state_store()
.set_kv_data(
StateStoreDataKey::HomeserverCapabilities,
StateStoreDataValue::HomeserverCapabilities(res.capabilities.clone()),
)
.await
{
warn!("error when caching homeserver capabilities: {err}");
}
Ok(res.capabilities)
}
}
#[cfg(all(not(target_family = "wasm"), test))]
mod tests {
use matrix_sdk_test::async_test;
use super::*;
use crate::test_utils::mocks::MatrixMockServer;
#[async_test]
async fn test_refresh_always_updates_capabilities() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
// Set the expected capabilities to something we can check
let mut expected_capabilities = Capabilities::default();
expected_capabilities.change_password.enabled = true;
server
.mock_get_homeserver_capabilities()
.ok_with_capabilities(expected_capabilities)
.mock_once()
.mount()
.await;
// Refresh the capabilities
let capabilities = client.homeserver_capabilities();
capabilities.refresh().await.expect("refreshing capabilities failed");
// Check the values we get are updated
assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
let mut expected_capabilities = Capabilities::default();
expected_capabilities.change_password.enabled = false;
server
.mock_get_homeserver_capabilities()
.ok_with_capabilities(expected_capabilities)
.mock_once()
.mount()
.await;
// Check the values we get are not updated without a refresh, they're loaded
// from the cache
assert!(capabilities.can_change_password().await.expect("checking capabilities failed"));
// Do another refresh to make sure we get the updated values
capabilities.refresh().await.expect("refreshing capabilities failed");
// Check the values we get are updated
assert!(!capabilities.can_change_password().await.expect("checking capabilities failed"));
}
#[async_test]
async fn test_get_functions_refresh_the_data_if_not_available_or_use_cache_if_available() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
// Set the expected capabilities to something we can check
let mut expected_capabilities = Capabilities::default();
let mut profile_fields = ProfileFieldsCapability::new(true);
profile_fields.allowed = Some(vec![ProfileFieldName::DisplayName]);
expected_capabilities.profile_fields = Some(profile_fields);
server
.mock_get_homeserver_capabilities()
.ok_with_capabilities(expected_capabilities)
// Ensure it's called just once
.mock_once()
.mount()
.await;
// Refresh the capabilities
let capabilities = client.homeserver_capabilities();
// Check the values we get are updated
assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
// Now revert the previous mock so we can check we're getting the cached value
// instead of this one
let mut expected_capabilities = Capabilities::default();
let mut profile_fields = ProfileFieldsCapability::new(true);
profile_fields.disallowed = Some(vec![ProfileFieldName::DisplayName]);
expected_capabilities.profile_fields = Some(profile_fields);
server
.mock_get_homeserver_capabilities()
.ok_with_capabilities(expected_capabilities)
// Ensure it's not called, since we'd be using the cached values instead
.never()
.mount()
.await;
// Check the values we get are not updated without a refresh, they're loaded
// from the cache
assert!(capabilities.can_change_displayname().await.expect("checking capabilities failed"));
}
}
+9 -26
View File
@@ -59,7 +59,6 @@ use ruma::{
directory::{get_public_rooms, get_public_rooms_filtered},
discovery::{
discover_homeserver::{self, RtcFocusInfo},
get_capabilities::{self, v3::Capabilities},
get_supported_versions,
},
error::{ErrorKind, UnknownTokenErrorData},
@@ -98,7 +97,10 @@ use crate::{
AuthCtx, AuthData, ReloadSessionCallback, SaveSessionCallback, matrix::MatrixAuth,
oauth::OAuth,
},
client::thread_subscriptions::ThreadSubscriptionCatchup,
client::{
homeserver_capabilities::HomeserverCapabilities,
thread_subscriptions::ThreadSubscriptionCatchup,
},
config::{RequestConfig, SyncToken},
deduplicating_handler::DeduplicatingHandler,
error::HttpResult,
@@ -129,6 +131,7 @@ use crate::{
mod builder;
pub(crate) mod caches;
pub(crate) mod futures;
pub(crate) mod homeserver_capabilities;
pub(crate) mod thread_subscriptions;
pub use self::builder::{ClientBuildError, ClientBuilder, sanitize_server_name};
@@ -567,30 +570,10 @@ impl Client {
Ok(())
}
/// Get the capabilities of the homeserver.
///
/// This method should be used to check what features are supported by the
/// homeserver.
///
/// # Examples
///
/// ```no_run
/// # use matrix_sdk::Client;
/// # use url::Url;
/// # async {
/// # let homeserver = Url::parse("http://example.com")?;
/// let client = Client::new(homeserver).await?;
///
/// let capabilities = client.get_capabilities().await?;
///
/// if capabilities.change_password.enabled {
/// // Change password
/// }
/// # anyhow::Ok(()) };
/// ```
pub async fn get_capabilities(&self) -> HttpResult<Capabilities> {
let res = self.send(get_capabilities::v3::Request::new()).await?;
Ok(res.capabilities)
/// Retrieves a helper component to access the [`HomeserverCapabilities`]
/// supported or disabled by the homeserver.
pub fn homeserver_capabilities(&self) -> HomeserverCapabilities {
HomeserverCapabilities::new(self.clone())
}
/// Get the server vendor information from the federation API.
+5 -1
View File
@@ -1052,7 +1052,11 @@ mod tests {
// All of Client's async methods that do network requests (and
// possibly some that don't) are `!Send` on wasm. We obviously want
// to be able to use them in event handlers.
let _caps = client.get_capabilities().await.map_err(|e| anyhow::anyhow!("{}", e))?;
client
.homeserver_capabilities()
.refresh()
.await
.map_err(|e| anyhow::anyhow!("{}", e))?;
anyhow::Ok(())
});
}
+1
View File
@@ -65,6 +65,7 @@ pub mod widget;
pub use account::Account;
pub use authentication::{AuthApi, AuthSession, SessionTokens};
pub use client::homeserver_capabilities::HomeserverCapabilities;
#[cfg(feature = "experimental-search")]
pub mod search_index;
pub use client::{
@@ -35,6 +35,7 @@ use ruma::{
DeviceId, EventId, MilliSecondsSinceUnixEpoch, MxcUri, OwnedDeviceId, OwnedEventId,
OwnedOneTimeKeyId, OwnedRoomId, OwnedUserId, RoomId, ServerName, UserId,
api::client::{
discovery::get_capabilities::v3::Capabilities,
profile::{ProfileFieldName, ProfileFieldValue},
receipt::create_receipt::v3::ReceiptType,
room::Visibility,
@@ -1685,6 +1686,15 @@ impl MatrixMockServer {
Mock::given(method("GET")).and(path(format!("/_matrix/client/v3/profile/{user_id}")));
self.mock_endpoint(mock, GetProfileEndpoint)
}
/// Create a prebuilt mock for the endpoint used to get the capabilities of
/// the homeserver.
pub fn mock_get_homeserver_capabilities(
&self,
) -> MockEndpoint<'_, GetHomeserverCapabilitiesEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/client/v3/capabilities"));
self.mock_endpoint(mock, GetHomeserverCapabilitiesEndpoint)
}
}
/// A specification for a push rule ID.
@@ -4916,3 +4926,15 @@ impl<'a> MockEndpoint<'a, GetProfileEndpoint> {
self.respond_with(ResponseTemplate::new(200).set_body_json(profile))
}
}
/// A prebuilt mock for `GET /_matrix/client/*/capabilities`.
pub struct GetHomeserverCapabilitiesEndpoint;
impl<'a> MockEndpoint<'a, GetHomeserverCapabilitiesEndpoint> {
/// Returns a successful empty response.
pub fn ok_with_capabilities(self, capabilities: Capabilities) -> MatrixMock<'a> {
self.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"capabilities": capabilities,
})))
}
}