tuwunel_service/rooms/spaces/
federation.rs1use 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#[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}