federation-api: Implement space summary API

According to MSC2946
This commit is contained in:
Kévin Commaille 2022-02-25 19:44:44 +01:00
parent 84e1c919c9
commit c52e51c016
No known key found for this signature in database
GPG Key ID: DD507DAE96E8245C
4 changed files with 331 additions and 0 deletions

View File

@ -1,5 +1,9 @@
# [unreleased] # [unreleased]
Improvements:
* Add support for the space summary API in `space::get_hierarchy` according to MSC2946.
# 0.4.0 # 0.4.0
Breaking changes: Breaking changes:

View File

@ -23,6 +23,7 @@ pub mod knock;
pub mod membership; pub mod membership;
pub mod openid; pub mod openid;
pub mod query; pub mod query;
pub mod space;
pub mod thirdparty; pub mod thirdparty;
pub mod transactions; pub mod transactions;

View File

@ -0,0 +1,256 @@
//! Spaces endpoints.
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;
/// The summary of a parent space.
///
/// To create an instance of this type, first create a `SpaceHierarchyParentSummaryInit` and convert
/// it via `SpaceHierarchyParentSummary::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct SpaceHierarchyParentSummary {
/// 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>>,
/// If the room is a restricted room, these are the room IDs which are specified by the join
/// rules.
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
pub allowed_room_ids: Vec<Box<RoomId>>,
}
/// Initial set of mandatory fields of `SpaceHierarchyParentSummary`.
///
/// This struct will not be updated even if additional fields are added to
/// `SpaceHierarchyParentSummary` in a new (non-breaking) release of the Matrix specification.
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct SpaceHierarchyParentSummaryInit {
/// 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>>,
/// If the room is a restricted room, these are the room IDs which are specified by the join
/// rules.
pub allowed_room_ids: Vec<Box<RoomId>>,
}
impl From<SpaceHierarchyParentSummaryInit> for SpaceHierarchyParentSummary {
fn from(init: SpaceHierarchyParentSummaryInit) -> Self {
let SpaceHierarchyParentSummaryInit {
num_joined_members,
room_id,
world_readable,
guest_can_join,
join_rule,
children_state,
allowed_room_ids,
} = 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,
allowed_room_ids,
}
}
}
/// The summary of a space's child.
///
/// To create an instance of this type, first create a `SpaceHierarchyChildSummaryInit` and convert
/// it via `SpaceHierarchyChildSummary::from` / `.into()`.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
pub struct SpaceHierarchyChildSummary {
/// 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>,
/// If the room is a restricted room, these are the room IDs which are specified by the join
/// rules.
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
pub allowed_room_ids: Vec<Box<RoomId>>,
}
/// Initial set of mandatory fields of `SpaceHierarchyChildSummary`.
///
/// This struct will not be updated even if additional fields are added to
/// `SpaceHierarchyChildSummary` in a new (non-breaking) release of the Matrix specification.
#[derive(Debug)]
#[allow(clippy::exhaustive_structs)]
pub struct SpaceHierarchyChildSummaryInit {
/// 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,
/// If the room is a restricted room, these are the room IDs which are specified by the join
/// rules.
pub allowed_room_ids: Vec<Box<RoomId>>,
}
impl From<SpaceHierarchyChildSummaryInit> for SpaceHierarchyChildSummary {
fn from(init: SpaceHierarchyChildSummaryInit) -> Self {
let SpaceHierarchyChildSummaryInit {
num_joined_members,
room_id,
world_readable,
guest_can_join,
join_rule,
allowed_room_ids,
} = 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,
allowed_room_ids,
}
}
}

View File

@ -0,0 +1,70 @@
//! `GET /_matrix/federation/*/hierarchy/{roomId}`
//!
//! Endpoint to get the children of a given space.
pub mod v1 {
//! `/v1/` ([spec])
//!
//! [spec]: https://spec.matrix.org/v1.2/server-server-api/#get_matrixfederationv1hierarchyroomid
use ruma_api::ruma_api;
use ruma_identifiers::RoomId;
use crate::space::{SpaceHierarchyChildSummary, SpaceHierarchyParentSummary};
ruma_api! {
metadata: {
description: "Get the space tree in a depth-first manner to locate child rooms of a given space.",
name: "hierarchy",
method: GET,
unstable_path: "/_matrix/federation/unstable/org.matrix.msc2946/hierarchy/:room_id",
stable_path: "/_matrix/federation/v1/hierarchy/:room_id",
rate_limited: false,
authentication: ServerSignatures,
added: 1.2,
}
request: {
/// The room ID of the space to get a hierarchy for.
#[ruma_api(path)]
pub room_id: &'a RoomId,
/// Whether or not the server should only consider suggested rooms.
///
/// Suggested rooms are annotated in their `m.space.child` event contents.
#[ruma_api(query)]
#[serde(default, skip_serializing_if = "ruma_serde::is_default")]
pub suggested_only: bool,
}
response: {
/// A summary of the spaces children.
///
/// Rooms which the requesting server cannot peek/join will be excluded.
pub children: Vec<SpaceHierarchyChildSummary>,
/// The list of room IDs the requesting server doesnt have a viable way to peek/join.
///
/// Rooms which the responding server cannot provide details on will be outright
/// excluded from the response instead.
pub inaccessible_children: Vec<Box<RoomId>>,
/// A summary of the requested room.
pub room: SpaceHierarchyParentSummary,
}
}
impl<'a> Request<'a> {
/// Creates a `Request` with the given room ID.
pub fn new(room_id: &'a RoomId) -> Self {
Self { room_id, suggested_only: false }
}
}
impl Response {
/// Creates a new `Response` with the given room summary.
pub fn new(room_summary: SpaceHierarchyParentSummary) -> Self {
Self { children: Vec::new(), inaccessible_children: Vec::new(), room: room_summary }
}
}
}