Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

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

5 changes: 5 additions & 0 deletions crates/ruma-client-api/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ Breaking changes:

Improvements:

- Add `M_INVITE_BLOCKED` candidate error code proposed by
[MSC4380](https://github.com/matrix-org/matrix-spec-proposals/pull/4380)
sharing an unstable prefix with the preceding
[MSC4155](https://github.com/matrix-org/matrix-spec-proposals/pull/4155).

- Added support for the sliding sync extension for thread subscriptions, as well as the
accompanying endpoint, both from experimental MSC4308.
- Added support for the experiment MSC4306 thread subscription endpoints.
Expand Down
2 changes: 2 additions & 0 deletions crates/ruma-client-api/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,12 @@ unstable-msc4140 = ["ruma-common/unstable-msc4140"]
unstable-msc4143 = []
unstable-msc4186 = ["ruma-common/unstable-msc4186"]
unstable-msc4191 = []
unstable-msc4195 = ["unstable-msc4143"]
unstable-msc4222 = []
# Thread subscription support.
unstable-msc4306 = []
unstable-msc4308 = []
unstable-msc4380 = ["ruma-common/unstable-msc4380", "ruma-events/unstable-msc4380"]

[dependencies]
as_variant = { workspace = true }
Expand Down
194 changes: 3 additions & 191 deletions crates/ruma-client-api/src/discovery/discover_homeserver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,14 @@
//!
//! Get discovery information about the domain.

#[cfg(feature = "unstable-msc4143")]
use std::borrow::Cow;

#[cfg(feature = "unstable-msc4143")]
use ruma_common::serde::JsonObject;
use ruma_common::{
api::{request, response, Metadata},
metadata,
};
#[cfg(feature = "unstable-msc4143")]
use serde::de::DeserializeOwned;
use serde::{Deserialize, Serialize};

#[cfg(feature = "unstable-msc4143")]
use serde_json::Value as JsonValue;
use crate::rtc::RtcTransport;

const METADATA: Metadata = metadata! {
method: GET,
Expand Down Expand Up @@ -61,7 +55,7 @@ pub struct Response {
default,
skip_serializing_if = "Vec::is_empty"
)]
pub rtc_foci: Vec<RtcFocusInfo>,
pub rtc_foci: Vec<RtcTransport>,
}

impl Request {
Expand Down Expand Up @@ -133,185 +127,3 @@ impl TileServerInfo {
Self { map_style_url }
}
}

/// Information about a specific MatrixRTC focus.
#[cfg(feature = "unstable-msc4143")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
#[serde(tag = "type")]
pub enum RtcFocusInfo {
/// A LiveKit RTC focus.
#[serde(rename = "livekit")]
LiveKit(LiveKitRtcFocusInfo),

/// A custom RTC focus.
#[doc(hidden)]
#[serde(untagged)]
_Custom(CustomRtcFocusInfo),
}

#[cfg(feature = "unstable-msc4143")]
impl RtcFocusInfo {
/// A constructor to create a custom RTC focus.
///
/// Prefer to use the public variants of `RtcFocusInfo` where possible; this constructor is
/// meant to be used for unsupported focus types only and does not allow setting arbitrary data
/// for supported ones.
///
/// # Errors
///
/// Returns an error if the `focus_type` is known and serialization of `data` to the
/// corresponding `RtcFocusInfo` variant fails.
pub fn new(focus_type: &str, data: JsonObject) -> serde_json::Result<Self> {
fn deserialize_variant<T: DeserializeOwned>(obj: JsonObject) -> serde_json::Result<T> {
serde_json::from_value(JsonValue::Object(obj))
}

Ok(match focus_type {
"livekit" => Self::LiveKit(deserialize_variant(data)?),
_ => Self::_Custom(CustomRtcFocusInfo { focus_type: focus_type.to_owned(), data }),
})
}

/// Creates a new `RtcFocusInfo::LiveKit`.
pub fn livekit(service_url: String) -> Self {
Self::LiveKit(LiveKitRtcFocusInfo { service_url })
}

/// Returns a reference to the focus type of this RTC focus.
pub fn focus_type(&self) -> &str {
match self {
Self::LiveKit(_) => "livekit",
Self::_Custom(custom) => &custom.focus_type,
}
}

/// Returns the associated data.
///
/// The returned JSON object won't contain the `focus_type` field, please use
/// [`.focus_type()`][Self::focus_type] to access that.
///
/// Prefer to use the public variants of `RtcFocusInfo` where possible; this method is meant to
/// be used for custom focus types only.
pub fn data(&self) -> Cow<'_, JsonObject> {
fn serialize<T: Serialize>(object: &T) -> JsonObject {
match serde_json::to_value(object).expect("rtc focus type serialization to succeed") {
JsonValue::Object(object) => object,
_ => panic!("all rtc focus types must serialize to objects"),
}
}

match self {
Self::LiveKit(info) => Cow::Owned(serialize(info)),
Self::_Custom(info) => Cow::Borrowed(&info.data),
}
}
}

