client-api: Implement space summary API
According to MSC2946
This commit is contained in:
parent
ede7601aa9
commit
84e1c919c9
@ -4,6 +4,10 @@ Breaking changes:
|
|||||||
|
|
||||||
* Use `Raw` for `config::set_*_account_data::Request::data`.
|
* Use `Raw` for `config::set_*_account_data::Request::data`.
|
||||||
|
|
||||||
|
Improvements:
|
||||||
|
|
||||||
|
* Add support for the space summary API in `space::get_hierarchy` according to MSC2946.
|
||||||
|
|
||||||
# 0.13.0
|
# 0.13.0
|
||||||
|
|
||||||
Bug fixes:
|
Bug fixes:
|
||||||
|
@ -35,6 +35,7 @@ pub mod room;
|
|||||||
pub mod search;
|
pub mod search;
|
||||||
pub mod server;
|
pub mod server;
|
||||||
pub mod session;
|
pub mod session;
|
||||||
|
pub mod space;
|
||||||
pub mod state;
|
pub mod state;
|
||||||
pub mod sync;
|
pub mod sync;
|
||||||
pub mod tag;
|
pub mod tag;
|
||||||
|
128
crates/ruma-client-api/src/space.rs
Normal file
128
crates/ruma-client-api/src/space.rs
Normal file
@ -0,0 +1,128 @@
|
|||||||
|
//! Endpoints for spaces.
|
||||||
|
|
||||||
|
use js_int::UInt;
|
||||||
|
use ruma_common::{directory::PublicRoomJoinRule, room::RoomType};
|
||||||
|
use ruma_events::space::child::HierarchySpaceChildStateEvent;
|
||||||
|
use ruma_identifiers::{MxcUri, RoomAliasId, RoomId, RoomName};
|
||||||
|
use ruma_serde::Raw;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod get_hierarchy;
|
||||||
|
|
||||||
|
/// A chunk of a space hierarchy response, describing one room.
|
||||||
|
///
|
||||||
|
/// To create an instance of this type, first create a `SpaceHierarchyRoomsChunkInit` and convert it
|
||||||
|
/// via `SpaceHierarchyRoomsChunk::from` / `.into()`.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct SpaceHierarchyRoomsChunk {
|
||||||
|
/// The canonical alias of the room, if any.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
#[cfg_attr(
|
||||||
|
feature = "compat",
|
||||||
|
serde(default, deserialize_with = "ruma_serde::empty_string_as_none")
|
||||||
|
)]
|
||||||
|
pub canonical_alias: Option<Box<RoomAliasId>>,
|
||||||
|
|
||||||
|
/// The name of the room, if any.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub name: Option<Box<RoomName>>,
|
||||||
|
|
||||||
|
/// The number of members joined to the room.
|
||||||
|
pub num_joined_members: UInt,
|
||||||
|
|
||||||
|
/// The ID of the room.
|
||||||
|
pub room_id: Box<RoomId>,
|
||||||
|
|
||||||
|
/// The topic of the room, if any.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub topic: Option<String>,
|
||||||
|
|
||||||
|
/// Whether the room may be viewed by guest users without joining.
|
||||||
|
pub world_readable: bool,
|
||||||
|
|
||||||
|
/// Whether guest users may join the room and participate in it.
|
||||||
|
///
|
||||||
|
/// If they can, they will be subject to ordinary power level rules like any other user.
|
||||||
|
pub guest_can_join: bool,
|
||||||
|
|
||||||
|
/// The URL for the room's avatar, if one is set.
|
||||||
|
///
|
||||||
|
/// If you activate the `compat` 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",
|
||||||
|
serde(default, deserialize_with = "ruma_serde::empty_string_as_none")
|
||||||
|
)]
|
||||||
|
pub avatar_url: Option<Box<MxcUri>>,
|
||||||
|
|
||||||
|
/// The join rule of the room.
|
||||||
|
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
|
||||||
|
pub join_rule: PublicRoomJoinRule,
|
||||||
|
|
||||||
|
/// The type of room from `m.room.create`, if any.
|
||||||
|
pub room_type: Option<RoomType>,
|
||||||
|
|
||||||
|
/// The stripped `m.space.child` events of the space-room.
|
||||||
|
///
|
||||||
|
/// If the room is not a space-room, this should be empty.
|
||||||
|
pub children_state: Vec<Raw<HierarchySpaceChildStateEvent>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Initial set of mandatory fields of `SpaceHierarchyRoomsChunk`.
|
||||||
|
///
|
||||||
|
/// This struct will not be updated even if additional fields are added to
|
||||||
|
/// `SpaceHierarchyRoomsChunk` in a new (non-breaking) release of the Matrix specification.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[allow(clippy::exhaustive_structs)]
|
||||||
|
pub struct SpaceHierarchyRoomsChunkInit {
|
||||||
|
/// The number of members joined to the room.
|
||||||
|
pub num_joined_members: UInt,
|
||||||
|
|
||||||
|
/// The ID of the room.
|
||||||
|
pub room_id: Box<RoomId>,
|
||||||
|
|
||||||
|
/// Whether the room may be viewed by guest users without joining.
|
||||||
|
pub world_readable: bool,
|
||||||
|
|
||||||
|
/// Whether guest users may join the room and participate in it.
|
||||||
|
///
|
||||||
|
/// If they can, they will be subject to ordinary power level rules like any other user.
|
||||||
|
pub guest_can_join: bool,
|
||||||
|
|
||||||
|
/// The join rule of the room.
|
||||||
|
pub join_rule: PublicRoomJoinRule,
|
||||||
|
|
||||||
|
/// The stripped `m.space.child` events of the space-room.
|
||||||
|
///
|
||||||
|
/// If the room is not a space-room, this should be empty.
|
||||||
|
pub children_state: Vec<Raw<HierarchySpaceChildStateEvent>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<SpaceHierarchyRoomsChunkInit> for SpaceHierarchyRoomsChunk {
|
||||||
|
fn from(init: SpaceHierarchyRoomsChunkInit) -> Self {
|
||||||
|
let SpaceHierarchyRoomsChunkInit {
|
||||||
|
num_joined_members,
|
||||||
|
room_id,
|
||||||
|
world_readable,
|
||||||
|
guest_can_join,
|
||||||
|
join_rule,
|
||||||
|
children_state,
|
||||||
|
} = init;
|
||||||
|
|
||||||
|
Self {
|
||||||
|
canonical_alias: None,
|
||||||
|
name: None,
|
||||||
|
num_joined_members,
|
||||||
|
room_id,
|
||||||
|
topic: None,
|
||||||
|
world_readable,
|
||||||
|
guest_can_join,
|
||||||
|
avatar_url: None,
|
||||||
|
join_rule,
|
||||||
|
room_type: None,
|
||||||
|
children_state,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
85
crates/ruma-client-api/src/space/get_hierarchy.rs
Normal file
85
crates/ruma-client-api/src/space/get_hierarchy.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
//! `GET /_matrix/client/*/rooms/{roomId}/hierarchy`
|
||||||
|
|
||||||
|
pub mod v1 {
|
||||||
|
//! `/v1/` ([spec])
|
||||||
|
//!
|
||||||
|
//! [spec]: https://spec.matrix.org/v1.2/client-server-api/#get_matrixclientv1roomsroomidhierarchy
|
||||||
|
|
||||||
|
use js_int::UInt;
|
||||||
|
use ruma_api::ruma_api;
|
||||||
|
use ruma_identifiers::RoomId;
|
||||||
|
|
||||||
|
use crate::space::SpaceHierarchyRoomsChunk;
|
||||||
|
|
||||||
|
ruma_api! {
|
||||||
|
metadata: {
|
||||||
|
description: "Paginates over the space tree in a depth-first manner to locate child rooms of a given space.",
|
||||||
|
method: GET,
|
||||||
|
name: "hierarchy",
|
||||||
|
unstable_path: "/_matrix/client/unstable/org.matrix.msc2946/rooms/:room_id/hierarchy",
|
||||||
|
stable_path: "/_matrix/client/v1/rooms/:room_id/hierarchy",
|
||||||
|
rate_limited: true,
|
||||||
|
authentication: AccessToken,
|
||||||
|
added: 1.2,
|
||||||
|
}
|
||||||
|
|
||||||
|
request: {
|
||||||
|
/// The room ID of the space to get a hierarchy for.
|
||||||
|
#[ruma_api(path)]
|
||||||
|
pub room_id: &'a RoomId,
|
||||||
|
|
||||||
|
/// A pagination token from a previous result.
|
||||||
|
///
|
||||||
|
/// If specified, `max_depth` and `suggested_only` cannot be changed from the first request.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
pub from: Option<&'a str>,
|
||||||
|
|
||||||
|
/// The maximum number of rooms to include per response.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
pub limit: Option<UInt>,
|
||||||
|
|
||||||
|
/// How far to go into the space.
|
||||||
|
///
|
||||||
|
/// When reached, no further child rooms will be returned.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
pub max_depth: Option<UInt>,
|
||||||
|
|
||||||
|
/// Whether or not the server should only consider suggested rooms.
|
||||||
|
///
|
||||||
|
/// Suggested rooms are annotated in their `m.space.child` event contents.
|
||||||
|
///
|
||||||
|
/// Defaults to `false`.
|
||||||
|
#[ruma_api(query)]
|
||||||
|
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
|
||||||
|
pub suggested_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
response: {
|
||||||
|
/// A token to supply to from to keep paginating the responses.
|
||||||
|
///
|
||||||
|
/// Not present when there are no further results.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub next_batch: Option<String>,
|
||||||
|
|
||||||
|
/// A paginated chunk of the space children.
|
||||||
|
pub chunk: Vec<SpaceHierarchyRoomsChunk>,
|
||||||
|
}
|
||||||
|
|
||||||
|
error: crate::Error
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Request<'a> {
|
||||||
|
/// Creates a new `Request` with the given room ID.
|
||||||
|
pub fn new(room_id: &'a RoomId) -> Self {
|
||||||
|
Self { room_id, from: None, limit: None, max_depth: None, suggested_only: false }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Response {
|
||||||
|
/// Creates an empty `Response`.
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -357,6 +357,9 @@ pub enum EventKind {
|
|||||||
|
|
||||||
/// Presence event kind.
|
/// Presence event kind.
|
||||||
Presence,
|
Presence,
|
||||||
|
|
||||||
|
/// Hierarchy space child kind.
|
||||||
|
HierarchySpaceChild,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// `HasDeserializeFields` is used in the code generated by the `Event` derive
|
/// `HasDeserializeFields` is used in the code generated by the `Event` derive
|
||||||
|
@ -2,8 +2,9 @@
|
|||||||
//!
|
//!
|
||||||
//! [`m.space.child`]: https://spec.matrix.org/v1.2/client-server-api/#mspacechild
|
//! [`m.space.child`]: https://spec.matrix.org/v1.2/client-server-api/#mspacechild
|
||||||
|
|
||||||
use ruma_identifiers::ServerName;
|
use ruma_common::MilliSecondsSinceUnixEpoch;
|
||||||
use ruma_macros::EventContent;
|
use ruma_identifiers::{ServerName, UserId};
|
||||||
|
use ruma_macros::{Event, EventContent};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
/// The content of an `m.space.child` event.
|
/// The content of an `m.space.child` event.
|
||||||
@ -50,12 +51,33 @@ impl SpaceChildEventContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An `m.space.child` event represented as a Stripped State Event with an added `origin_server_ts`
|
||||||
|
/// key.
|
||||||
|
#[derive(Clone, Debug, Event)]
|
||||||
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
|
pub struct HierarchySpaceChildStateEvent {
|
||||||
|
/// The content of the space child event.
|
||||||
|
pub content: SpaceChildEventContent,
|
||||||
|
|
||||||
|
/// The fully-qualified ID of the user who sent this event.
|
||||||
|
pub sender: Box<UserId>,
|
||||||
|
|
||||||
|
/// The room ID of the child.
|
||||||
|
pub state_key: String,
|
||||||
|
|
||||||
|
/// Timestamp in milliseconds on originating homeserver when this event was sent.
|
||||||
|
pub origin_server_ts: MilliSecondsSinceUnixEpoch,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use ruma_identifiers::server_name;
|
use js_int::uint;
|
||||||
use serde_json::{json, to_value as to_json_value};
|
use matches::assert_matches;
|
||||||
|
use ruma_common::MilliSecondsSinceUnixEpoch;
|
||||||
|
use ruma_identifiers::{server_name, user_id};
|
||||||
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
|
||||||
use super::SpaceChildEventContent;
|
use super::{HierarchySpaceChildStateEvent, SpaceChildEventContent};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn space_child_serialization() {
|
fn space_child_serialization() {
|
||||||
@ -82,4 +104,63 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(to_json_value(&content).unwrap(), json);
|
assert_eq!(to_json_value(&content).unwrap(), json);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hierarchy_space_child_serialization() {
|
||||||
|
let event = HierarchySpaceChildStateEvent {
|
||||||
|
content: SpaceChildEventContent {
|
||||||
|
via: Some(vec![server_name!("example.com").to_owned()]),
|
||||||
|
order: Some("uwu".to_owned()),
|
||||||
|
suggested: None,
|
||||||
|
},
|
||||||
|
sender: user_id!("@example:localhost").to_owned(),
|
||||||
|
state_key: "!child:localhost".to_owned(),
|
||||||
|
origin_server_ts: MilliSecondsSinceUnixEpoch(uint!(1_629_413_349)),
|
||||||
|
};
|
||||||
|
|
||||||
|
let json = json!({
|
||||||
|
"content": {
|
||||||
|
"via": ["example.com"],
|
||||||
|
"order": "uwu",
|
||||||
|
},
|
||||||
|
"sender": "@example:localhost",
|
||||||
|
"state_key": "!child:localhost",
|
||||||
|
"origin_server_ts": 1_629_413_349,
|
||||||
|
"type": "m.space.child",
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(to_json_value(&event).unwrap(), json);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hierarchy_space_child_deserialization() {
|
||||||
|
let json = json!({
|
||||||
|
"content": {
|
||||||
|
"via": [
|
||||||
|
"example.org"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1_629_413_349,
|
||||||
|
"sender": "@alice:example.org",
|
||||||
|
"state_key": "!a:example.org",
|
||||||
|
"type": "m.space.child"
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<HierarchySpaceChildStateEvent>(json).unwrap(),
|
||||||
|
HierarchySpaceChildStateEvent {
|
||||||
|
content: SpaceChildEventContent {
|
||||||
|
via: Some(via),
|
||||||
|
order: None,
|
||||||
|
suggested: None,
|
||||||
|
},
|
||||||
|
origin_server_ts,
|
||||||
|
sender,
|
||||||
|
state_key,
|
||||||
|
} if via[0] == "example.org"
|
||||||
|
&& origin_server_ts.get() == uint!(1_629_413_349)
|
||||||
|
&& sender == "@alice:example.org"
|
||||||
|
&& state_key == "!a:example.org"
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -394,7 +394,10 @@ fn generate_marker_trait_impl(
|
|||||||
EventKind::MessageLike => quote! { MessageLikeEventContent },
|
EventKind::MessageLike => quote! { MessageLikeEventContent },
|
||||||
EventKind::State => quote! { StateEventContent },
|
EventKind::State => quote! { StateEventContent },
|
||||||
EventKind::ToDevice => quote! { ToDeviceEventContent },
|
EventKind::ToDevice => quote! { ToDeviceEventContent },
|
||||||
EventKind::RoomRedaction | EventKind::Presence | EventKind::Decrypted => {
|
EventKind::RoomRedaction
|
||||||
|
| EventKind::Presence
|
||||||
|
| EventKind::Decrypted
|
||||||
|
| EventKind::HierarchySpaceChild => {
|
||||||
return Err(syn::Error::new_spanned(
|
return Err(syn::Error::new_spanned(
|
||||||
ident,
|
ident,
|
||||||
"valid event kinds are GlobalAccountData, RoomAccountData, \
|
"valid event kinds are GlobalAccountData, RoomAccountData, \
|
||||||
@ -454,7 +457,10 @@ fn generate_static_event_content_impl(
|
|||||||
EventKind::MessageLike => quote! { MessageLike { redacted: #redacted } },
|
EventKind::MessageLike => quote! { MessageLike { redacted: #redacted } },
|
||||||
EventKind::State => quote! { State { redacted: #redacted } },
|
EventKind::State => quote! { State { redacted: #redacted } },
|
||||||
EventKind::ToDevice => quote! { ToDevice },
|
EventKind::ToDevice => quote! { ToDevice },
|
||||||
EventKind::RoomRedaction | EventKind::Presence | EventKind::Decrypted => {
|
EventKind::RoomRedaction
|
||||||
|
| EventKind::Presence
|
||||||
|
| EventKind::Decrypted
|
||||||
|
| EventKind::HierarchySpaceChild => {
|
||||||
unreachable!("not a valid event content kind")
|
unreachable!("not a valid event content kind")
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -85,6 +85,7 @@ pub enum EventKind {
|
|||||||
ToDevice,
|
ToDevice,
|
||||||
RoomRedaction,
|
RoomRedaction,
|
||||||
Presence,
|
Presence,
|
||||||
|
HierarchySpaceChild,
|
||||||
Decrypted,
|
Decrypted,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -99,6 +100,7 @@ impl fmt::Display for EventKind {
|
|||||||
EventKind::ToDevice => write!(f, "ToDeviceEvent"),
|
EventKind::ToDevice => write!(f, "ToDeviceEvent"),
|
||||||
EventKind::RoomRedaction => write!(f, "RoomRedactionEvent"),
|
EventKind::RoomRedaction => write!(f, "RoomRedactionEvent"),
|
||||||
EventKind::Presence => write!(f, "PresenceEvent"),
|
EventKind::Presence => write!(f, "PresenceEvent"),
|
||||||
|
EventKind::HierarchySpaceChild => write!(f, "HierarchySpaceChildStateEvent"),
|
||||||
EventKind::Decrypted => unreachable!(),
|
EventKind::Decrypted => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -204,6 +206,9 @@ pub fn to_kind_variation(ident: &Ident) -> Option<(EventKind, EventKindVariation
|
|||||||
"RedactedSyncStateEvent" => Some((EventKind::State, EventKindVariation::RedactedSync)),
|
"RedactedSyncStateEvent" => Some((EventKind::State, EventKindVariation::RedactedSync)),
|
||||||
"ToDeviceEvent" => Some((EventKind::ToDevice, EventKindVariation::Full)),
|
"ToDeviceEvent" => Some((EventKind::ToDevice, EventKindVariation::Full)),
|
||||||
"PresenceEvent" => Some((EventKind::Presence, EventKindVariation::Full)),
|
"PresenceEvent" => Some((EventKind::Presence, EventKindVariation::Full)),
|
||||||
|
"HierarchySpaceChildStateEvent" => {
|
||||||
|
Some((EventKind::HierarchySpaceChild, EventKindVariation::Stripped))
|
||||||
|
}
|
||||||
"RoomRedactionEvent" => Some((EventKind::RoomRedaction, EventKindVariation::Full)),
|
"RoomRedactionEvent" => Some((EventKind::RoomRedaction, EventKindVariation::Full)),
|
||||||
"SyncRoomRedactionEvent" => Some((EventKind::RoomRedaction, EventKindVariation::Sync)),
|
"SyncRoomRedactionEvent" => Some((EventKind::RoomRedaction, EventKindVariation::Sync)),
|
||||||
"RedactedRoomRedactionEvent" => {
|
"RedactedRoomRedactionEvent" => {
|
||||||
|
@ -31,7 +31,10 @@ pub fn expand_event_type_enum(
|
|||||||
room.push(&event.events);
|
room.push(&event.events);
|
||||||
}
|
}
|
||||||
EventKind::ToDevice => to_device.push(&event.events),
|
EventKind::ToDevice => to_device.push(&event.events),
|
||||||
EventKind::RoomRedaction | EventKind::Presence | EventKind::Decrypted => {}
|
EventKind::RoomRedaction
|
||||||
|
| EventKind::Presence
|
||||||
|
| EventKind::Decrypted
|
||||||
|
| EventKind::HierarchySpaceChild => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let presence = vec![EventEnumEntry {
|
let presence = vec![EventEnumEntry {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user