1use std::{cmp::Ordering, collections::HashMap, sync::Arc};
31
32use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
33use futures_util::pin_mut;
34use imbl::Vector;
35use itertools::Itertools;
36use matrix_sdk::{
37 Client, Error as SDKError, Room, deserialized_responses::SyncOrStrippedState,
38 executor::AbortOnDrop,
39};
40use matrix_sdk_common::executor::spawn;
41use ruma::{
42 OwnedRoomId, RoomId,
43 events::{
44 self, StateEventType, SyncStateEvent,
45 space::{child::SpaceChildEventContent, parent::SpaceParentEventContent},
46 },
47};
48use thiserror::Error;
49use tokio::sync::Mutex as AsyncMutex;
50use tracing::{error, trace, warn};
51
52use crate::spaces::{graph::SpaceGraph, leave::LeaveSpaceHandle, room::SpaceRoomChildState};
53pub use crate::spaces::{room::SpaceRoom, room_list::SpaceRoomList};
54
55pub mod graph;
56pub mod leave;
57pub mod room;
58pub mod room_list;
59
60#[derive(Debug, Error)]
62pub enum Error {
63 #[error("User ID not available from client")]
65 UserIdNotFound,
66
67 #[error("Room `{0}` not found")]
69 RoomNotFound(OwnedRoomId),
70
71 #[error("Missing `{0}` for `{1}`")]
73 MissingState(StateEventType, OwnedRoomId),
74
75 #[error("Failed to set either of the m.space.parent or m.space.child state events")]
78 UpdateRelationship(SDKError),
79
80 #[error(
83 "Failed to set the expected m.space.parent state event (but any m.space.child changes were successful)"
84 )]
85 UpdateInverseRelationship(SDKError),
86
87 #[error("Failed to leave space")]
89 LeaveSpace(SDKError),
90
91 #[error("Failed to load members")]
93 LoadRoomMembers(SDKError),
94}
95
96struct SpaceState {
97 graph: SpaceGraph,
98 top_level_joined_spaces: ObservableVector<SpaceRoom>,
99 space_filters: ObservableVector<SpaceFilter>,
100}
101
102pub struct SpaceService {
142 client: Client,
143
144 space_state: Arc<AsyncMutex<SpaceState>>,
145
146 _room_update_handle: AsyncMutex<AbortOnDrop<()>>,
147}
148
149impl SpaceService {
150 pub async fn new(client: Client) -> Self {
152 let space_state = Arc::new(AsyncMutex::new(SpaceState {
153 graph: SpaceGraph::new(),
154 top_level_joined_spaces: ObservableVector::new(),
155 space_filters: ObservableVector::new(),
156 }));
157
158 let room_update_handle = spawn({
159 let client = client.clone();
160 let space_state = Arc::clone(&space_state);
161 let all_room_updates_receiver = client.subscribe_to_all_room_updates();
162
163 async move {
164 pin_mut!(all_room_updates_receiver);
165
166 loop {
167 match all_room_updates_receiver.recv().await {
168 Ok(updates) => {
169 if updates.is_empty() {
170 continue;
171 }
172
173 let (spaces, filters, graph) = Self::build_space_state(&client).await;
174 Self::update_space_state_if_needed(
175 Vector::from(spaces),
176 Vector::from(filters),
177 graph,
178 &space_state,
179 )
180 .await;
181 }
182 Err(err) => {
183 error!("error when listening to room updates: {err}");
184 }
185 }
186 }
187 }
188 });
189
190 let (spaces, filters, graph) = Self::build_space_state(&client).await;
192 Self::update_space_state_if_needed(
193 Vector::from(spaces),
194 Vector::from(filters),
195 graph,
196 &space_state,
197 )
198 .await;
199
200 Self {
201 client,
202 space_state,
203 _room_update_handle: AsyncMutex::new(AbortOnDrop::new(room_update_handle)),
204 }
205 }
206
207 pub async fn subscribe_to_top_level_joined_spaces(
210 &self,
211 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
212 self.space_state
213 .lock()
214 .await
215 .top_level_joined_spaces
216 .subscribe()
217 .into_values_and_batched_stream()
218 }
219
220 pub async fn top_level_joined_spaces(&self) -> Vec<SpaceRoom> {
224 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
225
226 Self::update_space_state_if_needed(
227 Vector::from(top_level_joined_spaces.clone()),
228 Vector::from(filters),
229 graph,
230 &self.space_state,
231 )
232 .await;
233
234 top_level_joined_spaces
235 }
236
237 pub async fn space_filters(&self) -> Vec<SpaceFilter> {
282 let (top_level_joined_spaces, filters, graph) = Self::build_space_state(&self.client).await;
283
284 Self::update_space_state_if_needed(
285 Vector::from(top_level_joined_spaces),
286 Vector::from(filters.clone()),
287 graph,
288 &self.space_state,
289 )
290 .await;
291
292 filters
293 }
294
295 pub async fn subscribe_to_space_filters(
297 &self,
298 ) -> (Vector<SpaceFilter>, VectorSubscriberBatchedStream<SpaceFilter>) {
299 self.space_state.lock().await.space_filters.subscribe().into_values_and_batched_stream()
300 }
301
302 pub async fn editable_spaces(&self) -> Vec<SpaceRoom> {
308 let Some(user_id) = self.client.user_id() else {
309 return vec![];
310 };
311
312 let graph = &self.space_state.lock().await.graph;
313 let rooms = self.client.joined_space_rooms();
314
315 let mut editable_spaces = Vec::new();
316 for room in &rooms {
317 if let Ok(power_levels) = room.power_levels().await
318 && power_levels.user_can_send_state(user_id, StateEventType::SpaceChild)
319 {
320 let room_id = room.room_id();
321 editable_spaces
322 .push(SpaceRoom::new_from_known(room, graph.children_of(room_id).len() as u64));
323 }
324 }
325
326 editable_spaces
327 }
328
329 pub async fn space_room_list(&self, space_id: OwnedRoomId) -> SpaceRoomList {
331 SpaceRoomList::new(self.client.clone(), space_id).await
332 }
333
334 pub async fn joined_parents_of_child(&self, child_id: &RoomId) -> Vec<SpaceRoom> {
336 let graph = &self.space_state.lock().await.graph;
337
338 graph
339 .parents_of(child_id)
340 .into_iter()
341 .filter_map(|parent_id| self.client.get_room(parent_id))
342 .map(|room| {
343 SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64)
344 })
345 .collect()
346 }
347
348 pub async fn get_space_room(&self, room_id: &RoomId) -> Option<SpaceRoom> {
351 let graph = &self.space_state.lock().await.graph;
352
353 if graph.has_node(room_id)
354 && let Some(room) = self.client.get_room(room_id)
355 {
356 Some(SpaceRoom::new_from_known(&room, graph.children_of(room.room_id()).len() as u64))
357 } else {
358 None
359 }
360 }
361
362 pub async fn add_child_to_space(
363 &self,
364 child_id: OwnedRoomId,
365 space_id: OwnedRoomId,
366 ) -> Result<(), Error> {
367 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
368 let space_room =
369 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
370 let child_room =
371 self.client.get_room(&child_id).ok_or(Error::RoomNotFound(child_id.to_owned()))?;
372 let child_power_levels = child_room
373 .power_levels()
374 .await
375 .map_err(|error| Error::UpdateRelationship(matrix_sdk::Error::from(error)))?;
376
377 let child_route = child_room.route().await.map_err(Error::UpdateRelationship)?;
379 space_room
380 .send_state_event_for_key(&child_id, SpaceChildEventContent::new(child_route))
381 .await
382 .map_err(Error::UpdateRelationship)?;
383
384 if child_power_levels.user_can_send_state(user_id, StateEventType::SpaceParent) {
386 let parent_route =
387 space_room.route().await.map_err(Error::UpdateInverseRelationship)?;
388 child_room
389 .send_state_event_for_key(&space_id, SpaceParentEventContent::new(parent_route))
390 .await
391 .map_err(Error::UpdateInverseRelationship)?;
392 } else {
393 warn!("The current user doesn't have permission to set the child's parent.");
394 }
395
396 Ok(())
397 }
398
399 pub async fn remove_child_from_space(
400 &self,
401 child_id: OwnedRoomId,
402 space_id: OwnedRoomId,
403 ) -> Result<(), Error> {
404 let user_id = self.client.user_id().ok_or(Error::UserIdNotFound)?;
405 let space_room =
406 self.client.get_room(&space_id).ok_or(Error::RoomNotFound(space_id.to_owned()))?;
407
408 if let Ok(Some(_)) =
409 space_room.get_state_event_static_for_key::<SpaceChildEventContent, _>(&child_id).await
410 {
411 space_room
418 .send_state_event_raw("m.space.child", child_id.as_str(), serde_json::json!({}))
419 .await
420 .map_err(Error::UpdateRelationship)?;
421 } else {
422 warn!("A space child event wasn't found on the parent, ignoring.");
423 }
424
425 if let Some(child_room) = self.client.get_room(&child_id) {
426 let power_levels = child_room.power_levels().await.map_err(|error| {
427 Error::UpdateInverseRelationship(matrix_sdk::Error::from(error))
428 })?;
429
430 if power_levels.user_can_send_state(user_id, StateEventType::SpaceParent)
431 && let Ok(Some(_)) = child_room
432 .get_state_event_static_for_key::<SpaceParentEventContent, _>(&space_id)
433 .await
434 {
435 child_room
437 .send_state_event_raw(
438 "m.space.parent",
439 space_id.as_str(),
440 serde_json::json!({}),
441 )
442 .await
443 .map_err(Error::UpdateInverseRelationship)?;
444 } else {
445 warn!("A space parent event wasn't found on the child, ignoring.");
446 }
447 } else {
448 warn!("The child room is unknown, skipping m.space.parent removal.");
449 }
450
451 Ok(())
452 }
453
454 pub async fn leave_space(&self, space_id: &RoomId) -> Result<LeaveSpaceHandle, Error> {
462 let space_state = self.space_state.lock().await;
463
464 if !space_state.graph.has_node(space_id) {
465 return Err(Error::RoomNotFound(space_id.to_owned()));
466 }
467
468 let room_ids = space_state.graph.flattened_bottom_up_subtree(space_id);
469
470 let handle = LeaveSpaceHandle::new(self.client.clone(), room_ids).await;
471
472 Ok(handle)
473 }
474
475 async fn update_space_state_if_needed(
476 new_spaces: Vector<SpaceRoom>,
477 new_filters: Vector<SpaceFilter>,
478 new_graph: SpaceGraph,
479 space_state: &Arc<AsyncMutex<SpaceState>>,
480 ) {
481 let mut space_state = space_state.lock().await;
482
483 if new_spaces != space_state.top_level_joined_spaces.clone() {
484 space_state.top_level_joined_spaces.clear();
485 space_state.top_level_joined_spaces.append(new_spaces);
486 }
487
488 if new_filters != space_state.space_filters.clone() {
489 space_state.space_filters.clear();
490 space_state.space_filters.append(new_filters);
491 }
492
493 space_state.graph = new_graph;
494 }
495
496 async fn build_space_state(client: &Client) -> (Vec<SpaceRoom>, Vec<SpaceFilter>, SpaceGraph) {
497 let joined_spaces = client.joined_space_rooms();
498
499 let mut graph = SpaceGraph::new();
501
502 let mut space_child_states = HashMap::<OwnedRoomId, SpaceRoomChildState>::new();
504
505 for space in joined_spaces.iter() {
508 graph.add_node(space.room_id().to_owned());
509
510 if let Ok(parents) = space.get_state_events_static::<SpaceParentEventContent>().await {
511 parents.into_iter()
512 .flat_map(|parent_event| match parent_event.deserialize() {
513 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
514 Some(e.state_key)
515 }
516 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
517 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
518 Err(e) => {
519 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.parent: {e}");
520 None
521 }
522 }).for_each(|parent| graph.add_edge(parent, space.room_id().to_owned()));
523 } else {
524 error!(room_id = ?space.room_id(), "Could not get m.space.parent events");
525 }
526
527 if let Ok(children) = space.get_state_events_static::<SpaceChildEventContent>().await {
528 children.into_iter()
529 .filter_map(|child_event| match child_event.deserialize() {
530 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Original(e))) => {
531 space_child_states.insert(
532 e.state_key.to_owned(),
533 SpaceRoomChildState {
534 order: e.content.order.clone(),
535 origin_server_ts: e.origin_server_ts,
536 },
537 );
538
539 Some(e.state_key)
540 }
541 Ok(SyncOrStrippedState::Sync(SyncStateEvent::Redacted(_))) => None,
542 Ok(SyncOrStrippedState::Stripped(e)) => Some(e.state_key),
543 Err(e) => {
544 trace!(room_id = ?space.room_id(), "Could not deserialize m.space.child: {e}");
545 None
546 }
547 }).for_each(|child| graph.add_edge(space.room_id().to_owned(), child));
548 } else {
549 error!(room_id = ?space.room_id(), "Could not get m.space.child events");
550 }
551 }
552
553 graph.remove_cycles();
556
557 let root_nodes = graph.root_nodes();
558
559 let top_level_space_rooms = joined_spaces
563 .iter()
564 .filter(|room| root_nodes.contains(&room.room_id()))
565 .collect::<Vec<_>>();
566
567 let mut top_level_space_order = HashMap::new();
568 for space in &top_level_space_rooms {
569 if let Ok(Some(raw_event)) =
570 space.account_data_static::<events::space_order::SpaceOrderEventContent>().await
571 && let Ok(event) = raw_event.deserialize()
572 {
573 top_level_space_order.insert(space.room_id().to_owned(), event.content.order);
574 }
575 }
576
577 let top_level_space_rooms = top_level_space_rooms
578 .into_iter()
579 .sorted_by(|a, b| {
580 match (
582 top_level_space_order.get(a.room_id()),
583 top_level_space_order.get(b.room_id()),
584 ) {
585 (Some(a_order), Some(b_order)) => {
586 a_order.cmp(b_order).then(a.room_id().cmp(b.room_id()))
587 }
588 (Some(_), None) => Ordering::Less,
589 (None, Some(_)) => Ordering::Greater,
590 (None, None) => a.room_id().cmp(b.room_id()),
591 }
592 })
593 .collect::<Vec<_>>();
594
595 let top_level_spaces = top_level_space_rooms
596 .iter()
597 .map(|room| {
598 SpaceRoom::new_from_known(room, graph.children_of(room.room_id()).len() as u64)
599 })
600 .collect();
601
602 let space_filters =
603 Self::build_space_filters(client, &graph, top_level_space_rooms, space_child_states);
604
605 (top_level_spaces, space_filters, graph)
606 }
607
608 fn build_space_filters(
617 client: &Client,
618 graph: &SpaceGraph,
619 top_level_space_rooms: Vec<&Room>,
620 space_child_states: HashMap<OwnedRoomId, SpaceRoomChildState>,
621 ) -> Vec<SpaceFilter> {
622 let mut filters = Vec::new();
623 for top_level_space in top_level_space_rooms {
624 let children = graph
625 .children_of(top_level_space.room_id())
626 .into_iter()
627 .map(|id| id.to_owned())
628 .collect::<Vec<_>>();
629
630 filters.push(SpaceFilter {
631 space_room: SpaceRoom::new_from_known(top_level_space, children.len() as u64),
632 level: 0,
633 descendants: children.clone(),
634 });
635
636 filters.append(
637 &mut children
638 .iter()
639 .filter_map(|id| client.get_room(id))
640 .filter(|room| room.is_space())
641 .map(|room| {
642 SpaceRoom::new_from_known(
643 &room,
644 graph.children_of(room.room_id()).len() as u64,
645 )
646 })
647 .sorted_by(|a, b| {
648 let a_state = space_child_states.get(&a.room_id).cloned();
649 let b_state = space_child_states.get(&b.room_id).cloned();
650
651 SpaceRoom::compare_rooms(a, b, a_state, b_state)
652 })
653 .map(|space_room| {
654 let descendants = graph.flattened_bottom_up_subtree(&space_room.room_id);
655
656 SpaceFilter { space_room, level: 1, descendants }
657 })
658 .collect::<Vec<_>>(),
659 );
660 }
661
662 filters
663 }
664}
665
666#[derive(Debug, Clone, PartialEq)]
667pub struct SpaceFilter {
668 pub space_room: SpaceRoom,
670
671 pub level: u8,
674
675 pub descendants: Vec<OwnedRoomId>,
679}
680
681#[cfg(test)]
682mod tests {
683 use std::collections::BTreeMap;
684
685 use assert_matches2::assert_let;
686 use eyeball_im::VectorDiff;
687 use futures_util::{StreamExt, pin_mut};
688 use matrix_sdk::{room::ParentSpace, test_utils::mocks::MatrixMockServer};
689 use matrix_sdk_test::{
690 JoinedRoomBuilder, LeftRoomBuilder, RoomAccountDataTestEvent, async_test,
691 event_factory::EventFactory,
692 };
693 use ruma::{
694 MilliSecondsSinceUnixEpoch, RoomVersionId, UserId, event_id, owned_room_id, room_id,
695 serde::Raw,
696 };
697 use serde_json::json;
698 use stream_assert::{assert_next_eq, assert_pending};
699
700 use super::*;
701
702 #[async_test]
703 async fn test_spaces_hierarchy() {
704 let server = MatrixMockServer::new().await;
705 let client = server.client_builder().build().await;
706 let user_id = client.user_id().unwrap();
707 let space_service = SpaceService::new(client.clone()).await;
708 let factory = EventFactory::new();
709
710 server.mock_room_state_encryption().plain().mount().await;
711
712 let parent_space_id = room_id!("!parent_space:example.org");
715 let child_space_id_1 = room_id!("!child_space_1:example.org");
716 let child_space_id_2 = room_id!("!child_space_2:example.org");
717
718 add_space_rooms(
719 vec![
720 MockSpaceRoomParameters {
721 room_id: child_space_id_1,
722 order: None,
723 parents: vec![parent_space_id],
724 children: vec![],
725 power_level: None,
726 },
727 MockSpaceRoomParameters {
728 room_id: child_space_id_2,
729 order: None,
730 parents: vec![parent_space_id],
731 children: vec![],
732 power_level: None,
733 },
734 MockSpaceRoomParameters {
735 room_id: parent_space_id,
736 order: None,
737 parents: vec![],
738 children: vec![child_space_id_1, child_space_id_2],
739 power_level: None,
740 },
741 ],
742 &client,
743 &server,
744 &factory,
745 user_id,
746 )
747 .await;
748
749 assert_eq!(
751 space_service
752 .top_level_joined_spaces()
753 .await
754 .iter()
755 .map(|s| s.room_id.to_owned())
756 .collect::<Vec<_>>(),
757 vec![parent_space_id]
758 );
759
760 assert_eq!(
762 space_service
763 .top_level_joined_spaces()
764 .await
765 .iter()
766 .map(|s| s.children_count)
767 .collect::<Vec<_>>(),
768 vec![2]
769 );
770
771 let parent_space = client.get_room(parent_space_id).unwrap();
772 assert!(parent_space.is_space());
773
774 let spaces: Vec<ParentSpace> = client
777 .get_room(child_space_id_1)
778 .unwrap()
779 .parent_spaces()
780 .await
781 .unwrap()
782 .map(Result::unwrap)
783 .collect()
784 .await;
785
786 assert_let!(ParentSpace::Reciprocal(parent) = spaces.first().unwrap());
787 assert_eq!(parent.room_id(), parent_space.room_id());
788
789 let spaces: Vec<ParentSpace> = client
790 .get_room(child_space_id_2)
791 .unwrap()
792 .parent_spaces()
793 .await
794 .unwrap()
795 .map(Result::unwrap)
796 .collect()
797 .await;
798
799 assert_let!(ParentSpace::Reciprocal(parent) = spaces.last().unwrap());
800 assert_eq!(parent.room_id(), parent_space.room_id());
801 }
802
803 #[async_test]
804 async fn test_joined_spaces_updates() {
805 let server = MatrixMockServer::new().await;
806 let client = server.client_builder().build().await;
807 let user_id = client.user_id().unwrap();
808 let factory = EventFactory::new();
809
810 server.mock_room_state_encryption().plain().mount().await;
811
812 let first_space_id = room_id!("!first_space:example.org");
813 let second_space_id = room_id!("!second_space:example.org");
814
815 server
817 .sync_room(
818 &client,
819 JoinedRoomBuilder::new(first_space_id)
820 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type()),
821 )
822 .await;
823
824 let space_service = SpaceService::new(client.clone()).await;
828
829 let (initial_values, joined_spaces_subscriber) =
830 space_service.subscribe_to_top_level_joined_spaces().await;
831 pin_mut!(joined_spaces_subscriber);
832 assert_pending!(joined_spaces_subscriber);
833
834 assert_eq!(
835 initial_values,
836 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)].into()
837 );
838
839 assert_eq!(
840 space_service.top_level_joined_spaces().await,
841 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
842 );
843
844 assert_pending!(joined_spaces_subscriber);
847
848 server
851 .sync_room(
852 &client,
853 JoinedRoomBuilder::new(second_space_id)
854 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type())
855 .add_state_event(
856 factory
857 .space_child(
858 second_space_id.to_owned(),
859 owned_room_id!("!child:example.org"),
860 )
861 .sender(user_id),
862 ),
863 )
864 .await;
865
866 assert_eq!(
868 space_service.top_level_joined_spaces().await,
869 vec![
870 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
871 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
872 ]
873 );
874
875 assert_next_eq!(
876 joined_spaces_subscriber,
877 vec![
878 VectorDiff::Clear,
879 VectorDiff::Append {
880 values: vec![
881 SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0),
882 SpaceRoom::new_from_known(&client.get_room(second_space_id).unwrap(), 1)
883 ]
884 .into()
885 },
886 ]
887 );
888
889 server.sync_room(&client, LeftRoomBuilder::new(second_space_id)).await;
890
891 assert_next_eq!(
893 joined_spaces_subscriber,
894 vec![
895 VectorDiff::Clear,
896 VectorDiff::Append {
897 values: vec![SpaceRoom::new_from_known(
898 &client.get_room(first_space_id).unwrap(),
899 0
900 )]
901 .into()
902 },
903 ]
904 );
905
906 server
908 .sync_room(
909 &client,
910 JoinedRoomBuilder::new(room_id!("!room:example.org"))
911 .add_state_event(factory.create(user_id, RoomVersionId::V1)),
912 )
913 .await;
914
915 assert_pending!(joined_spaces_subscriber);
917 assert_eq!(
918 space_service.top_level_joined_spaces().await,
919 vec![SpaceRoom::new_from_known(&client.get_room(first_space_id).unwrap(), 0)]
920 );
921 }
922
923 #[async_test]
924 async fn test_space_filters() {
925 let server = MatrixMockServer::new().await;
926 let client = server.client_builder().build().await;
927
928 server.mock_room_state_encryption().plain().mount().await;
929
930 add_space_rooms(
931 vec![
932 MockSpaceRoomParameters {
933 room_id: room_id!("!1:a.b"),
934 order: None,
935 parents: vec![],
936 children: vec![],
937 power_level: None,
938 },
939 MockSpaceRoomParameters {
940 room_id: room_id!("!1.2:a.b"),
941 order: None,
942 parents: vec![room_id!("!1:a.b")],
943 children: vec![],
944 power_level: None,
945 },
946 MockSpaceRoomParameters {
947 room_id: room_id!("!1.2.3:a.b"),
948 order: None,
949 parents: vec![room_id!("!1.2:a.b")],
950 children: vec![],
951 power_level: None,
952 },
953 MockSpaceRoomParameters {
954 room_id: room_id!("!1.2.3.4:a.b"),
955 order: None,
956 parents: vec![room_id!("!1.2.3:a.b")],
957 children: vec![],
958 power_level: None,
959 },
960 ],
961 &client,
962 &server,
963 &EventFactory::new(),
964 client.user_id().unwrap(),
965 )
966 .await;
967
968 let space_service = SpaceService::new(client.clone()).await;
969
970 let filters = space_service.space_filters().await;
971 assert_eq!(filters.len(), 2);
972 assert_eq!(filters[0].space_room.room_id, room_id!("!1:a.b"));
973 assert_eq!(filters[0].level, 0);
974 assert_eq!(filters[0].descendants.len(), 1); assert_eq!(filters[1].space_room.room_id, room_id!("!1.2:a.b"));
976 assert_eq!(filters[1].level, 1);
977 assert_eq!(filters[1].descendants.len(), 3);
978
979 let (initial_values, space_filters_subscriber) =
980 space_service.subscribe_to_space_filters().await;
981 pin_mut!(space_filters_subscriber);
982 assert_pending!(space_filters_subscriber);
983
984 assert_eq!(initial_values, filters.into());
985
986 add_space_rooms(
987 vec![MockSpaceRoomParameters {
988 room_id: room_id!("!1.2.3.4.5:a.b"),
989 order: None,
990 parents: vec![room_id!("!1.2.3.4:a.b")],
991 children: vec![],
992 power_level: None,
993 }],
994 &client,
995 &server,
996 &EventFactory::new(),
997 client.user_id().unwrap(),
998 )
999 .await;
1000
1001 space_filters_subscriber.next().await;
1002
1003 let filters = space_service.space_filters().await;
1004 assert_eq!(filters[0].descendants.len(), 1);
1005 assert_eq!(filters[1].descendants.len(), 4);
1006 }
1007
1008 #[async_test]
1009 async fn test_top_level_space_order() {
1010 let server = MatrixMockServer::new().await;
1011 let client = server.client_builder().build().await;
1012
1013 server.mock_room_state_encryption().plain().mount().await;
1014
1015 add_space_rooms(
1016 vec![
1017 MockSpaceRoomParameters {
1018 room_id: room_id!("!2:a.b"),
1019 order: Some("2"),
1020 parents: vec![],
1021 children: vec![],
1022 power_level: None,
1023 },
1024 MockSpaceRoomParameters {
1025 room_id: room_id!("!4:a.b"),
1026 order: None,
1027 parents: vec![],
1028 children: vec![],
1029 power_level: None,
1030 },
1031 MockSpaceRoomParameters {
1032 room_id: room_id!("!3:a.b"),
1033 order: None,
1034 parents: vec![],
1035 children: vec![],
1036 power_level: None,
1037 },
1038 MockSpaceRoomParameters {
1039 room_id: room_id!("!1:a.b"),
1040 order: Some("1"),
1041 parents: vec![],
1042 children: vec![],
1043 power_level: None,
1044 },
1045 ],
1046 &client,
1047 &server,
1048 &EventFactory::new(),
1049 client.user_id().unwrap(),
1050 )
1051 .await;
1052
1053 let space_service = SpaceService::new(client.clone()).await;
1054
1055 assert_eq!(
1058 space_service.top_level_joined_spaces().await,
1059 vec![
1060 SpaceRoom::new_from_known(&client.get_room(room_id!("!1:a.b")).unwrap(), 0),
1061 SpaceRoom::new_from_known(&client.get_room(room_id!("!2:a.b")).unwrap(), 0),
1062 SpaceRoom::new_from_known(&client.get_room(room_id!("!3:a.b")).unwrap(), 0),
1063 SpaceRoom::new_from_known(&client.get_room(room_id!("!4:a.b")).unwrap(), 0),
1064 ]
1065 );
1066 }
1067
1068 #[async_test]
1069 async fn test_editable_spaces() {
1070 let server = MatrixMockServer::new().await;
1072 let client = server.client_builder().build().await;
1073 let user_id = client.user_id().unwrap();
1074 let factory = EventFactory::new();
1075
1076 server.mock_room_state_encryption().plain().mount().await;
1077
1078 let admin_space_id = room_id!("!admin_space:example.org");
1079 let admin_subspace_id = room_id!("!admin_subspace:example.org");
1080 let regular_space_id = room_id!("!regular_space:example.org");
1081 let regular_subspace_id = room_id!("!regular_subspace:example.org");
1082
1083 add_space_rooms(
1084 vec![
1085 MockSpaceRoomParameters {
1086 room_id: admin_space_id,
1087 order: None,
1088 parents: vec![],
1089 children: vec![regular_subspace_id],
1090 power_level: Some(100),
1091 },
1092 MockSpaceRoomParameters {
1093 room_id: admin_subspace_id,
1094 order: None,
1095 parents: vec![regular_space_id],
1096 children: vec![],
1097 power_level: Some(100),
1098 },
1099 MockSpaceRoomParameters {
1100 room_id: regular_space_id,
1101 order: None,
1102 parents: vec![],
1103 children: vec![admin_subspace_id],
1104 power_level: Some(0),
1105 },
1106 MockSpaceRoomParameters {
1107 room_id: regular_subspace_id,
1108 order: None,
1109 parents: vec![admin_space_id],
1110 children: vec![],
1111 power_level: Some(0),
1112 },
1113 ],
1114 &client,
1115 &server,
1116 &factory,
1117 user_id,
1118 )
1119 .await;
1120
1121 let space_service = SpaceService::new(client.clone()).await;
1122
1123 let editable_spaces = space_service.editable_spaces().await;
1125
1126 assert_eq!(
1128 editable_spaces.iter().map(|room| room.room_id.to_owned()).collect::<Vec<_>>(),
1129 vec![admin_space_id.to_owned(), admin_subspace_id.to_owned()]
1130 );
1131 }
1132
1133 #[async_test]
1134 async fn test_joined_parents_of_child() {
1135 let server = MatrixMockServer::new().await;
1137 let client = server.client_builder().build().await;
1138 let user_id = client.user_id().unwrap();
1139 let factory = EventFactory::new();
1140
1141 server.mock_room_state_encryption().plain().mount().await;
1142
1143 let parent_space_id_1 = room_id!("!parent_space_1:example.org");
1144 let parent_space_id_2 = room_id!("!parent_space_2:example.org");
1145 let unknown_parent_space_id = room_id!("!unknown_parent_space:example.org");
1146 let child_space_id = room_id!("!child_space:example.org");
1147
1148 add_space_rooms(
1149 vec![
1150 MockSpaceRoomParameters {
1151 room_id: child_space_id,
1152 order: None,
1153 parents: vec![parent_space_id_1, parent_space_id_2, unknown_parent_space_id],
1154 children: vec![],
1155 power_level: None,
1156 },
1157 MockSpaceRoomParameters {
1158 room_id: parent_space_id_1,
1159 order: None,
1160 parents: vec![],
1161 children: vec![child_space_id],
1162 power_level: None,
1163 },
1164 MockSpaceRoomParameters {
1165 room_id: parent_space_id_2,
1166 order: None,
1167 parents: vec![],
1168 children: vec![child_space_id],
1169 power_level: None,
1170 },
1171 ],
1172 &client,
1173 &server,
1174 &factory,
1175 user_id,
1176 )
1177 .await;
1178
1179 let space_service = SpaceService::new(client.clone()).await;
1180
1181 let parents = space_service.joined_parents_of_child(child_space_id).await;
1183
1184 assert_eq!(
1186 parents.iter().map(|space| space.room_id.to_owned()).collect::<Vec<_>>(),
1187 vec![parent_space_id_1, parent_space_id_2]
1188 );
1189 }
1190
1191 #[async_test]
1192 async fn test_get_space_room_for_id() {
1193 let server = MatrixMockServer::new().await;
1194 let client = server.client_builder().build().await;
1195 let user_id = client.user_id().unwrap();
1196 let factory = EventFactory::new();
1197
1198 server.mock_room_state_encryption().plain().mount().await;
1199
1200 let space_id = room_id!("!single_space:example.org");
1201
1202 add_space_rooms(
1203 vec![MockSpaceRoomParameters {
1204 room_id: space_id,
1205 order: None,
1206 parents: vec![],
1207 children: vec![],
1208 power_level: None,
1209 }],
1210 &client,
1211 &server,
1212 &factory,
1213 user_id,
1214 )
1215 .await;
1216
1217 let space_service = SpaceService::new(client.clone()).await;
1218
1219 let found = space_service.get_space_room(space_id).await;
1220 assert!(found.is_some());
1221
1222 let expected = SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0);
1223 assert_eq!(found.unwrap(), expected);
1224 }
1225
1226 #[async_test]
1227 async fn test_add_child_to_space() {
1228 let server = MatrixMockServer::new().await;
1230 let client = server.client_builder().build().await;
1231 let user_id = client.user_id().unwrap();
1232 let factory = EventFactory::new();
1233
1234 server.mock_room_state_encryption().plain().mount().await;
1235
1236 let space_child_event_id = event_id!("$1");
1237 let space_parent_event_id = event_id!("$2");
1238 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1239 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1240
1241 let space_id = room_id!("!my_space:example.org");
1242 let child_id = room_id!("!my_child:example.org");
1243
1244 add_space_rooms(
1245 vec![
1246 MockSpaceRoomParameters {
1247 room_id: space_id,
1248 order: None,
1249 parents: vec![],
1250 children: vec![],
1251 power_level: Some(100),
1252 },
1253 MockSpaceRoomParameters {
1254 room_id: child_id,
1255 order: None,
1256 parents: vec![],
1257 children: vec![],
1258 power_level: Some(100),
1259 },
1260 ],
1261 &client,
1262 &server,
1263 &factory,
1264 user_id,
1265 )
1266 .await;
1267
1268 let space_service = SpaceService::new(client.clone()).await;
1269
1270 let result =
1272 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1273
1274 assert!(result.is_ok());
1276 }
1277
1278 #[async_test]
1279 async fn test_add_child_to_space_without_space_admin() {
1280 let server = MatrixMockServer::new().await;
1282 let client = server.client_builder().build().await;
1283 let user_id = client.user_id().unwrap();
1284 let factory = EventFactory::new();
1285
1286 server.mock_room_state_encryption().plain().mount().await;
1287
1288 server.mock_set_space_child().unauthorized().expect(1).mount().await;
1289 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1290
1291 let space_id = room_id!("!my_space:example.org");
1292 let child_id = room_id!("!my_child:example.org");
1293
1294 add_space_rooms(
1295 vec![
1296 MockSpaceRoomParameters {
1297 room_id: space_id,
1298 order: None,
1299 parents: vec![],
1300 children: vec![],
1301 power_level: Some(0),
1302 },
1303 MockSpaceRoomParameters {
1304 room_id: child_id,
1305 order: None,
1306 parents: vec![],
1307 children: vec![],
1308 power_level: Some(0),
1309 },
1310 ],
1311 &client,
1312 &server,
1313 &factory,
1314 user_id,
1315 )
1316 .await;
1317
1318 let space_service = SpaceService::new(client.clone()).await;
1319
1320 let result =
1322 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1323
1324 assert!(result.is_err());
1327 }
1328
1329 #[async_test]
1330 async fn test_add_child_to_space_without_child_admin() {
1331 let server = MatrixMockServer::new().await;
1334 let client = server.client_builder().build().await;
1335 let user_id = client.user_id().unwrap();
1336 let factory = EventFactory::new();
1337
1338 server.mock_room_state_encryption().plain().mount().await;
1339
1340 let space_child_event_id = event_id!("$1");
1341 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1342 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1343
1344 let space_id = room_id!("!my_space:example.org");
1345 let child_id = room_id!("!my_child:example.org");
1346
1347 add_space_rooms(
1348 vec![
1349 MockSpaceRoomParameters {
1350 room_id: space_id,
1351 order: None,
1352 parents: vec![],
1353 children: vec![],
1354 power_level: Some(100),
1355 },
1356 MockSpaceRoomParameters {
1357 room_id: child_id,
1358 order: None,
1359 parents: vec![],
1360 children: vec![],
1361 power_level: Some(0),
1362 },
1363 ],
1364 &client,
1365 &server,
1366 &factory,
1367 user_id,
1368 )
1369 .await;
1370
1371 let space_service = SpaceService::new(client.clone()).await;
1372
1373 let result =
1375 space_service.add_child_to_space(child_id.to_owned(), space_id.to_owned()).await;
1376
1377 error!("result: {:?}", result);
1378 assert!(result.is_ok());
1381 }
1382
1383 #[async_test]
1384 async fn test_remove_child_from_space() {
1385 let server = MatrixMockServer::new().await;
1387 let client = server.client_builder().build().await;
1388 let user_id = client.user_id().unwrap();
1389 let factory = EventFactory::new();
1390
1391 server.mock_room_state_encryption().plain().mount().await;
1392
1393 let space_child_event_id = event_id!("$1");
1394 let space_parent_event_id = event_id!("$2");
1395 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1396 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1397
1398 let parent_id = room_id!("!parent_space:example.org");
1399 let child_id = room_id!("!child_space:example.org");
1400
1401 add_space_rooms(
1402 vec![
1403 MockSpaceRoomParameters {
1404 room_id: parent_id,
1405 order: None,
1406 parents: vec![],
1407 children: vec![child_id],
1408 power_level: None,
1409 },
1410 MockSpaceRoomParameters {
1411 room_id: child_id,
1412 order: None,
1413 parents: vec![parent_id],
1414 children: vec![],
1415 power_level: None,
1416 },
1417 ],
1418 &client,
1419 &server,
1420 &factory,
1421 user_id,
1422 )
1423 .await;
1424
1425 let space_service = SpaceService::new(client.clone()).await;
1426
1427 let result =
1429 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1430
1431 assert!(result.is_ok());
1433 }
1434
1435 #[async_test]
1436 async fn test_remove_child_from_space_without_parent_event() {
1437 let server = MatrixMockServer::new().await;
1439 let client = server.client_builder().build().await;
1440 let user_id = client.user_id().unwrap();
1441 let factory = EventFactory::new();
1442
1443 server.mock_room_state_encryption().plain().mount().await;
1444
1445 let space_child_event_id = event_id!("$1");
1446 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1447 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1448
1449 let parent_id = room_id!("!parent_space:example.org");
1450 let child_id = room_id!("!child_space:example.org");
1451
1452 add_space_rooms(
1453 vec![
1454 MockSpaceRoomParameters {
1455 room_id: parent_id,
1456 order: None,
1457 parents: vec![],
1458 children: vec![child_id],
1459 power_level: None,
1460 },
1461 MockSpaceRoomParameters {
1462 room_id: child_id,
1463 order: None,
1464 parents: vec![],
1465 children: vec![],
1466 power_level: None,
1467 },
1468 ],
1469 &client,
1470 &server,
1471 &factory,
1472 user_id,
1473 )
1474 .await;
1475
1476 let space_service = SpaceService::new(client.clone()).await;
1477
1478 let result =
1480 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1481
1482 assert!(result.is_ok());
1485 }
1486
1487 #[async_test]
1488 async fn test_remove_child_from_space_without_child_event() {
1489 let server = MatrixMockServer::new().await;
1491 let client = server.client_builder().build().await;
1492 let user_id = client.user_id().unwrap();
1493 let factory = EventFactory::new();
1494
1495 server.mock_room_state_encryption().plain().mount().await;
1496
1497 let space_parent_event_id = event_id!("$2");
1498 server.mock_set_space_child().unauthorized().expect(0).mount().await;
1499 server.mock_set_space_parent().ok(space_parent_event_id.to_owned()).expect(1).mount().await;
1500
1501 let parent_id = room_id!("!parent_space:example.org");
1502 let child_id = room_id!("!child_space:example.org");
1503
1504 add_space_rooms(
1505 vec![
1506 MockSpaceRoomParameters {
1507 room_id: parent_id,
1508 order: None,
1509 parents: vec![],
1510 children: vec![],
1511 power_level: None,
1512 },
1513 MockSpaceRoomParameters {
1514 room_id: child_id,
1515 order: None,
1516 parents: vec![parent_id],
1517 children: vec![],
1518 power_level: None,
1519 },
1520 ],
1521 &client,
1522 &server,
1523 &factory,
1524 user_id,
1525 )
1526 .await;
1527
1528 let space_service = SpaceService::new(client.clone()).await;
1529
1530 let result =
1532 space_service.remove_child_from_space(child_id.to_owned(), parent_id.to_owned()).await;
1533
1534 assert!(result.is_ok());
1537 }
1538
1539 #[async_test]
1540 async fn test_remove_unknown_child_from_space() {
1541 let server = MatrixMockServer::new().await;
1543 let client = server.client_builder().build().await;
1544 let user_id = client.user_id().unwrap();
1545 let factory = EventFactory::new();
1546
1547 server.mock_room_state_encryption().plain().mount().await;
1548
1549 let space_child_event_id = event_id!("$1");
1550 server.mock_set_space_child().ok(space_child_event_id.to_owned()).expect(1).mount().await;
1551 server.mock_set_space_parent().unauthorized().expect(0).mount().await;
1553
1554 let parent_id = room_id!("!parent_space:example.org");
1555 let unknown_child_id = room_id!("!unknown_child:example.org");
1556
1557 add_space_rooms(
1559 vec![MockSpaceRoomParameters {
1560 room_id: parent_id,
1561 order: None,
1562 parents: vec![],
1563 children: vec![unknown_child_id],
1564 power_level: None,
1565 }],
1566 &client,
1567 &server,
1568 &factory,
1569 user_id,
1570 )
1571 .await;
1572
1573 assert!(client.get_room(unknown_child_id).is_none());
1575
1576 let space_service = SpaceService::new(client.clone()).await;
1577
1578 let result = space_service
1580 .remove_child_from_space(unknown_child_id.to_owned(), parent_id.to_owned())
1581 .await;
1582
1583 assert!(result.is_ok());
1586 }
1587
1588 #[async_test]
1589 async fn test_space_child_updates() {
1590 let server = MatrixMockServer::new().await;
1592 let client = server.client_builder().build().await;
1593 let user_id = client.user_id().unwrap();
1594 let factory = EventFactory::new();
1595
1596 server.mock_room_state_encryption().plain().mount().await;
1597
1598 let space_id = room_id!("!space:localhost");
1599 let first_child_id = room_id!("!first_child:localhost");
1600 let second_child_id = room_id!("!second_child:localhost");
1601
1602 server
1604 .sync_room(
1605 &client,
1606 JoinedRoomBuilder::new(space_id)
1607 .add_state_event(factory.create(user_id, RoomVersionId::V11).with_space_type()),
1608 )
1609 .await;
1610
1611 let space_service = SpaceService::new(client.clone()).await;
1614
1615 let (initial_values, joined_spaces_subscriber) =
1616 space_service.subscribe_to_top_level_joined_spaces().await;
1617 pin_mut!(joined_spaces_subscriber);
1618 assert_pending!(joined_spaces_subscriber);
1619
1620 assert_eq!(
1621 initial_values,
1622 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0)].into()
1623 );
1624
1625 assert_eq!(
1626 space_service.top_level_joined_spaces().await,
1627 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 0)]
1628 );
1629
1630 server
1632 .sync_room(
1633 &client,
1634 JoinedRoomBuilder::new(space_id)
1635 .add_state_event(
1636 factory
1637 .space_child(space_id.to_owned(), first_child_id.to_owned())
1638 .sender(user_id),
1639 )
1640 .add_state_event(
1641 factory
1642 .space_child(space_id.to_owned(), second_child_id.to_owned())
1643 .sender(user_id),
1644 ),
1645 )
1646 .await;
1647
1648 assert_eq!(
1650 space_service.top_level_joined_spaces().await,
1651 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2)]
1652 );
1653 assert_next_eq!(
1654 joined_spaces_subscriber,
1655 vec![
1656 VectorDiff::Clear,
1657 VectorDiff::Append {
1658 values: vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 2)]
1659 .into()
1660 },
1661 ]
1662 );
1663
1664 server
1666 .sync_room(
1667 &client,
1668 JoinedRoomBuilder::new(space_id).add_state_bulk([Raw::new(&json!({
1669 "content": {},
1670 "type": "m.space.child",
1671 "event_id": "$cancelsecondchild",
1672 "origin_server_ts": MilliSecondsSinceUnixEpoch::now(),
1673 "sender": user_id,
1674 "state_key": second_child_id,
1675 }))
1676 .unwrap()
1677 .cast_unchecked()]),
1678 )
1679 .await;
1680
1681 assert_eq!(
1683 space_service.top_level_joined_spaces().await,
1684 vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1)]
1685 );
1686 assert_next_eq!(
1687 joined_spaces_subscriber,
1688 vec![
1689 VectorDiff::Clear,
1690 VectorDiff::Append {
1691 values: vec![SpaceRoom::new_from_known(&client.get_room(space_id).unwrap(), 1)]
1692 .into()
1693 },
1694 ]
1695 );
1696 }
1697
1698 async fn add_space_rooms(
1699 rooms: Vec<MockSpaceRoomParameters>,
1700 client: &Client,
1701 server: &MatrixMockServer,
1702 factory: &EventFactory,
1703 user_id: &UserId,
1704 ) {
1705 for parameters in rooms {
1706 let mut builder = JoinedRoomBuilder::new(parameters.room_id)
1707 .add_state_event(factory.create(user_id, RoomVersionId::V1).with_space_type());
1708
1709 if let Some(order) = parameters.order {
1710 builder = builder.add_account_data(RoomAccountDataTestEvent::Custom(json!({
1711 "type": "m.space_order",
1712 "content": {
1713 "order": order
1714 }
1715 })));
1716 }
1717
1718 for parent_id in parameters.parents {
1719 builder = builder.add_state_event(
1720 factory
1721 .space_parent(parent_id.to_owned(), parameters.room_id.to_owned())
1722 .sender(user_id),
1723 );
1724 }
1725
1726 for child_id in parameters.children {
1727 builder = builder.add_state_event(
1728 factory
1729 .space_child(parameters.room_id.to_owned(), child_id.to_owned())
1730 .sender(user_id),
1731 );
1732 }
1733
1734 let mut power_levels = if let Some(power_level) = parameters.power_level {
1735 BTreeMap::from([(user_id.to_owned(), power_level.into())])
1736 } else {
1737 BTreeMap::from([(user_id.to_owned(), 100.into())])
1738 };
1739
1740 builder = builder.add_state_event(
1741 factory.power_levels(&mut power_levels).state_key("").sender(user_id),
1742 );
1743
1744 server.sync_room(client, builder).await;
1745 }
1746 }
1747
1748 struct MockSpaceRoomParameters {
1749 room_id: &'static RoomId,
1750 order: Option<&'static str>,
1751 parents: Vec<&'static RoomId>,
1752 children: Vec<&'static RoomId>,
1753 power_level: Option<i32>,
1754 }
1755}