/// Information about a LiveKit RTC focus.
#[cfg(feature = "unstable-msc4143")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct LiveKitRtcFocusInfo {
/// The URL for the LiveKit service.
#[serde(rename = "livekit_service_url")]
pub service_url: String,
}

/// Information about a custom RTC focus type.
#[doc(hidden)]
#[cfg(feature = "unstable-msc4143")]
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq, Eq)]
pub struct CustomRtcFocusInfo {
/// The type of RTC focus.
#[serde(rename = "type")]
focus_type: String,

/// Remaining RTC focus data.
#[serde(flatten)]
data: JsonObject,
}

#[cfg(test)]
mod tests {
#[cfg(feature = "unstable-msc4143")]
use assert_matches2::assert_matches;
#[cfg(feature = "unstable-msc4143")]
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};

#[cfg(feature = "unstable-msc4143")]
use super::RtcFocusInfo;

#[test]
#[cfg(feature = "unstable-msc4143")]
fn test_livekit_rtc_focus_deserialization() {
// Given the JSON for a LiveKit RTC focus.
let json = json!({
"type": "livekit",
"livekit_service_url": "https://livekit.example.com"
});

// When deserializing it into an RtcFocusInfo.
let focus: RtcFocusInfo = from_json_value(json).unwrap();

// Then it should be recognized as a LiveKit focus with the correct service URL.
assert_matches!(focus, RtcFocusInfo::LiveKit(info));
assert_eq!(info.service_url, "https://livekit.example.com");
}

#[test]
#[cfg(feature = "unstable-msc4143")]
fn test_livekit_rtc_focus_serialization() {
// Given a LiveKit RTC focus info.
let focus = RtcFocusInfo::livekit("https://livekit.example.com".to_owned());

// When serializing it to JSON.
let json = to_json_value(&focus).unwrap();

// Then it should match the expected JSON structure.
assert_eq!(
json,
json!({
"type": "livekit",
"livekit_service_url": "https://livekit.example.com"
})
);
}

#[test]
#[cfg(feature = "unstable-msc4143")]
fn test_custom_rtc_focus_serialization() {
// Given the JSON for a custom RTC focus type with additional fields.
let json = json!({
"type": "some-focus-type",
"additional-type-specific-field": "https://my_focus.domain",
"another-additional-type-specific-field": ["with", "Array", "type"]
});

// When deserializing it into an RtcFocusInfo.
let focus: RtcFocusInfo = from_json_value(json.clone()).unwrap();

// Then it should be recognized as a custom focus type, with all the additional fields
// included.
assert_eq!(focus.focus_type(), "some-focus-type");

let data = &focus.data();
assert_eq!(data["additional-type-specific-field"], "https://my_focus.domain");

let array_values: Vec<&str> = data["another-additional-type-specific-field"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
assert_eq!(array_values, vec!["with", "Array", "type"]);

assert!(!data.contains_key("type"));

// When serializing it back to JSON.
let serialized = to_json_value(&focus).unwrap();

// Then it should match the original JSON.
assert_eq!(serialized, json);
}
}
18 changes: 14 additions & 4 deletions crates/ruma-client-api/src/discovery/get_supported_versions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ use ruma_common::{
api::{request, response, Metadata, SupportedVersions},
metadata,
};
use smallstr::SmallString;

