1use std::{cmp::Ordering, collections::HashMap, sync::Arc};
16
17use eyeball::{ObservableWriteGuard, SharedObservable, Subscriber};
18use eyeball_im::{ObservableVector, VectorSubscriberBatchedStream};
19use futures_util::pin_mut;
20use imbl::Vector;
21use itertools::Itertools;
22use matrix_sdk::{Client, Error, executor::AbortOnDrop, locks::Mutex, paginators::PaginationToken};
23use matrix_sdk_common::executor::spawn;
24use ruma::{
25 OwnedRoomId,
26 api::client::space::get_hierarchy,
27 events::space::child::{HierarchySpaceChildEvent, SpaceChildEventContent},
28 uint,
29};
30use tokio::sync::Mutex as AsyncMutex;
31use tracing::{error, warn};
32
33use crate::spaces::SpaceRoom;
34
35#[cfg_attr(feature = "uniffi", derive(uniffi::Enum))]
36#[derive(Clone, Debug, Eq, PartialEq)]
37pub enum SpaceRoomListPaginationState {
38 Idle { end_reached: bool },
39 Loading,
40}
41
42pub struct SpaceRoomList {
104 client: Client,
105
106 space_id: OwnedRoomId,
107
108 space: SharedObservable<Option<SpaceRoom>>,
109
110 children_state: Mutex<Option<HashMap<OwnedRoomId, HierarchySpaceChildEvent>>>,
111
112 token: AsyncMutex<PaginationToken>,
113
114 pagination_state: SharedObservable<SpaceRoomListPaginationState>,
115
116 rooms: Arc<Mutex<ObservableVector<SpaceRoom>>>,
117
118 _space_update_handle: Option<AbortOnDrop<()>>,
119
120 _room_update_handle: AbortOnDrop<()>,
121}
122
123impl SpaceRoomList {
124 pub async fn new(client: Client, space_id: OwnedRoomId) -> Self {
126 let rooms = Arc::new(Mutex::new(ObservableVector::<SpaceRoom>::new()));
127
128 let all_room_updates_receiver = client.subscribe_to_all_room_updates();
129
130 let room_update_handle = spawn({
131 let client = client.clone();
132 let rooms = rooms.clone();
133
134 async move {
135 pin_mut!(all_room_updates_receiver);
136
137 loop {
138 match all_room_updates_receiver.recv().await {
139 Ok(updates) => {
140 if updates.is_empty() {
141 continue;
142 }
143
144 let mut mutable_rooms = rooms.lock();
145
146 updates.iter_all_room_ids().for_each(|updated_room_id| {
147 if let Some((position, room)) = mutable_rooms
148 .clone()
149 .iter()
150 .find_position(|room| &room.room_id == updated_room_id)
151 && let Some(updated_room) = client.get_room(updated_room_id)
152 {
153 mutable_rooms.set(
154 position,
155 SpaceRoom::new_from_known(
156 &updated_room,
157 room.children_count,
158 ),
159 );
160 }
161 })
162 }
163 Err(err) => {
164 error!("error when listening to room updates: {err}");
165 }
166 }
167 }
168 }
169 });
170
171 let space_observable = SharedObservable::new(None);
172
173 let (space_room, space_update_handle) = if let Some(parent) = client.get_room(&space_id) {
174 let children_count = parent
175 .get_state_events_static::<SpaceChildEventContent>()
176 .await
177 .map_or(0, |c| c.len() as u64);
178
179 let mut subscriber = parent.subscribe_info();
180 let space_update_handle = spawn({
181 let client = client.clone();
182 let space_id = space_id.clone();
183 let space_observable = space_observable.clone();
184 async move {
185 while subscriber.next().await.is_some() {
186 if let Some(room) = client.get_room(&space_id) {
187 space_observable
188 .set(Some(SpaceRoom::new_from_known(&room, children_count)));
189 }
190 }
191 }
192 });
193
194 (
195 Some(SpaceRoom::new_from_known(&parent, children_count)),
196 Some(AbortOnDrop::new(space_update_handle)),
197 )
198 } else {
199 (None, None)
200 };
201
202 space_observable.set(space_room);
203
204 Self {
205 client,
206 space_id,
207 space: space_observable,
208 children_state: Mutex::new(None),
209 token: AsyncMutex::new(None.into()),
210 pagination_state: SharedObservable::new(SpaceRoomListPaginationState::Idle {
211 end_reached: false,
212 }),
213 rooms,
214 _space_update_handle: space_update_handle,
215 _room_update_handle: AbortOnDrop::new(room_update_handle),
216 }
217 }
218
219 pub fn space(&self) -> Option<SpaceRoom> {
221 self.space.get()
222 }
223
224 pub fn subscribe_to_space_updates(&self) -> Subscriber<Option<SpaceRoom>> {
226 self.space.subscribe()
227 }
228
229 pub fn pagination_state(&self) -> SpaceRoomListPaginationState {
231 self.pagination_state.get()
232 }
233
234 pub fn subscribe_to_pagination_state_updates(
236 &self,
237 ) -> Subscriber<SpaceRoomListPaginationState> {
238 self.pagination_state.subscribe()
239 }
240
241 pub fn rooms(&self) -> Vec<SpaceRoom> {
243 self.rooms.lock().iter().cloned().collect_vec()
244 }
245
246 pub fn subscribe_to_room_updates(
248 &self,
249 ) -> (Vector<SpaceRoom>, VectorSubscriberBatchedStream<SpaceRoom>) {
250 self.rooms.lock().subscribe().into_values_and_batched_stream()
251 }
252
253 pub async fn paginate(&self) -> Result<(), Error> {
256 {
257 let mut pagination_state = self.pagination_state.write();
258
259 match *pagination_state {
260 SpaceRoomListPaginationState::Idle { end_reached } if end_reached => {
261 return Ok(());
262 }
263 SpaceRoomListPaginationState::Loading => {
264 return Ok(());
265 }
266 _ => {}
267 }
268
269 ObservableWriteGuard::set(&mut pagination_state, SpaceRoomListPaginationState::Loading);
270 }
271
272 let mut request = get_hierarchy::v1::Request::new(self.space_id.clone());
273 request.max_depth = Some(uint!(1)); let mut pagination_token = self.token.lock().await;
276
277 if let PaginationToken::HasMore(ref token) = *pagination_token {
278 request.from = Some(token.clone());
279 }
280
281 match self.client.send(request).await {
282 Ok(result) => {
283 *pagination_token = match &result.next_batch {
284 Some(val) => PaginationToken::HasMore(val.clone()),
285 None => PaginationToken::HitEnd,
286 };
287
288 let mut rooms = self.rooms.lock();
289
290 let (space, children): (Vec<_>, Vec<_>) =
293 result.rooms.into_iter().partition(|f| f.summary.room_id == self.space_id);
294
295 if let Some(room) = space.first() {
296 let mut children_state =
297 HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
298 for child_state in &room.children_state {
299 match child_state.deserialize() {
300 Ok(child) => {
301 children_state.insert(child.state_key.clone(), child.clone());
302 }
303 Err(error) => {
304 warn!("Failed deserializing space child event: {error}");
305 }
306 }
307 }
308 *self.children_state.lock() = Some(children_state);
309
310 let mut space = self.space.write();
311 if space.is_none() {
312 ObservableWriteGuard::set(
313 &mut space,
314 Some(SpaceRoom::new_from_summary(
315 &room.summary,
316 self.client.get_room(&room.summary.room_id),
317 room.children_state.len() as u64,
318 vec![],
319 )),
320 );
321 }
322 }
323
324 let children_state = (*self.children_state.lock()).clone().unwrap_or_default();
325
326 children
327 .iter()
328 .map(|room| {
329 let via = children_state
330 .get(&room.summary.room_id)
331 .map(|state| state.content.via.clone());
332
333 SpaceRoom::new_from_summary(
334 &room.summary,
335 self.client.get_room(&room.summary.room_id),
336 room.children_state.len() as u64,
337 via.unwrap_or_default(),
338 )
339 })
340 .sorted_by(|a, b| Self::compare_rooms(a, b, &children_state))
341 .for_each(|room| rooms.push_back(room));
342
343 self.pagination_state.set(SpaceRoomListPaginationState::Idle {
344 end_reached: result.next_batch.is_none(),
345 });
346
347 Ok(())
348 }
349 Err(err) => {
350 self.pagination_state
351 .set(SpaceRoomListPaginationState::Idle { end_reached: false });
352 Err(err.into())
353 }
354 }
355 }
356
357 fn compare_rooms(
360 a: &SpaceRoom,
361 b: &SpaceRoom,
362 children_state: &HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
363 ) -> Ordering {
364 let a_state = children_state.get(&a.room_id);
365 let b_state = children_state.get(&b.room_id);
366
367 match (a_state, b_state) {
368 (Some(a_state), Some(b_state)) => {
369 match (&a_state.content.order, &b_state.content.order) {
370 (Some(a_order), Some(b_order)) => a_order
371 .cmp(b_order)
372 .then(a_state.origin_server_ts.cmp(&b_state.origin_server_ts))
373 .then(a.room_id.cmp(&b.room_id)),
374 (Some(_), None) => Ordering::Greater,
375 (None, Some(_)) => Ordering::Less,
376 (None, None) => a_state
377 .origin_server_ts
378 .cmp(&b_state.origin_server_ts)
379 .then(a.room_id.to_string().cmp(&b.room_id.to_string())),
380 }
381 }
382 _ => a.room_id.to_string().cmp(&b.room_id.to_string()),
383 }
384 }
385}
386
387#[cfg(test)]
388mod tests {
389 use std::{cmp::Ordering, collections::HashMap};
390
391 use assert_matches2::{assert_let, assert_matches};
392 use eyeball_im::VectorDiff;
393 use futures_util::pin_mut;
394 use matrix_sdk::{RoomState, test_utils::mocks::MatrixMockServer};
395 use matrix_sdk_test::{
396 JoinedRoomBuilder, LeftRoomBuilder, async_test, event_factory::EventFactory,
397 };
398 use ruma::{
399 OwnedRoomId, RoomId,
400 events::space::child::HierarchySpaceChildEvent,
401 owned_room_id, owned_server_name,
402 room::{JoinRuleSummary, RoomSummary},
403 room_id, server_name, uint,
404 };
405 use serde_json::{from_value, json};
406 use stream_assert::{assert_next_eq, assert_next_matches, assert_pending, assert_ready};
407
408 use crate::spaces::{
409 SpaceRoom, SpaceRoomList, SpaceService, room_list::SpaceRoomListPaginationState,
410 };
411
412 #[async_test]
413 async fn test_room_list_pagination() {
414 let server = MatrixMockServer::new().await;
415 let client = server.client_builder().build().await;
416 let user_id = client.user_id().unwrap();
417 let space_service = SpaceService::new(client.clone());
418 let factory = EventFactory::new();
419
420 server.mock_room_state_encryption().plain().mount().await;
421
422 let parent_space_id = room_id!("!parent_space:example.org");
423 let child_space_id_1 = room_id!("!1:example.org");
424 let child_space_id_2 = room_id!("!2:example.org");
425
426 server
427 .sync_room(
428 &client,
429 JoinedRoomBuilder::new(parent_space_id)
430 .add_state_event(
431 factory
432 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
433 .sender(user_id),
434 )
435 .add_state_event(
436 factory
437 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
438 .sender(user_id),
439 ),
440 )
441 .await;
442
443 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
444
445 assert_let!(Some(parent_space) = room_list.space());
447 assert_eq!(parent_space.children_count, 2);
448
449 assert_matches!(
451 room_list.pagination_state(),
452 SpaceRoomListPaginationState::Idle { end_reached: false }
453 );
454
455 assert_eq!(room_list.rooms(), vec![]);
457
458 let pagination_state_subscriber = room_list.subscribe_to_pagination_state_updates();
461 pin_mut!(pagination_state_subscriber);
462 assert_pending!(pagination_state_subscriber);
463
464 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
465 pin_mut!(rooms_subscriber);
466 assert_pending!(rooms_subscriber);
467
468 server
470 .mock_get_hierarchy()
471 .ok_with_room_ids_and_children_state(
472 vec![child_space_id_1, child_space_id_2],
473 vec![(room_id!("!child:example.org"), vec![])],
474 )
475 .mount()
476 .await;
477
478 room_list.paginate().await.unwrap();
479
480 assert_next_matches!(
482 pagination_state_subscriber,
483 SpaceRoomListPaginationState::Idle { end_reached: true }
484 );
485
486 assert_next_eq!(
488 rooms_subscriber,
489 vec![
490 VectorDiff::PushBack {
491 value: SpaceRoom::new_from_summary(
492 &RoomSummary::new(
493 child_space_id_1.to_owned(),
494 JoinRuleSummary::Public,
495 false,
496 uint!(1),
497 false,
498 ),
499 None,
500 1,
501 vec![],
502 )
503 },
504 VectorDiff::PushBack {
505 value: SpaceRoom::new_from_summary(
506 &RoomSummary::new(
507 child_space_id_2.to_owned(),
508 JoinRuleSummary::Public,
509 false,
510 uint!(1),
511 false,
512 ),
513 None,
514 1,
515 vec![],
516 ),
517 }
518 ]
519 );
520 }
521
522 #[async_test]
523 async fn test_room_state_updates() {
524 let server = MatrixMockServer::new().await;
525 let client = server.client_builder().build().await;
526 let space_service = SpaceService::new(client.clone());
527
528 let parent_space_id = room_id!("!parent_space:example.org");
529 let child_room_id_1 = room_id!("!1:example.org");
530 let child_room_id_2 = room_id!("!2:example.org");
531
532 server
533 .mock_get_hierarchy()
534 .ok_with_room_ids(vec![child_room_id_1, child_room_id_2])
535 .mount()
536 .await;
537
538 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
539
540 room_list.paginate().await.unwrap();
541
542 assert_eq!(room_list.rooms().first().unwrap().room_id, child_room_id_1);
544 assert_eq!(room_list.rooms().last().unwrap().room_id, child_room_id_2);
545
546 assert_eq!(room_list.rooms().first().unwrap().state, None);
548 assert_eq!(room_list.rooms().last().unwrap().state, None);
549
550 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
551 pin_mut!(rooms_subscriber);
552 assert_pending!(rooms_subscriber);
553
554 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_1)).await;
556
557 assert_ready!(rooms_subscriber);
559 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
560 assert_eq!(room_list.rooms().last().unwrap().state, None);
561
562 server.sync_room(&client, JoinedRoomBuilder::new(child_room_id_2)).await;
564 assert_ready!(rooms_subscriber);
565 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Joined));
566 assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Joined));
567
568 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_1)).await;
570 server.sync_room(&client, LeftRoomBuilder::new(child_room_id_2)).await;
571 assert_ready!(rooms_subscriber);
572 assert_eq!(room_list.rooms().first().unwrap().state, Some(RoomState::Left));
573 assert_eq!(room_list.rooms().last().unwrap().state, Some(RoomState::Left));
574 }
575
576 #[async_test]
577 async fn test_parent_space_updates() {
578 let server = MatrixMockServer::new().await;
579 let client = server.client_builder().build().await;
580 let user_id = client.user_id().unwrap();
581 let space_service = SpaceService::new(client.clone());
582 let factory = EventFactory::new();
583
584 server.mock_room_state_encryption().plain().mount().await;
585
586 let parent_space_id = room_id!("!parent_space:example.org");
587 let child_space_id_1 = room_id!("!1:example.org");
588 let child_space_id_2 = room_id!("!2:example.org");
589
590 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
592 assert!(room_list.space().is_none());
593
594 let parent_space_subscriber = room_list.subscribe_to_space_updates();
595 pin_mut!(parent_space_subscriber);
596 assert_pending!(parent_space_subscriber);
597
598 server
599 .mock_get_hierarchy()
600 .ok_with_room_ids_and_children_state(
601 vec![parent_space_id, child_space_id_1, child_space_id_2],
602 vec![(
603 room_id!("!child:example.org"),
604 vec![server_name!("matrix-client.example.org")],
605 )],
606 )
607 .mount()
608 .await;
609
610 room_list.paginate().await.unwrap();
612 assert_let!(Some(parent_space) = room_list.space());
613 assert_eq!(parent_space.room_id, parent_space_id);
614
615 assert_next_eq!(parent_space_subscriber, Some(parent_space));
617
618 server
621 .sync_room(
622 &client,
623 JoinedRoomBuilder::new(parent_space_id)
624 .add_state_event(
625 factory
626 .space_child(parent_space_id.to_owned(), child_space_id_1.to_owned())
627 .sender(user_id),
628 )
629 .add_state_event(
630 factory
631 .space_child(parent_space_id.to_owned(), child_space_id_2.to_owned())
632 .sender(user_id),
633 ),
634 )
635 .await;
636
637 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
638
639 assert_let!(Some(parent_space) = room_list.space());
641 assert_eq!(parent_space.children_count, 2);
642 }
643
644 #[async_test]
645 async fn test_parent_space_room_info_update() {
646 let server = MatrixMockServer::new().await;
647 let client = server.client_builder().build().await;
648 let user_id = client.user_id().unwrap();
649 let space_service = SpaceService::new(client.clone());
650 let factory = EventFactory::new();
651
652 server.mock_room_state_encryption().plain().mount().await;
653
654 let parent_space_id = room_id!("!parent_space:example.org");
655
656 server.sync_room(&client, JoinedRoomBuilder::new(parent_space_id)).await;
657
658 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
659 assert_let!(Some(parent_space) = room_list.space());
660
661 let parent_space_subscriber = room_list.subscribe_to_space_updates();
663 pin_mut!(parent_space_subscriber);
664 assert_pending!(parent_space_subscriber);
665
666 server
668 .sync_room(
669 &client,
670 JoinedRoomBuilder::new(parent_space_id)
671 .add_state_event(factory.room_topic("New room topic").sender(user_id))
672 .add_state_event(factory.room_name("New room name").sender(user_id)),
673 )
674 .await;
675
676 let mut updated_parent_space = parent_space.clone();
677 updated_parent_space.topic = Some("New room topic".to_owned());
678 updated_parent_space.name = Some("New room name".to_owned());
679 updated_parent_space.display_name = "New room name".to_owned();
680
681 assert_next_eq!(parent_space_subscriber, Some(updated_parent_space));
683 }
684
685 #[async_test]
686 async fn test_via_retrieval() {
687 let server = MatrixMockServer::new().await;
688 let client = server.client_builder().build().await;
689 let space_service = SpaceService::new(client.clone());
690
691 server.mock_room_state_encryption().plain().mount().await;
692
693 let parent_space_id = room_id!("!parent_space:example.org");
694 let child_space_id_1 = room_id!("!1:example.org");
695 let child_space_id_2 = room_id!("!2:example.org");
696
697 let room_list = space_service.space_room_list(parent_space_id.to_owned()).await;
698
699 let (_, rooms_subscriber) = room_list.subscribe_to_room_updates();
700 pin_mut!(rooms_subscriber);
701
702 server
704 .mock_get_hierarchy()
705 .ok_with_room_ids_and_children_state(
706 vec![parent_space_id, child_space_id_1, child_space_id_2],
707 vec![
708 (child_space_id_1, vec![server_name!("matrix-client.example.org")]),
709 (child_space_id_2, vec![server_name!("other-matrix-client.example.org")]),
710 ],
711 )
712 .mount()
713 .await;
714
715 room_list.paginate().await.unwrap();
716
717 assert_next_eq!(
719 rooms_subscriber,
720 vec![
721 VectorDiff::PushBack {
722 value: SpaceRoom::new_from_summary(
723 &RoomSummary::new(
724 child_space_id_1.to_owned(),
725 JoinRuleSummary::Public,
726 false,
727 uint!(1),
728 false,
729 ),
730 None,
731 2,
732 vec![owned_server_name!("matrix-client.example.org")],
733 )
734 },
735 VectorDiff::PushBack {
736 value: SpaceRoom::new_from_summary(
737 &RoomSummary::new(
738 child_space_id_2.to_owned(),
739 JoinRuleSummary::Public,
740 false,
741 uint!(1),
742 false,
743 ),
744 None,
745 2,
746 vec![owned_server_name!("other-matrix-client.example.org")],
747 ),
748 }
749 ]
750 );
751 }
752
753 #[async_test]
754 async fn test_room_list_sorting() {
755 let mut children_state = HashMap::<OwnedRoomId, HierarchySpaceChildEvent>::new();
756
757 assert_eq!(
759 SpaceRoomList::compare_rooms(
760 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
761 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
762 &children_state,
763 ),
764 Ordering::Less
765 );
766
767 assert_eq!(
768 SpaceRoomList::compare_rooms(
769 &make_space_room(owned_room_id!("!Marțolea:a.b"), None, None, &mut children_state),
770 &make_space_room(owned_room_id!("!Luana:a.b"), None, None, &mut children_state),
771 &children_state,
772 ),
773 Ordering::Greater
774 );
775
776 assert_eq!(
779 SpaceRoomList::compare_rooms(
780 &make_space_room(owned_room_id!("!Luana:a.b"), None, Some(1), &mut children_state),
781 &make_space_room(
782 owned_room_id!("!Marțolea:a.b"),
783 None,
784 Some(0),
785 &mut children_state
786 ),
787 &children_state,
788 ),
789 Ordering::Greater
790 );
791
792 assert_eq!(
794 SpaceRoomList::compare_rooms(
795 &make_space_room(
796 owned_room_id!("!Joiana:a.b"),
797 Some("last"),
798 Some(123),
799 &mut children_state
800 ),
801 &make_space_room(
802 owned_room_id!("!Mioara:a.b"),
803 Some("first"),
804 Some(234),
805 &mut children_state
806 ),
807 &children_state,
808 ),
809 Ordering::Greater
810 );
811
812 assert_eq!(
814 SpaceRoomList::compare_rooms(
815 &make_space_room(
816 owned_room_id!("!Joiana:a.b"),
817 Some("Same pasture"),
818 Some(1),
819 &mut children_state
820 ),
821 &make_space_room(
822 owned_room_id!("!Mioara:a.b"),
823 Some("Same pasture"),
824 Some(0),
825 &mut children_state
826 ),
827 &children_state,
828 ),
829 Ordering::Greater
830 );
831
832 assert_eq!(
835 SpaceRoomList::compare_rooms(
836 &make_space_room(
837 owned_room_id!("!Joiana:a.b"),
838 Some("same_pasture"),
839 Some(0),
840 &mut children_state
841 ),
842 &make_space_room(
843 owned_room_id!("!Mioara:a.b"),
844 Some("same_pasture"),
845 Some(0),
846 &mut children_state
847 ),
848 &children_state,
849 ),
850 Ordering::Less
851 );
852
853 assert_eq!(
856 SpaceRoomList::compare_rooms(
857 &make_space_room(owned_room_id!("!Viola:a.b"), None, None, &mut children_state),
858 &make_space_room(
859 owned_room_id!("!Sâmbotina:a.b"),
860 None,
861 Some(0),
862 &mut children_state
863 ),
864 &children_state,
865 ),
866 Ordering::Greater
867 );
868
869 assert_eq!(
870 SpaceRoomList::compare_rooms(
871 &make_space_room(
872 owned_room_id!("!Sâmbotina:a.b"),
873 None,
874 Some(1),
875 &mut children_state
876 ),
877 &make_space_room(
878 owned_room_id!("!Dumana:a.b"),
879 Some("Some pasture"),
880 Some(1),
881 &mut children_state
882 ),
883 &children_state,
884 ),
885 Ordering::Less
886 );
887 }
888
889 fn make_space_room(
890 room_id: OwnedRoomId,
891 order: Option<&str>,
892 origin_server_ts: Option<u32>,
893 children_state: &mut HashMap<OwnedRoomId, HierarchySpaceChildEvent>,
894 ) -> SpaceRoom {
895 if let Some(origin_server_ts) = origin_server_ts {
896 children_state.insert(
897 room_id.clone(),
898 hierarchy_space_child_event(&room_id, order, origin_server_ts),
899 );
900 }
901 SpaceRoom {
902 room_id,
903 canonical_alias: None,
904 name: Some("New room name".to_owned()),
905 display_name: "Empty room".to_owned(),
906 topic: None,
907 avatar_url: None,
908 room_type: None,
909 num_joined_members: 0,
910 join_rule: None,
911 world_readable: None,
912 guest_can_join: false,
913 is_direct: None,
914 children_count: 0,
915 state: None,
916 heroes: None,
917 via: vec![],
918 }
919 }
920
921 fn hierarchy_space_child_event(
922 room_id: &RoomId,
923 order: Option<&str>,
924 origin_server_ts: u32,
925 ) -> HierarchySpaceChildEvent {
926 let json = json!({
927 "content": {
928 "order": order.unwrap_or(""),
929 "via": []
930 },
931 "origin_server_ts": origin_server_ts,
932 "sender": "@bob:a.b",
933 "state_key": room_id.to_string(),
934 "type": "m.space.child"
935 });
936
937 from_value::<HierarchySpaceChildEvent>(json).unwrap()
938 }
939}