Skip to content

Commit 69bef9a

Browse files
authored
feat(sdk,ffi): Add server_vendor_info method to matrix-sdk with automatic logging in FFI
Add a new `server_vendor_info` method on the `matrix-sdk` `Client` that calls the `/_matrix/federation/v1/version` endpoint to retrieve the server's software name and version information. Also add it to the bindings + log it when initializing the logs.
1 parent b3c53dd commit 69bef9a

File tree

8 files changed

+144
-1
lines changed

8 files changed

+144
-1
lines changed

Cargo.lock

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

bindings/matrix-sdk-ffi/src/client.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1618,6 +1618,14 @@ impl Client {
16181618
.any(|focus| matches!(focus, RtcFocusInfo::LiveKit(_))))
16191619
}
16201620

1621+
/// Get server vendor information from the federation API.
1622+
///
1623+
/// This method retrieves information about the server's name and version
1624+
/// by calling the `/_matrix/federation/v1/version` endpoint.
1625+
pub async fn server_vendor_info(&self) -> Result<matrix_sdk::ServerVendorInfo, ClientError> {
1626+
Ok(self.inner.server_vendor_info().await?)
1627+
}
1628+
16211629
/// Subscribe to changes in the media preview configuration.
16221630
pub async fn subscribe_to_media_preview_config(
16231631
&self,

bindings/matrix-sdk-ffi/src/client_builder.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,17 @@ impl ClientBuilder {
574574

575575
let sdk_client = inner_builder.build().await?;
576576

577+
// Log server version information at info level.
578+
if let Ok(server_info) = sdk_client.server_vendor_info().await {
579+
tracing::info!(
580+
server_name = %server_info.server_name,
581+
version = %server_info.version,
582+
"Connected to Matrix server"
583+
);
584+
} else {
585+
tracing::warn!("Could not retrieve server version information");
586+
}
587+
577588
Ok(Arc::new(
578589
Client::new(
579590
sdk_client,

crates/matrix-sdk/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ pin-project-lite.workspace = true
101101
rand = { workspace = true, optional = true }
102102
ruma = { workspace = true, features = [
103103
"rand",
104+
"federation-api-c",
104105
"unstable-msc2448",
105106
"unstable-msc4191",
106107
"unstable-msc3930",

crates/matrix-sdk/src/client/mod.rs

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ use ruma::{
6565
user_directory::search_users,
6666
},
6767
error::FromHttpResponseError,
68+
federation::discovery::get_server_version,
6869
FeatureFlag, MatrixVersion, OutgoingRequest, SupportedVersions,
6970
},
7071
assign,
@@ -153,6 +154,16 @@ pub enum SessionChange {
153154
TokensRefreshed,
154155
}
155156

157+
/// Information about the server vendor obtained from the federation API.
158+
#[derive(Debug, Clone, PartialEq, Eq)]
159+
#[cfg_attr(feature = "uniffi", derive(uniffi::Record))]
160+
pub struct ServerVendorInfo {
161+
/// The server name.
162+
pub server_name: String,
163+
/// The server version.
164+
pub version: String,
165+
}
166+
156167
/// An async/await enabled Matrix client.
157168
///
158169
/// All of the state is held in an `Arc` so the `Client` can be cloned freely.
@@ -523,6 +534,38 @@ impl Client {
523534
Ok(res.capabilities)
524535
}
525536

537+
/// Get the server vendor information from the federation API.
538+
///
539+
/// This method calls the `/_matrix/federation/v1/version` endpoint to get
540+
/// both the server's software name and version.
541+
///
542+
/// # Examples
543+
///
544+
/// ```no_run
545+
/// # use matrix_sdk::Client;
546+
/// # use url::Url;
547+
/// # async {
548+
/// # let homeserver = Url::parse("http://example.com")?;
549+
/// let client = Client::new(homeserver).await?;
550+
///
551+
/// let server_info = client.server_vendor_info().await?;
552+
/// println!(
553+
/// "Server: {}, Version: {}",
554+
/// server_info.server_name, server_info.version
555+
/// );
556+
/// # anyhow::Ok(()) };
557+
/// ```
558+
pub async fn server_vendor_info(&self) -> HttpResult<ServerVendorInfo> {
559+
let res = self.send(get_server_version::v1::Request::new()).await?;
560+
561+
// Extract server info, using defaults if fields are missing.
562+
let server = res.server.unwrap_or_default();
563+
let server_name_str = server.name.unwrap_or_else(|| "unknown".to_owned());
564+
let version = server.version.unwrap_or_else(|| "unknown".to_owned());
565+
566+
Ok(ServerVendorInfo { server_name: server_name_str, version })
567+
}
568+
526569
/// Get a copy of the default request config.
527570
///
528571
/// The default request config is what's used when sending requests if no

crates/matrix-sdk/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,8 @@ pub mod widget;
6868
pub use account::Account;
6969
pub use authentication::{AuthApi, AuthSession, SessionTokens};
7070
pub use client::{
71-
sanitize_server_name, Client, ClientBuildError, ClientBuilder, LoopCtrl, SessionChange,
71+
sanitize_server_name, Client, ClientBuildError, ClientBuilder, LoopCtrl, ServerVendorInfo,
72+
SessionChange,
7273
};
7374
pub use error::{
7475
Error, HttpError, HttpResult, NotificationSettingsError, RefreshTokenError, Result,

crates/matrix-sdk/src/test_utils/mocks/mod.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1384,6 +1384,12 @@ impl MatrixMockServer {
13841384
)));
13851385
self.mock_endpoint(mock, EnablePushRuleEndpoint).expect_default_access_token()
13861386
}
1387+
1388+
/// Create a prebuilt mock for the federation version endpoint.
1389+
pub fn mock_federation_version(&self) -> MockEndpoint<'_, FederationVersionEndpoint> {
1390+
let mock = Mock::given(method("GET")).and(path("/_matrix/federation/v1/version"));
1391+
self.mock_endpoint(mock, FederationVersionEndpoint)
1392+
}
13871393
}
13881394

13891395
/// Parameter to [`MatrixMockServer::sync_room`].
@@ -3983,3 +3989,25 @@ impl<'a> MockEndpoint<'a, EnablePushRuleEndpoint> {
39833989
self.ok_empty_json()
39843990
}
39853991
}
3992+
3993+
/// A prebuilt mock for the federation version endpoint.
3994+
pub struct FederationVersionEndpoint;
3995+
3996+
impl<'a> MockEndpoint<'a, FederationVersionEndpoint> {
3997+
/// Returns a successful response with the given server name and version.
3998+
pub fn ok(self, server_name: &str, version: &str) -> MatrixMock<'a> {
3999+
let response_body = json!({
4000+
"server": {
4001+
"name": server_name,
4002+
"version": version
4003+
}
4004+
});
4005+
self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
4006+
}
4007+
4008+
/// Returns a successful response with empty/missing server information.
4009+
pub fn ok_empty(self) -> MatrixMock<'a> {
4010+
let response_body = json!({});
4011+
self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
4012+
}
4013+
}

