use std::{cmp::Ordering, collections::HashMap, sync::Arc};
use eyeball::{ObservableWriteGuard, SharedObservable, Subscriber};
use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
use futures_util::pin_mut;
use imbl::Vector;
use itertools::Itertools;
use matrix_sdk::{
Client, Error, locks::Mutex, paginators::PaginationToken, task_monitor::BackgroundTaskHandle,
};
use ruma::{
OwnedRoomId,
api::client::space::get_hierarchy,
events::space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
uint,
};
use tokio::sync::Mutex as AsyncMutex;
use tracing::{error, warn};
use crate::spaces::SpaceRoom;
#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum SpaceRoomListPaginationState {
Idle { end_reached: bool },
Loading,
}
pub struct SpaceRoomList {
client: Client,
space_id: OwnedRoomId,
space: SharedObservable<Option<SpaceRoom>>,
children_state: Mutex<Option<HashMap<OwnedRoomId, HierarchySpaceChildEvent>>>,
token: AsyncMutex<PaginationToken>,
pagination_state: SharedObservable<SpaceRoomListPaginationState>,
rooms: Arc<Mutex<ObservableVector<SpaceRoom>>>,
_space_update_handle: Option<BackgroundTaskHandle>,
_room_update_handle: BackgroundTaskHandle,
}
impl SpaceRoomList {
pub async fn new(client: Client, space_id: OwnedRoomId) -> Self {
let rooms = Arc::new(Mutex::new(ObservableVector::<SpaceRoom>::new()));
let all_room_updates_receiver = client.subscribe_to_all_room_updates();
let room_update_handle = client
.task_monitor()
.spawn_infinite_task("space_room_list::room_updates", {
let client = client.clone();
let rooms = rooms.clone();
async move {
pin_mut!(all_room_updates_receiver);
loop {
match all_room_updates_receiver.recv().await {
Ok(updates) => {
if updates.is_empty() {
continue;
}
let mut mutable_rooms = rooms.lock();
updates.iter_all_room_ids().for_each(|updated_room_id| {
if let Some((position, room)) = mutable_rooms
.clone()
.iter()
.find_position(|room| &room.room_id == updated_room_id)
&& let Some(updated_room) = client.get_room(updated_room_id)
{
mutable_rooms.set(
position,
SpaceRoom::new_from_known(
&updated_room,
room.children_count,
),
);
}
})
}
Err(err) => {
error!("error when listening to room updates: {err}");
}
}
}
}
})
.abort_on_drop();
let space_observable = SharedObservable::new(None);
let (space_room, space_update_handle) = if let Some(parent) = client.get_room(&space_id) {
let children_count = parent
.get_state_events_static::<SpaceChildEventContent>()
.await
.map_or(0, |c| c.len() as u64);
let mut subscriber = parent.subscribe_info();
let space_update_handle = client
.task_monitor()
.spawn_infinite_task("space_room_list::space_update", {
let client = client.clone();
let space_id = space_id.clone();
let space_observable = space_observable.clone();
async move {
while subscriber.next().await.is_some() {
if let Some(room) = client.get_room(&space_id) {
space_observable
.set(Some(SpaceRoom::new_from_known(&room, children_count)));
}
}
}
})
.abort_on_drop();
(Some(SpaceRoom::new_from_known(&parent, children_count)), Some(space_update_handle))
} else {
(None, None)
};
space_observable.set(space_room);
Self {
client,
space_id,
space: space_observable,
children_state: Mutex::new(None),
token: AsyncMutex::new(None.into()),
pagination_state: SharedObservable::new(SpaceRoomListPaginationState::Idle {
end_reached: false,
}),
rooms,
_space_update_handle: space_update_handle,
_room_update_handle: room_update_handle,
}
}
pub fn space(&self) -> Option<SpaceRoom> {
self.space.get()
}
pub fn subscribe_to_space_updates(&self) -> Subscriber<Option<SpaceRoom>> {
self.space.subscribe()
}
pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
self.pagination_state.get()
}
pub fn subscribe_to_pagination_state_updates(
&self,
) -> Subscriber<SpaceRoomListPaginationState> {
self.pagination_state.subscribe()
}
pub fn rooms(&self) -> Vec<SpaceRoom> {
self.rooms.lock().iter().cloned().collect_vec()
}
pub fn subscribe_to_room_updates(
&self,
) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
self.rooms.lock().subscribe().into_values_and_batched_stream()
}
pub async fn paginate(&self) -> Result<(), Error> {
{
let mut pagination_state = self.pagination_state.write();
match *pagination_state {
SpaceRoomListPaginationState::Idle { end_reached } if end_reached => {
return Ok(());
}
SpaceRoomListPaginationState::Loading => {
return Ok(());
}
_ => {}
}
ObservableWriteGuard::set(&mut pagination_state, SpaceRoomListPaginationState::Loading);
}
let mut request = get_hierarchy::v1::Request::new(self.space_id.clone());
request.max_depth = Some(uint!(1));
let mut pagination_token = self.token.lock().await;
if let PaginationToken::HasMore(ref token) = *pagination_token {
request.from = Some(token.clone());
}
match self.client.send(request).await {
Ok(result) => {
*pagination_token = match &result.next_batch {
Some(val) => PaginationToken::HasMore(val.clone()),
None => PaginationToken::HitEnd,
};
let mut rooms = self.rooms.lock();
let (space, children): (Vec<_>, Vec<_>) =
result.rooms.into_iter().partition(|f| f.summary.room_id == self.space_id);
if let Some(room) = space.first() {
let mut children_state =
HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
for child_state in &room.children_state {
match child_state.deserialize() {
Ok(child) => {
children_state.insert(child.state_key.clone(), child.clone());
}
Err(error) => {
warn!("Failed deserializing space child event: {error}");
}
}
}
*self.children_state.lock() = Some(children_state);
let mut space = self.space.write();
if space.is_none() {
ObservableWriteGuard::set(
&mut space,
Some(SpaceRoom::new_from_summary(
&room.summary,
self.client.get_room(&room.summary.room_id),
room.children_state.len() as u64,
vec![],
false,
)),
);
}
}
let children_state = (*self.children_state.lock()).clone().unwrap_or_default();
children
.iter()
.map(|room| {
let child_state = children_state.get(&room.summary.room_id);
let via =
child_state.map(|state| state.content.via.clone()).unwrap_or_default();
let suggested =
child_state.map(|state| state.content.suggested).unwrap_or(false);
SpaceRoom::new_from_summary(
&room.summary,
self.client.get_room(&room.summary.room_id),
room.children_state.len() as u64,
via,
suggested,
)
})
.sorted_by(|a, b| Self::compare_rooms(a, b, &children_state))
.for_each(|room| rooms.push_back(room));
self.pagination_state.set(SpaceRoomListPaginationState::Idle {
end_reached: result.next_batch.is_none(),
});
Ok(())
}
Err(err) => {
self.pagination_state
.set(SpaceRoomListPaginationState::Idle { end_reached: false });
Err(err.into())
}
}
}
pub async fn reset(&self) {
let mut pagination_token = self.token.lock().await;
*pagination_token = None.into();
self.rooms.lock().clear();
self.children_state.lock().take();
self.pagination_state.set(SpaceRoomListPaginationState::Idle { end_reached: false });
}
fn compare_rooms(
a: &SpaceRoom,
b: &SpaceRoom,
children_state: &HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
) -> Ordering {
let a_state = children_state.get(&a.room_id);
let b_state = children_state.get(&b.room_id);
SpaceRoom::compare_rooms(
(&a.room_id, a_state.map(Into::into).as_ref()),
(&b.room_id, b_state.map(Into::into).as_ref()),
)
}
}
#[cfg(test)]
mod tests {
use std::{cmp::Ordering, collections::HashMap};
use assert_matches2::{assert_let, assert_matches};
use eyeball_im::VectorDiff;
use futures_util::pin_mut;
use matrix_sdk::{RoomState, test_utils::mocks::MatrixMockServer};
use matrix_sdk_test::{
JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
};
use ruma::{
MilliSecondsSinceUnixEpoch, OwnedRoomId, RoomId,
events::space::child::HierarchySpaceChildEvent,
owned_room_id, owned_server_name,
room::{JoinRuleSummary, RoomSummary},
room_id, server_name, uint,
};
use serde_json::{from_value, json};
use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
use wiremock::ResponseTemplate;
use crate::spaces::{
SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
};
#[async_test]
async fn test_room_list_pagination() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let user_id = client.user_id().unwrap();
let space_service = SpaceService::new(client.clone()).await;
let factory = EventFactory::new();
server.mock_room_state_encryption().plain().mount().await;
let parent_space_id = room_id!("!parent_space:example.org");
let child_space_id_1 = room_id!("!1:example.org");
let child_space_id_2 = room_id!("!2:example.org");
server
.sync_room(
&client,
JoinedRoomBuilder::new(parent_space_id)
.add_state_event(
factory
.space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
.sender(user_id),
)
.add_state_event(
factory
.space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
.sender(user_id),
),
)
.await;
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
assert_let!(Some(parent_space) = room_list.space());
assert_eq!(parent_space.children_count, 2);
assert_matches!(
room_list.pagination_state(),
SpaceRoomListPaginationState::Idle { end_reached: false }
);
assert_eq!(room_list.rooms(), vec![]);
let pagination_state_subscriber = room_list.subscribe_to_pagination_state_updates();
pin_mut!(pagination_state_subscriber);
assert_pending!(pagination_state_subscriber);
let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
pin_mut!(rooms_subscriber);
assert_pending!(rooms_subscriber);
server
.mock_get_hierarchy()
.ok_with_room_ids_and_children_state(
vec![child_space_id_1, child_space_id_2],
vec![(room_id!("!child:example.org"), vec![])],
)
.mount()
.await;
room_list.paginate().await.unwrap();
assert_next_matches!(
pagination_state_subscriber,
SpaceRoomListPaginationState::Idle { end_reached: true }
);
assert_next_eq!(
rooms_subscriber,
vec![
VectorDiff::PushBack {
value: SpaceRoom::new_from_summary(
&RoomSummary::new(
child_space_id_1.to_owned(),
JoinRuleSummary::Public,
false,
uint!(1),
false,
),
None,
1,
vec![],
false,
)
},
VectorDiff::PushBack {
value: SpaceRoom::new_from_summary(
&RoomSummary::new(
child_space_id_2.to_owned(),
JoinRuleSummary::Public,
false,
uint!(1),
false,
),
None,
1,
vec![],
false,
),
}
]
);
}
#[async_test]
async fn test_room_state_updates() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let space_service = SpaceService::new(client.clone()).await;
let parent_space_id = room_id!("!parent_space:example.org");
let child_room_id_1 = room_id!("!1:example.org");
let child_room_id_2 = room_id!("!2:example.org");
server
.mock_get_hierarchy()
.ok_with_room_ids(vec![child_room_id_1, child_room_id_2])
.mount()
.await;
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
room_list.paginate().await.unwrap();
assert_eq!(room_list.rooms().first().unwrap().room_id, child_room_id_1);
assert_eq!(room_list.rooms().last().unwrap().room_id, child_room_id_2);
assert_eq!(room_list.rooms().first().unwrap().state, None);
assert_eq!(room_list.rooms().last().unwrap().state, None);
let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
pin_mut!(rooms_subscriber);
assert_pending!(rooms_subscriber);
server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_1)).await;
assert_ready!(rooms_subscriber);
assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
assert_eq!(room_list.rooms().last().unwrap().state, None);
server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_2)).await;
assert_ready!(rooms_subscriber);
assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Joined));
server.sync_room(&client, LeftRoomBuilder::new(child_room_id_1)).await;
server.sync_room(&client, LeftRoomBuilder::new(child_room_id_2)).await;
assert_ready!(rooms_subscriber);
assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Left));
assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Left));
}
#[async_test]
async fn test_parent_space_updates() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let user_id = client.user_id().unwrap();
let space_service = SpaceService::new(client.clone()).await;
let factory = EventFactory::new();
server.mock_room_state_encryption().plain().mount().await;
let parent_space_id = room_id!("!parent_space:example.org");
let child_space_id_1 = room_id!("!1:example.org");
let child_space_id_2 = room_id!("!2:example.org");
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
assert!(room_list.space().is_none());
let parent_space_subscriber = room_list.subscribe_to_space_updates();
pin_mut!(parent_space_subscriber);
assert_pending!(parent_space_subscriber);
server
.mock_get_hierarchy()
.ok_with_room_ids_and_children_state(
vec![parent_space_id, child_space_id_1, child_space_id_2],
vec![(
room_id!("!child:example.org"),
vec![server_name!("matrix-client.example.org")],
)],
)
.mount()
.await;
room_list.paginate().await.unwrap();
assert_let!(Some(parent_space) = room_list.space());
assert_eq!(parent_space.room_id, parent_space_id);
assert_next_eq!(parent_space_subscriber, Some(parent_space));
server
.sync_room(
&client,
JoinedRoomBuilder::new(parent_space_id)
.add_state_event(
factory
.space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
.sender(user_id),
)
.add_state_event(
factory
.space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
.sender(user_id),
),
)
.await;
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
assert_let!(Some(parent_space) = room_list.space());
assert_eq!(parent_space.children_count, 2);
}
#[async_test]
async fn test_parent_space_room_info_update() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let user_id = client.user_id().unwrap();
let space_service = SpaceService::new(client.clone()).await;
let factory = EventFactory::new();
server.mock_room_state_encryption().plain().mount().await;
let parent_space_id = room_id!("!parent_space:example.org");
server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
assert_let!(Some(parent_space) = room_list.space());
let parent_space_subscriber = room_list.subscribe_to_space_updates();
pin_mut!(parent_space_subscriber);
assert_pending!(parent_space_subscriber);
server
.sync_room(
&client,
JoinedRoomBuilder::new(parent_space_id)
.add_state_event(factory.room_topic("New room topic").sender(user_id))
.add_state_event(factory.room_name("New room name").sender(user_id)),
)
.await;
let mut updated_parent_space = parent_space.clone();
updated_parent_space.topic = Some("New room topic".to_owned());
updated_parent_space.name = Some("New room name".to_owned());
updated_parent_space.display_name = "New room name".to_owned();
assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
}
#[async_test]
async fn test_via_retrieval() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let space_service = SpaceService::new(client.clone()).await;
server.mock_room_state_encryption().plain().mount().await;
let parent_space_id = room_id!("!parent_space:example.org");
let child_space_id_1 = room_id!("!1:example.org");
let child_space_id_2 = room_id!("!2:example.org");
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
pin_mut!(rooms_subscriber);
server
.mock_get_hierarchy()
.ok_with_room_ids_and_children_state(
vec![parent_space_id, child_space_id_1, child_space_id_2],
vec![
(child_space_id_1, vec![server_name!("matrix-client.example.org")]),
(child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
],
)
.mount()
.await;
room_list.paginate().await.unwrap();
assert_next_eq!(
rooms_subscriber,
vec![
VectorDiff::PushBack {
value: SpaceRoom::new_from_summary(
&RoomSummary::new(
child_space_id_1.to_owned(),
JoinRuleSummary::Public,
false,
uint!(1),
false,
),
None,
2,
vec![owned_server_name!("matrix-client.example.org")],
false,
)
},
VectorDiff::PushBack {
value: SpaceRoom::new_from_summary(
&RoomSummary::new(
child_space_id_2.to_owned(),
JoinRuleSummary::Public,
false,
uint!(1),
false,
),
None,
2,
vec![owned_server_name!("other-matrix-client.example.org")],
false,
),
}
]
);
}
#[async_test]
async fn test_suggested_field() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let space_service = SpaceService::new(client.clone()).await;
server.mock_room_state_encryption().plain().mount().await;
let parent_space_id = room_id!("!parent_space:example.org");
let suggested_child = room_id!("!suggested:example.org");
let not_suggested_child = room_id!("!not_suggested:example.org");
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
pin_mut!(rooms_subscriber);
let children_state = vec![
json!({
"type": "m.space.child",
"state_key": suggested_child,
"content": { "via": ["example.org"], "suggested": true },
"sender": "@admin:example.org",
"origin_server_ts": MilliSecondsSinceUnixEpoch::now()
}),
json!({
"type": "m.space.child",
"state_key": not_suggested_child,
"content": { "via": ["example.org"], "suggested": false },
"sender": "@admin:example.org",
"origin_server_ts": MilliSecondsSinceUnixEpoch::now()
}),
];
server
.mock_get_hierarchy()
.respond_with(ResponseTemplate::new(200).set_body_json(json!({
"rooms": [
{
"room_id": parent_space_id,
"num_joined_members": 1,
"world_readable": false,
"guest_can_join": false,
"children_state": children_state
},
{
"room_id": suggested_child,
"num_joined_members": 5,
"world_readable": false,
"guest_can_join": false,
"children_state": []
},
{
"room_id": not_suggested_child,
"num_joined_members": 3,
"world_readable": false,
"guest_can_join": false,
"children_state": []
},
]
})))
.mount()
.await;
room_list.paginate().await.unwrap();
let rooms_diff = assert_next_matches!(rooms_subscriber, diff => diff);
assert_eq!(rooms_diff.len(), 2);
let mut rooms_by_id = HashMap::new();
for diff in rooms_diff {
if let VectorDiff::PushBack { value } = diff {
rooms_by_id.insert(value.room_id.clone(), value);
} else {
panic!("Expected PushBack, got {:?}", diff);
}
}
let suggested_room = rooms_by_id.get(suggested_child).expect("suggested child not found");
assert!(
suggested_room.suggested,
"Room with 'suggested: true' should have suggested == true"
);
let not_suggested_room =
rooms_by_id.get(not_suggested_child).expect("not-suggested child not found");
assert!(
!not_suggested_room.suggested,
"Room with 'suggested: false' should have suggested == false"
);
}
#[async_test]
async fn test_room_list_sorting() {
let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
&make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
&children_state,
),
Ordering::Less
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
&make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
&children_state,
),
Ordering::Greater
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
&make_space_room(
owned_room_id!("!Marțolea:a.b"),
None,
Some(0),
&mut children_state
),
&children_state,
),
Ordering::Greater
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(
owned_room_id!("!Joiana:a.b"),
Some("last"),
Some(123),
&mut children_state
),
&make_space_room(
owned_room_id!("!Mioara:a.b"),
Some("first"),
Some(234),
&mut children_state
),
&children_state,
),
Ordering::Greater
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(
owned_room_id!("!Joiana:a.b"),
Some("Same pasture"),
Some(1),
&mut children_state
),
&make_space_room(
owned_room_id!("!Mioara:a.b"),
Some("Same pasture"),
Some(0),
&mut children_state
),
&children_state,
),
Ordering::Greater
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(
owned_room_id!("!Joiana:a.b"),
Some("same_pasture"),
Some(0),
&mut children_state
),
&make_space_room(
owned_room_id!("!Mioara:a.b"),
Some("same_pasture"),
Some(0),
&mut children_state
),
&children_state,
),
Ordering::Less
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
&make_space_room(
owned_room_id!("!Sâmbotina:a.b"),
None,
Some(0),
&mut children_state
),
&children_state,
),
Ordering::Greater
);
assert_eq!(
SpaceRoomList::compare_rooms(
&make_space_room(
owned_room_id!("!Sâmbotina:a.b"),
None,
Some(1),
&mut children_state
),
&make_space_room(
owned_room_id!("!Dumana:a.b"),
Some("Some pasture"),
Some(1),
&mut children_state
),
&children_state,
),
Ordering::Greater
);
}
#[async_test]
async fn test_reset() {
let server = MatrixMockServer::new().await;
let client = server.client_builder().build().await;
let space_service = SpaceService::new(client.clone()).await;
let parent_space_id = room_id!("!parent_space:example.org");
let child_space_id_1 = room_id!("!1:example.org");
server
.mock_get_hierarchy()
.ok_with_room_ids(vec![child_space_id_1])
.expect(2)
.mount()
.await;
let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
room_list.paginate().await.unwrap();
assert_eq!(room_list.rooms().len(), 1);
room_list.reset().await;
assert_eq!(room_list.rooms().len(), 0);
assert_matches!(
room_list.pagination_state(),
SpaceRoomListPaginationState::Idle { end_reached: false }
);
room_list.paginate().await.unwrap();
assert_eq!(room_list.rooms().len(), 1);
}
fn make_space_room(
room_id: OwnedRoomId,
order: Option<&str>,
origin_server_ts: Option<u32>,
children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
) -> SpaceRoom {
if let Some(origin_server_ts) = origin_server_ts {
children_state.insert(
room_id.clone(),
hierarchy_space_child_event(&room_id, order, origin_server_ts),
);
}
SpaceRoom {
room_id,
canonical_alias: None,
name: Some("New room name".to_owned()),
display_name: "Empty room".to_owned(),
topic: None,
avatar_url: None,
room_type: None,
num_joined_members: 0,
join_rule: None,
world_readable: None,
guest_can_join: false,
is_direct: None,
children_count: 0,
state: None,
heroes: None,
via: vec![],
suggested: false,
is_dm: None,
}
}
fn hierarchy_space_child_event(
room_id: &RoomId,
order: Option<&str>,
origin_server_ts: u32,
) -> HierarchySpaceChildEvent {
let mut json = json!({
"content": {
"via": []
},
"origin_server_ts": origin_server_ts,
"sender": "@bob:a.b",
"state_key": room_id.to_string(),
"type": "m.space.child"
});
if let Some(order) = order {
json["content"]["order"] = json!(order);
}
from_value::<HierarchySpaceChildEvent>(json).unwrap()
}
}