Skip to main content

tuwunel_service/rooms/spaces/
federation.rs

1use futures::{StreamExt, pin_mut, stream::FuturesUnordered};
2use ruma::{
3	OwnedServerName, RoomId,
4	api::federation::space::{
5		SpaceHierarchyParentSummary as ParentSummary,
6		get_hierarchy::v1::{Request, Response},
7	},
8	room::RoomType,
9};
10use tuwunel_core::{Err, Result, debug, implement};
11
12use super::{
13	Accessibility,
14	Accessibility::{Accessible, Inaccessible},
15	Identifier,
16};
17
18/// Gets the summary of a space using solely federation.
19#[implement(super::Service)]
20#[tracing::instrument(
21	name = "federation",
22	level = "debug",
23	err(level = "debug"),
24	ret(level = "trace"),
25	skip(self)
26)]
27pub(super) async fn get_summary_and_children_federation(
28	&self,
29	current_room: &RoomId,
30	sender: &Identifier<'_>,
31	via: &[OwnedServerName],
32) -> Result<Accessibility> {
33	let request = Request {
34		room_id: current_room.to_owned(),
35		suggested_only: false,
36	};
37
38	let requests: FuturesUnordered<_> = via
39		.iter()
40		.map(|server| {
41			self.services
42				.federation
43				.execute(server, request.clone())
44		})
45		.collect();
46
47	pin_mut!(requests);
48	debug!(
49		?current_room,
50		?sender,
51		?via,
52		requests = requests.len(),
53		"waiting for federation response"
54	);
55
56	let mut response = None;
57	while let Some(result) = requests.next().await {
58		match result {
59			| Ok(ok_response) => {
60				debug!(?ok_response, "federation response");
61
62				response = Some(ok_response);
63				break;
64			},
65			| Err(error) => {
66				debug!(?error, "federation error");
67			},
68		}
69	}
70
71	let Some(Response { room, children, inaccessible_children }) = response else {
72		self.cache_put(current_room, None);
73		return Err!(Request(NotFound("Space room not found over federation.")));
74	};
75
76	for room_id in &inaccessible_children {
77		self.cache_put(room_id, None);
78	}
79
80	for summary in children
81		.into_iter()
82		.filter(|child| child.room_type.ne(&Some(RoomType::Space)))
83	{
84		let room_id = summary.room_id.clone();
85		let summary = ParentSummary {
86			summary,
87			children_state: Default::default(),
88		};
89
90		self.cache_put(&room_id, Some(&summary));
91	}
92
93	self.cache_put(current_room, Some(&room));
94
95	self.is_accessible_child(current_room, &room.summary.join_rule.clone(), sender)
96		.await
97		.then(|| Ok(Accessible(room)))
98		.unwrap_or(Ok(Inaccessible))
99}