crates/matrix-sdk/tests/integration/client.rs

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1490,3 +1490,36 @@ async fn test_room_sync_state_after() {
14901490
let member = room.get_member_no_sync(user_id!("@invited:localhost")).await.unwrap().unwrap();
14911491
assert_eq!(*member.membership(), MembershipState::Leave);
14921492
}
1493+
1494+
#[async_test]
1495+
async fn test_server_vendor_info() {
1496+
use matrix_sdk::test_utils::mocks::MatrixMockServer;
1497+
1498+
let server = MatrixMockServer::new().await;
1499+
let client = server.client_builder().build().await;
1500+
1501+
// Mock the federation version endpoint
1502+
server.mock_federation_version().ok("Synapse", "1.70.0").mount().await;
1503+
1504+
let server_info = client.server_vendor_info().await.unwrap();
1505+
1506+
assert_eq!(server_info.server_name, "Synapse");
1507+
assert_eq!(server_info.version, "1.70.0");
1508+
}
1509+
1510+
#[async_test]
1511+
async fn test_server_vendor_info_with_missing_fields() {
1512+
use matrix_sdk::test_utils::mocks::MatrixMockServer;
1513+
1514+
let server = MatrixMockServer::new().await;
1515+
let client = server.client_builder().build().await;
1516+
1517+
// Mock the federation version endpoint with missing fields
1518+
server.mock_federation_version().ok_empty().mount().await;
1519+
1520+
let server_info = client.server_vendor_info().await.unwrap();
1521+
1522+
// Should use defaults for missing fields
1523+
assert_eq!(server_info.server_name, "unknown");
1524+
assert_eq!(server_info.version, "unknown");
1525+
}

0 commit comments

Comments
 (0)