sync upstream spaces/hierarchy federation MR

also had to fix a million clippy lints

fix(spaces): deal with hierarchy recursion
fix(spaces): properly handle max_depth
refactor(spaces): token scheme to prevent clients from modifying max_depth and suggested_only
perf(spaces): use tokens to skip to room to start populating results at
feat(spaces): request hierarchy from servers in via field of child event

Co-authored-by: Matthias Ahouansou <matthias@ahouansou.cz>
Signed-off-by: strawberry <strawberry@puppygock.gay>
This commit is contained in:
strawberry 2024-07-02 16:42:07 -04:00
parent 9115901c66
commit 28ac3790c2
5 changed files with 433 additions and 784 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,145 @@
#![cfg(test)]
use std::str::FromStr;
use ruma::{
api::federation::space::{SpaceHierarchyParentSummary, SpaceHierarchyParentSummaryInit},
owned_room_id, owned_server_name,
space::SpaceRoomJoinRule,
UInt,
};
use crate::rooms::spaces::{get_parent_children_via, PaginationToken};
#[test]
fn get_summary_children() {
let summary: SpaceHierarchyParentSummary = SpaceHierarchyParentSummaryInit {
num_joined_members: UInt::from(1_u32),
room_id: owned_room_id!("!root:example.org"),
world_readable: true,
guest_can_join: true,
join_rule: SpaceRoomJoinRule::Public,
children_state: vec![
serde_json::from_str(
r#"{
"content": {
"via": [
"example.org"
],
"suggested": false
},
"origin_server_ts": 1629413349153,
"sender": "@alice:example.org",
"state_key": "!foo:example.org",
"type": "m.space.child"
}"#,
)
.unwrap(),
serde_json::from_str(
r#"{
"content": {
"via": [
"example.org"
],
"suggested": true
},
"origin_server_ts": 1629413349157,
"sender": "@alice:example.org",
"state_key": "!bar:example.org",
"type": "m.space.child"
}"#,
)
.unwrap(),
serde_json::from_str(
r#"{
"content": {
"via": [
"example.org"
]
},
"origin_server_ts": 1629413349160,
"sender": "@alice:example.org",
"state_key": "!baz:example.org",
"type": "m.space.child"
}"#,
)
.unwrap(),
],
allowed_room_ids: vec![],
}
.into();
assert_eq!(
get_parent_children_via(&summary, false),
vec![
(owned_room_id!("!foo:example.org"), vec![owned_server_name!("example.org")]),
(owned_room_id!("!bar:example.org"), vec![owned_server_name!("example.org")]),
(owned_room_id!("!baz:example.org"), vec![owned_server_name!("example.org")])
]
);
assert_eq!(
get_parent_children_via(&summary, true),
vec![(owned_room_id!("!bar:example.org"), vec![owned_server_name!("example.org")])]
);
}
#[test]
fn invalid_pagination_tokens() {
fn token_is_err(token: &str) { PaginationToken::from_str(token).unwrap_err(); }
token_is_err("231_2_noabool");
token_is_err("");
token_is_err("111_3_");
token_is_err("foo_not_int");
token_is_err("11_4_true_");
token_is_err("___");
token_is_err("__false");
}
#[test]
fn valid_pagination_tokens() {
assert_eq!(
PaginationToken {
short_room_ids: vec![5383, 42934, 283, 423],
limit: UInt::from(20_u32),
max_depth: UInt::from(1_u32),
suggested_only: true
},
PaginationToken::from_str("5383,42934,283,423_20_1_true").unwrap()
);
assert_eq!(
PaginationToken {
short_room_ids: vec![740],
limit: UInt::from(97_u32),
max_depth: UInt::from(10539_u32),
suggested_only: false
},
PaginationToken::from_str("740_97_10539_false").unwrap()
);
}
#[test]
fn pagination_token_to_string() {
assert_eq!(
PaginationToken {
short_room_ids: vec![740],
limit: UInt::from(97_u32),
max_depth: UInt::from(10539_u32),
suggested_only: false
}
.to_string(),
"740_97_10539_false"
);
assert_eq!(
PaginationToken {
short_room_ids: vec![9, 34],
limit: UInt::from(3_u32),
max_depth: UInt::from(1_u32),
suggested_only: true
}
.to_string(),
"9,34_3_1_true"
);
}

View file

@ -16,6 +16,7 @@ use ruma::{
canonical_alias::RoomCanonicalAliasEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{AllowRule, JoinRule, RoomJoinRulesEventContent, RoomMembership},
member::{MembershipState, RoomMemberEventContent},
name::RoomNameEventContent,
power_levels::{RoomPowerLevels, RoomPowerLevelsEventContent},
@ -23,7 +24,8 @@ use ruma::{
},
StateEventType,
},
EventId, OwnedRoomAliasId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
space::SpaceRoomJoinRule,
EventId, OwnedRoomAliasId, OwnedRoomId, OwnedServerName, OwnedUserId, RoomId, ServerName, UserId,
};
use serde_json::value::to_raw_value;
@ -415,4 +417,38 @@ impl Service {
},
)
}
/// Returns the join rule for a given room
pub fn get_join_rule(&self, current_room: &RoomId) -> Result<(SpaceRoomJoinRule, Vec<OwnedRoomId>), Error> {
Ok(self
.room_state_get(current_room, &StateEventType::RoomJoinRules, "")?
.map(|s| {
serde_json::from_str(s.content.get())
.map(|c: RoomJoinRulesEventContent| {
(c.join_rule.clone().into(), self.allowed_room_ids(c.join_rule))
})
.map_err(|e| {
error!("Invalid room join rule event in database: {e}");
Error::BadDatabase("Invalid room join rule event in database.")
})
})
.transpose()?
.unwrap_or((SpaceRoomJoinRule::Invite, vec![])))
}
/// Returns an empty vec if not a restricted room
pub fn allowed_room_ids(&self, join_rule: JoinRule) -> Vec<OwnedRoomId> {
let mut room_ids = vec![];
if let JoinRule::Restricted(r) | JoinRule::KnockRestricted(r) = join_rule {
for rule in r.allow {
if let AllowRule::RoomMembership(RoomMembership {
room_id: membership,
}) = rule
{
room_ids.push(membership.clone());
}
}
}
room_ids
}
}

View file

@ -293,6 +293,7 @@ impl Service {
self.db.room_members(room_id)
}
/// Returns the number of users which are currently in a room
#[tracing::instrument(skip(self))]
pub fn room_joined_count(&self, room_id: &RoomId) -> Result<Option<u64>> { self.db.room_joined_count(room_id) }
@ -310,6 +311,7 @@ impl Service {
self.db.active_local_users_in_room(room_id)
}
/// Returns the number of users which are currently invited to a room
#[tracing::instrument(skip(self))]
pub fn room_invited_count(&self, room_id: &RoomId) -> Result<Option<u64>> { self.db.room_invited_count(room_id) }