Skip to content
Merged
18 changes: 18 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions bindings/matrix-sdk-ffi/src/client_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -574,6 +574,17 @@ impl ClientBuilder {

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

// Log server version information at info level
if let Ok(server_info) = sdk_client.server_version().await {
tracing::info!(
server_name = %server_info.server_name,
version = %server_info.version,
"Connected to Matrix server"
);
} else {
tracing::debug!("Could not retrieve server version information");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tracing::debug!("Could not retrieve server version information");
tracing::warn!("Could not retrieve server version information");

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed log level from debug! to warn! as requested in commit c89a9cc.

}

Ok(Arc::new(
Client::new(
sdk_client,
Expand Down
1 change: 1 addition & 0 deletions crates/matrix-sdk/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ pin-project-lite.workspace = true
rand = { workspace = true, optional = true }
ruma = { workspace = true, features = [
"rand",
"federation-api-c",
"unstable-msc2448",
"unstable-msc4191",
"unstable-msc3930",
Expand Down
39 changes: 39 additions & 0 deletions crates/matrix-sdk/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ use ruma::{
uiaa,
user_directory::search_users,
},
federation::discovery::get_server_version,
error::FromHttpResponseError,
FeatureFlag, MatrixVersion, OutgoingRequest, SupportedVersions,
},
Expand Down Expand Up @@ -151,6 +152,15 @@ pub enum SessionChange {
TokensRefreshed,
}

/// Information about the server version obtained from the federation API.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ServerVersionInfo {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we name this ServerVendorInfo?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed ServerVersionInfo to ServerVendorInfo as requested in commit c89a9cc.

/// The server name.
pub server_name: String,
/// The server version.
pub version: String,
}

/// An async/await enabled Matrix client.
///
/// All of the state is held in an `Arc` so the `Client` can be cloned freely.
Expand Down Expand Up @@ -521,6 +531,35 @@ impl Client {
Ok(res.capabilities)
}

/// Get the server version information from the federation API.
///
/// This method calls the `/_matrix/federation/v1/version` endpoint to get
/// both the server name and version.
///
/// # 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 server_info = client.server_version().await?;
/// println!("Server: {}, Version: {}", server_info.server_name, server_info.version);
/// # anyhow::Ok(()) };
/// ```
pub async fn server_version(&self) -> HttpResult<ServerVersionInfo> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we rename this method server_vendor_info?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed method from server_version to server_vendor_info as requested in commit c89a9cc.

let res = self.send(get_server_version::v1::Request::new()).await?;

// Extract server info, using defaults if fields are missing
let server = res.server.unwrap_or_default();
let server_name_str = server.name.unwrap_or_else(|| "unknown".to_string());
let version = server.version.unwrap_or_else(|| "unknown".to_string());

Ok(ServerVersionInfo { server_name: server_name_str, version })
}

/// Get a copy of the default request config.
///
/// The default request config is what's used when sending requests if no
Expand Down
3 changes: 2 additions & 1 deletion crates/matrix-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,8 @@ pub mod widget;
pub use account::Account;
pub use authentication::{AuthApi, AuthSession, SessionTokens};
pub use client::{
sanitize_server_name, Client, ClientBuildError, ClientBuilder, LoopCtrl, SessionChange,
sanitize_server_name, Client, ClientBuildError, ClientBuilder, LoopCtrl, ServerVersionInfo,
SessionChange,
};
pub use error::{
Error, HttpError, HttpResult, NotificationSettingsError, RefreshTokenError, Result,
Expand Down
28 changes: 28 additions & 0 deletions crates/matrix-sdk/src/test_utils/mocks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1377,6 +1377,12 @@ impl MatrixMockServer {
)));
self.mock_endpoint(mock, EnablePushRuleEndpoint).expect_default_access_token()
}

/// Create a prebuilt mock for the federation version endpoint.
pub fn mock_federation_version(&self) -> MockEndpoint<'_, FederationVersionEndpoint> {
let mock = Mock::given(method("GET")).and(path("/_matrix/federation/v1/version"));
self.mock_endpoint(mock, FederationVersionEndpoint)
}
}

/// Parameter to [`MatrixMockServer::sync_room`].
Expand Down Expand Up @@ -3955,3 +3961,25 @@ impl<'a> MockEndpoint<'a, EnablePushRuleEndpoint> {
self.ok_empty_json()
}
}

/// A prebuilt mock for the federation version endpoint.
pub struct FederationVersionEndpoint;

impl<'a> MockEndpoint<'a, FederationVersionEndpoint> {
/// Returns a successful response with the given server name and version.
pub fn ok(self, server_name: &str, version: &str) -> MatrixMock<'a> {
let response_body = json!({
"server": {
"name": server_name,
"version": version
}
});
self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
}

/// Returns a successful response with empty/missing server information.
pub fn ok_empty(self) -> MatrixMock<'a> {
let response_body = json!({});
self.respond_with(ResponseTemplate::new(200).set_body_json(response_body))
}
}
41 changes: 41 additions & 0 deletions crates/matrix-sdk/tests/integration/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1490,3 +1490,44 @@ async fn test_room_sync_state_after() {
let member = room.get_member_no_sync(user_id!("@invited:localhost")).await.unwrap().unwrap();
assert_eq!(*member.membership(), MembershipState::Leave);
}

#[async_test]
async fn test_server_version() {
use matrix_sdk::test_utils::mocks::MatrixMockServer;

let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;

// Mock the federation version endpoint
server
.mock_federation_version()
.ok("Synapse", "1.70.0")
.mount()
.await;

let server_info = client.server_version().await.unwrap();

assert_eq!(server_info.server_name, "Synapse");
assert_eq!(server_info.version, "1.70.0");
}

#[async_test]
async fn test_server_version_with_missing_fields() {
use matrix_sdk::test_utils::mocks::MatrixMockServer;

let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;

// Mock the federation version endpoint with missing fields
server
.mock_federation_version()
.ok_empty()
.mount()
.await;

let server_info = client.server_version().await.unwrap();

// Should use defaults for missing fields
assert_eq!(server_info.server_name, "unknown");
assert_eq!(server_info.version, "unknown");
}