Skip to main content

tuwunel_service/rooms/spaces/
federation.rs

1use futures::StreamExt;
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, utils::IterStream};
11
12use super::{
13	Accessibility,
14	Accessibility::{Accessible, Inaccessible},
15	Identifier,
16};
17use crate::federation::feds::{Fault, Opts, OutcomeExt, Record};
18
19/// Gets the summary of a space using solely federation.
20#[implement(super::Service)]
21#[tracing::instrument(
22	name = "federation",
23	level = "debug",
24	err(level = "debug"),
25	ret(level = "trace"),
26	skip(self)
27)]
28pub(super) async fn get_summary_and_children_federation(
29	&self,
30	current_room: &RoomId,
31	sender: &Identifier<'_>,
32	via: &[OwnedServerName],
33) -> Result<Accessibility> {
34	let request = Request {
35		room_id: current_room.to_owned(),
36		suggested_only: false,
37	};
38
39	debug!(
40		?current_room,
41		?sender,
42		?via,
43		requests = via.len(),
44		"waiting for federation response"
45	);
46	let opts = Opts {
47		record: Record::Contribute,
48		..Default::default()
49	};
50
51	let response = self
52		.services
53		.federation
54		.fanout_to(via.iter().cloned().stream(), move |_| request.clone(), opts)
55		.inspect(|outcome| match &outcome.result {
56			| Ok(response) => debug!(?response, "federation response"),
57			| Err(Fault::Error(error)) => debug!(?error, "federation error"),
58			| Err(fault) => debug!(?fault, "federation error"),
59		})
60		.first_acceptable(|_| true)
61		.await
62		.map(|(_, response)| response);
63
64	let Some(Response { room, children, inaccessible_children }) = response else {
65		self.cache_put(current_room, None);
66		return Err!(Request(NotFound("Space room not found over federation.")));
67	};
68
69	for room_id in &inaccessible_children {
70		self.cache_put(room_id, None);
71	}
72
73	for summary in children
74		.into_iter()
75		.filter(|child| child.room_type.ne(&Some(RoomType::Space)))
76	{
77		let room_id = summary.room_id.clone();
78		let summary = ParentSummary {
79			summary,
80			children_state: Default::default(),
81		};
82
83		self.cache_put(&room_id, Some(&summary));
84	}
85
86	self.cache_put(current_room, Some(&room));
87
88	self.is_accessible_child(current_room, &room.summary.join_rule.clone(), sender)
89		.await
90		.then(|| Ok(Accessible(room)))
91		.unwrap_or(Ok(Inaccessible))
92}