const METADATA: Metadata = metadata! {
method: GET,
Expand All @@ -29,16 +30,22 @@ pub struct Request {}
#[response(error = crate::Error)]
pub struct Response {
/// A list of Matrix client API protocol versions supported by the homeserver.
pub versions: Vec<String>,
pub versions: Vec<Version>,

/// Experimental features supported by the server.
///
/// Servers can enable some unstable features only for some users, so this
/// list might differ when an access token is provided.
#[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
pub unstable_features: BTreeMap<String, bool>,
pub unstable_features: BTreeMap<Feature, bool>,
}

/// Opinionated optimized Version String type.
pub type Version = SmallString<[u8; 16]>;

/// Opinionated optimized Feature String type.
pub type Feature = SmallString<[u8; 48]>;

impl Request {
/// Creates an empty `Request`.
pub fn new() -> Self {
Expand All @@ -48,7 +55,7 @@ impl Request {

impl Response {
/// Creates a new `Response` with the given `versions`.
pub fn new(versions: Vec<String>) -> Self {
pub fn new(versions: Vec<Version>) -> Self {
Self { versions, unstable_features: BTreeMap::new() }
}

Expand All @@ -58,6 +65,9 @@ impl Response {
/// Matrix versions that can't be parsed to a `MatrixVersion`, and features with the boolean
/// value set to `false` are discarded.
pub fn as_supported_versions(&self) -> SupportedVersions {
SupportedVersions::from_parts(&self.versions, &self.unstable_features)
SupportedVersions::from_parts(
self.versions.iter().map(Version::as_str),
self.unstable_features.iter().map(|(k, v)| (k.as_str(), v)),
)
}
}
19 changes: 19 additions & 0 deletions crates/ruma-client-api/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,13 @@ pub enum ErrorKind {
/// The desired user name is not valid.
InvalidUsername,

/// `M_INVITE_BLOCKED`
///
/// The invite was interdicted by moderation tools or configured access controls without having
/// been witnessed by the invitee.
#[cfg(feature = "unstable-msc4380")]
InviteBlocked,

/// `M_LIMIT_EXCEEDED`
///
/// The request has been refused due to [rate limiting]: too many requests have been sent in a
Expand Down Expand Up @@ -453,6 +460,8 @@ impl ErrorKind {
ErrorKind::InvalidParam => ErrorCode::InvalidParam,
ErrorKind::InvalidRoomState => ErrorCode::InvalidRoomState,
ErrorKind::InvalidUsername => ErrorCode::InvalidUsername,
#[cfg(feature = "unstable-msc4380")]
ErrorKind::InviteBlocked => ErrorCode::InviteBlocked,
ErrorKind::LimitExceeded { .. } => ErrorCode::LimitExceeded,
ErrorKind::MissingParam => ErrorCode::MissingParam,
ErrorKind::MissingToken => ErrorCode::MissingToken,
Expand Down Expand Up @@ -633,6 +642,16 @@ pub enum ErrorCode {
/// The desired user name is not valid.
InvalidUsername,

/// `M_INVITE_BLOCKED`
///
/// The invite was interdicted by moderation tools or configured access controls without having
/// been witnessed by the invitee.
///
/// Unstable prefix intentionally shared with MSC4155 for compatibility.
#[cfg(feature = "unstable-msc4380")]
#[ruma_enum(rename = "ORG.MATRIX.MSC4155.INVITE_BLOCKED")]
InviteBlocked,

/// `M_LIMIT_EXCEEDED`
///
/// The request has been refused due to [rate limiting]: too many requests have been sent in a
Expand Down
2 changes: 2 additions & 0 deletions crates/ruma-client-api/src/error/kind_serde.rs
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@ impl<'de> Visitor<'de> for ErrorKindVisitor {
ErrorCode::InvalidParam => ErrorKind::InvalidParam,
ErrorCode::InvalidRoomState => ErrorKind::InvalidRoomState,
ErrorCode::InvalidUsername => ErrorKind::InvalidUsername,
#[cfg(feature = "unstable-msc4380")]
ErrorCode::InviteBlocked => ErrorKind::InviteBlocked,
ErrorCode::LimitExceeded => ErrorKind::LimitExceeded {
retry_after: retry_after_ms
.map(from_json_value::<UInt>)
Expand Down
4 changes: 3 additions & 1 deletion crates/ruma-client-api/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! [client-api]: https://spec.matrix.org/latest/client-server-api/

#![cfg(any(feature = "client", feature = "server"))]
#![cfg_attr(docsrs, feature(doc_cfg))]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![warn(missing_docs)]

pub mod account;
Expand Down Expand Up @@ -43,6 +43,8 @@ pub mod relations;
pub mod rendezvous;
pub mod reporting;
pub mod room;
#[cfg(feature = "unstable-msc4143")]
pub mod rtc;
pub mod search;
pub mod server;
pub mod session;
Expand Down
1 change: 0 additions & 1 deletion crates/ruma-client-api/src/membership/invite_user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
pub mod v3 {
//! `/v3/` ([spec (MXID)][spec-mxid], [spec (3PID)][spec-3pid])
//!
//! This endpoint has two forms: one to invite a user
//! [by their Matrix identifier][spec-mxid], and one to invite a user
//! [by their third party identifier][spec-3pid].
//!
Expand Down
2 changes: 0 additions & 2 deletions crates/ruma-client-api/src/membership/joined_members.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,14 +61,12 @@ pub mod v3 {
#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
pub struct RoomMember {
/// The display name of the user.
#[serde(skip_serializing_if = "Option::is_none")]
pub display_name: Option<String>,

/// The mxc avatar url of the user.
///
/// If you activate the `compat-empty-string-null` feature, this field being an empty
/// string in JSON will result in `None` here during deserialization.
#[serde(skip_serializing_if = "Option::is_none")]
#[cfg_attr(
feature = "compat-empty-string-null",
serde(default, deserialize_with = "ruma_common::serde::empty_string_as_none")
Expand Down
Loading