refactor(base): Use PossiblyRedactedStateEventContent bound in MinimalStateEvent

We usually don't care if the event was redacted or not, we usually want
to no whether a field is set or not, so we don't need `Original` and
`Redacted` variants.

This simplifies several parts of the code since we don't have to handle
the intermediate enum to access the content now. Due to new APIs in
Ruma we can also just convert original and redacted event contents to
possibly redacted event contents.

Signed-off-by: Kévin Commaille <zecakeh@tedomum.fr>
This commit is contained in:
Kévin Commaille
2026-01-30 11:33:11 +01:00
committed by Damir Jelić
parent 8f156cbba8
commit 9ad7cf9662
13 changed files with 289 additions and 426 deletions
+1 -4
View File
@@ -68,10 +68,7 @@ pub use store::{
QueueWedgeError, StateChanges, StateStore, StateStoreDataKey, StateStoreDataValue, StoreError,
ThreadSubscriptionCatchupToken,
};
pub use utils::{
MinimalRoomMemberEvent, MinimalStateEvent, OriginalMinimalStateEvent,
RawSyncStateEventWithKeys, RedactedMinimalStateEvent,
};
pub use utils::{MinimalRoomMemberEvent, MinimalStateEvent, RawSyncStateEventWithKeys};
#[cfg(test)]
matrix_sdk_test_utils::init_tracing_for_tests!();
@@ -501,7 +501,7 @@ pub fn is_tombstone_event_valid(
.state_changes
.room_infos
.get(&successor_room_id)
.and_then(|room_info| Some(room_info.tombstone()?.replacement_room.clone()))
.and_then(|room_info| room_info.tombstone()?.replacement_room.clone())
.or_else(|| {
state_store
.room(&successor_room_id)
+12 -3
View File
@@ -16,7 +16,8 @@ use matrix_sdk_common::ROOM_VERSION_RULES_FALLBACK;
use ruma::{
OwnedUserId, RoomVersionId, assign,
events::{
EmptyStateKey, RedactContent, RedactedStateEventContent, StateEventType,
EmptyStateKey, PossiblyRedactedStateEventContent, RedactContent, RedactedStateEventContent,
StateEventContent, StateEventType, StaticEventContent,
macros::EventContent,
room::create::{PreviousRoom, RoomCreateEventContent},
},
@@ -135,10 +136,10 @@ impl RoomCreateWithCreatorEventContent {
pub type RedactedRoomCreateWithCreatorEventContent = RoomCreateWithCreatorEventContent;
impl RedactedStateEventContent for RedactedRoomCreateWithCreatorEventContent {
type StateKey = EmptyStateKey;
type StateKey = <RoomCreateWithCreatorEventContent as StateEventContent>::StateKey;
fn event_type(&self) -> StateEventType {
StateEventType::RoomCreate
RoomCreateWithCreatorEventContent::TYPE.into()
}
}
@@ -156,3 +157,11 @@ impl RedactContent for RoomCreateWithCreatorEventContent {
fn default_create_room_version_id() -> RoomVersionId {
RoomVersionId::V1
}
impl PossiblyRedactedStateEventContent for RoomCreateWithCreatorEventContent {
type StateKey = <RoomCreateWithCreatorEventContent as StateEventContent>::StateKey;
fn event_type(&self) -> StateEventType {
RoomCreateWithCreatorEventContent::TYPE.into()
}
}
+14 -16
View File
@@ -544,9 +544,11 @@ mod tests {
events::{
StateEventType,
room::{
canonical_alias::RoomCanonicalAliasEventContent,
canonical_alias::{
PossiblyRedactedRoomCanonicalAliasEventContent, RoomCanonicalAliasEventContent,
},
member::{MembershipState, RoomMemberEventContent, StrippedRoomMemberEvent},
name::RoomNameEventContent,
name::{PossiblyRedactedRoomNameEventContent, RoomNameEventContent},
},
},
room_alias_id, room_id,
@@ -557,8 +559,7 @@ mod tests {
use super::{Room, RoomDisplayName, compute_display_name_from_heroes};
use crate::{
MinimalStateEvent, OriginalMinimalStateEvent, RoomHero, RoomState, StateChanges,
StateStore, store::MemoryStore,
MinimalStateEvent, RoomHero, RoomState, StateChanges, StateStore, store::MemoryStore,
};
fn make_room_test_helper(room_type: RoomState) -> (Arc<MemoryStore>, Room) {
@@ -584,22 +585,22 @@ mod tests {
}
fn make_canonical_alias_event() -> MinimalStateEvent<RoomCanonicalAliasEventContent> {
MinimalStateEvent::Original(OriginalMinimalStateEvent {
content: assign!(RoomCanonicalAliasEventContent::new(), {
MinimalStateEvent {
content: assign!(PossiblyRedactedRoomCanonicalAliasEventContent::new(), {
alias: Some(room_alias_id!("#test:example.com").to_owned()),
}),
event_id: None,
})
}
}
fn make_name_event_with(name: &str) -> MinimalStateEvent<RoomNameEventContent> {
MinimalStateEvent::Original(OriginalMinimalStateEvent {
content: RoomNameEventContent::new(name.to_owned()),
fn make_name_event_with(name: &str) -> MinimalStateEvent<PossiblyRedactedRoomNameEventContent> {
MinimalStateEvent {
content: RoomNameEventContent::new(name.to_owned()).into(),
event_id: None,
})
}
}
fn make_name_event() -> MinimalStateEvent<RoomNameEventContent> {
fn make_name_event() -> MinimalStateEvent<PossiblyRedactedRoomNameEventContent> {
make_name_event_with("Test Room")
}
@@ -697,10 +698,7 @@ mod tests {
async fn test_display_name_for_invited_room_is_empty_if_room_name_empty() {
let (_, room) = make_room_test_helper(RoomState::Invited);
let room_name = MinimalStateEvent::Original(OriginalMinimalStateEvent {
content: RoomNameEventContent::new(String::new()),
event_id: None,
});
let room_name = make_name_event_with("");
room.info.update(|info| info.base_info.name = Some(room_name));
assert_eq!(room.compute_display_name().await.unwrap().into_inner(), RoomDisplayName::Empty);
+2 -2
View File
@@ -259,7 +259,7 @@ impl RoomMember {
/// Get the display name of the member if there is one.
pub fn display_name(&self) -> Option<&str> {
if let Some(p) = self.profile.as_ref() {
p.as_original().and_then(|e| e.content.displayname.as_deref())
p.content.displayname.as_deref()
} else {
self.event.displayname_value()
}
@@ -276,7 +276,7 @@ impl RoomMember {
/// Get the avatar url of the member, if there is one.
pub fn avatar_url(&self) -> Option<&MxcUri> {
if let Some(p) = self.profile.as_ref() {
p.as_original().and_then(|e| e.content.avatar_url.as_deref())
p.content.avatar_url.as_deref()
} else {
self.event.avatar_url()
}
+2 -5
View File
@@ -67,7 +67,7 @@ pub use tombstone::{PredecessorRoom, SuccessorRoom};
use tracing::{info, instrument, warn};
use crate::{
Error, MinimalStateEvent,
Error,
deserialized_responses::MemberEvent,
notification_settings::RoomNotificationMode,
read_receipts::RoomReadReceipts,
@@ -244,10 +244,7 @@ impl Room {
/// redacted, all fields except `creator` will be set to their default
/// value.
pub fn create_content(&self) -> Option<RoomCreateWithCreatorEventContent> {
match self.info.read().base_info.create.as_ref()? {
MinimalStateEvent::Original(ev) => Some(ev.content.clone()),
MinimalStateEvent::Redacted(ev) => Some(ev.content.clone()),
}
Some(self.info.read().base_info.create.as_ref()?.content.clone())
}
/// Is this room considered a direct message.
+60 -87
View File
@@ -29,20 +29,24 @@ use ruma::{
events::{
AnyStrippedStateEvent, AnySyncStateEvent, AnySyncTimelineEvent, StateEventType,
SyncStateEvent,
call::member::{CallMemberEventContent, CallMemberStateKey, MembershipData},
call::member::{
CallMemberStateKey, MembershipData, PossiblyRedactedCallMemberEventContent,
},
direct::OwnedDirectUserIdentifier,
room::{
avatar::{self, RoomAvatarEventContent},
canonical_alias::RoomCanonicalAliasEventContent,
avatar::{self, PossiblyRedactedRoomAvatarEventContent},
canonical_alias::PossiblyRedactedRoomCanonicalAliasEventContent,
encryption::RoomEncryptionEventContent,
guest_access::{GuestAccess, RoomGuestAccessEventContent},
history_visibility::{HistoryVisibility, RoomHistoryVisibilityEventContent},
join_rules::{JoinRule, RoomJoinRulesEventContent},
name::RoomNameEventContent,
guest_access::{GuestAccess, PossiblyRedactedRoomGuestAccessEventContent},
history_visibility::{
HistoryVisibility, PossiblyRedactedRoomHistoryVisibilityEventContent,
},
join_rules::{JoinRule, PossiblyRedactedRoomJoinRulesEventContent},
name::PossiblyRedactedRoomNameEventContent,
pinned_events::RoomPinnedEventsEventContent,
redaction::SyncRoomRedactionEvent,
tombstone::RoomTombstoneEventContent,
topic::RoomTopicEventContent,
tombstone::PossiblyRedactedRoomTombstoneEventContent,
topic::PossiblyRedactedRoomTopicEventContent,
},
tag::{TagEventContent, TagName, Tags},
},
@@ -58,7 +62,7 @@ use super::{
RoomHero, RoomNotableTags, RoomState, RoomSummary,
};
use crate::{
MinimalStateEvent, OriginalMinimalStateEvent,
MinimalStateEvent,
deserialized_responses::RawSyncOrStrippedState,
latest_event::LatestEventValue,
notification_settings::RoomNotificationMode,
@@ -126,9 +130,10 @@ impl Room {
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct BaseRoomInfo {
/// The avatar URL of this room.
pub(crate) avatar: Option<MinimalStateEvent<RoomAvatarEventContent>>,
pub(crate) avatar: Option<MinimalStateEvent<PossiblyRedactedRoomAvatarEventContent>>,
/// The canonical alias of this room.
pub(crate) canonical_alias: Option<MinimalStateEvent<RoomCanonicalAliasEventContent>>,
pub(crate) canonical_alias:
Option<MinimalStateEvent<PossiblyRedactedRoomCanonicalAliasEventContent>>,
/// The `m.room.create` event content of this room.
pub(crate) create: Option<MinimalStateEvent<RoomCreateWithCreatorEventContent>>,
/// A list of user ids this room is considered as direct message, if this
@@ -137,24 +142,25 @@ pub struct BaseRoomInfo {
/// The `m.room.encryption` event content that enabled E2EE in this room.
pub(crate) encryption: Option<RoomEncryptionEventContent>,
/// The guest access policy of this room.
pub(crate) guest_access: Option<MinimalStateEvent<RoomGuestAccessEventContent>>,
pub(crate) guest_access: Option<MinimalStateEvent<PossiblyRedactedRoomGuestAccessEventContent>>,
/// The history visibility policy of this room.
pub(crate) history_visibility: Option<MinimalStateEvent<RoomHistoryVisibilityEventContent>>,
pub(crate) history_visibility:
Option<MinimalStateEvent<PossiblyRedactedRoomHistoryVisibilityEventContent>>,
/// The join rule policy of this room.
pub(crate) join_rules: Option<MinimalStateEvent<RoomJoinRulesEventContent>>,
pub(crate) join_rules: Option<MinimalStateEvent<PossiblyRedactedRoomJoinRulesEventContent>>,
/// The maximal power level that can be found in this room.
pub(crate) max_power_level: i64,
/// The `m.room.name` of this room.
pub(crate) name: Option<MinimalStateEvent<RoomNameEventContent>>,
pub(crate) name: Option<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>,
/// The `m.room.tombstone` event content of this room.
pub(crate) tombstone: Option<MinimalStateEvent<RoomTombstoneEventContent>>,
pub(crate) tombstone: Option<MinimalStateEvent<PossiblyRedactedRoomTombstoneEventContent>>,
/// The topic of this room.
pub(crate) topic: Option<MinimalStateEvent<RoomTopicEventContent>>,
pub(crate) topic: Option<MinimalStateEvent<PossiblyRedactedRoomTopicEventContent>>,
/// All minimal state events that containing one or more running matrixRTC
/// memberships.
#[serde(skip_serializing_if = "BTreeMap::is_empty", default)]
pub(crate) rtc_member_events:
BTreeMap<CallMemberStateKey, MinimalStateEvent<CallMemberEventContent>>,
BTreeMap<CallMemberStateKey, MinimalStateEvent<PossiblyRedactedCallMemberEventContent>>,
/// Whether this room has been manually marked as unread.
#[serde(default)]
pub(crate) is_marked_unread: bool,
@@ -182,10 +188,7 @@ impl BaseRoomInfo {
/// For room versions earlier than room version 11, if the event is
/// redacted, this will return the default of [`RoomVersionId::V1`].
pub fn room_version(&self) -> Option<&RoomVersionId> {
match self.create.as_ref()? {
MinimalStateEvent::Original(ev) => Some(&ev.content.room_version),
MinimalStateEvent::Redacted(ev) => Some(&ev.content.room_version),
}
Some(&self.create.as_ref()?.content.room_version)
}
/// Handle a state event for this room and update our info accordingly.
@@ -352,10 +355,8 @@ impl BaseRoomInfo {
.insert(event.state_key.clone(), SyncStateEvent::Original(event).into());
// Remove all events that don't contain any memberships anymore.
self.rtc_member_events.retain(|_, ev| {
ev.as_original()
.is_some_and(|o| !o.content.active_memberships(None).is_empty())
});
self.rtc_member_events
.retain(|_, ev| !ev.content.active_memberships(None).is_empty());
true
} else if let Ok(call_member_key) =
@@ -464,44 +465,44 @@ impl BaseRoomInfo {
.redaction;
if let Some(ev) = &mut self.avatar
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.canonical_alias
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.create
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.guest_access
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.history_visibility
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.join_rules
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.name
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.tombstone
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else if let Some(ev) = &mut self.topic
&& ev.event_id() == Some(redacts)
&& ev.event_id.as_deref() == Some(redacts)
{
ev.redact(&redaction_rules);
} else {
self.rtc_member_events
.retain(|_, member_event| member_event.event_id() != Some(redacts));
.retain(|_, member_event| member_event.event_id.as_deref() != Some(redacts));
}
}
@@ -857,28 +858,22 @@ impl RoomInfo {
/// Returns the current room avatar.
pub fn avatar_url(&self) -> Option<&MxcUri> {
self.base_info
.avatar
.as_ref()
.and_then(|e| e.as_original().and_then(|e| e.content.url.as_deref()))
self.base_info.avatar.as_ref().and_then(|e| e.content.url.as_deref())
}
/// Update the room avatar.
pub fn update_avatar(&mut self, url: Option<OwnedMxcUri>) {
self.base_info.avatar = url.map(|url| {
let mut content = RoomAvatarEventContent::new();
let mut content = PossiblyRedactedRoomAvatarEventContent::new();
content.url = Some(url);
MinimalStateEvent::Original(OriginalMinimalStateEvent { content, event_id: None })
MinimalStateEvent { content, event_id: None }
});
}
/// Returns information about the current room avatar.
pub fn avatar_info(&self) -> Option<&avatar::ImageInfo> {
self.base_info
.avatar
.as_ref()
.and_then(|e| e.as_original().and_then(|e| e.content.info.as_deref()))
self.base_info.avatar.as_ref().and_then(|e| e.content.info.as_deref())
}
/// Update the notifications count.
@@ -974,7 +969,7 @@ impl RoomInfo {
/// Get the canonical alias of this room.
pub fn canonical_alias(&self) -> Option<&RoomAliasId> {
self.base_info.canonical_alias.as_ref()?.as_original()?.content.alias.as_deref()
self.base_info.canonical_alias.as_ref()?.content.alias.as_deref()
}
/// Get the alternative aliases of this room.
@@ -982,7 +977,6 @@ impl RoomInfo {
self.base_info
.canonical_alias
.as_ref()
.and_then(|ev| ev.as_original())
.map(|ev| ev.content.alt_aliases.as_ref())
.unwrap_or_default()
}
@@ -1021,35 +1015,27 @@ impl RoomInfo {
/// Get the room type of this room.
pub fn room_type(&self) -> Option<&RoomType> {
match self.base_info.create.as_ref()? {
MinimalStateEvent::Original(ev) => ev.content.room_type.as_ref(),
MinimalStateEvent::Redacted(ev) => ev.content.room_type.as_ref(),
}
self.base_info.create.as_ref()?.content.room_type.as_ref()
}
/// Get the creators of this room.
pub fn creators(&self) -> Option<Vec<OwnedUserId>> {
match self.base_info.create.as_ref()? {
MinimalStateEvent::Original(ev) => Some(ev.content.creators()),
MinimalStateEvent::Redacted(ev) => Some(ev.content.creators()),
}
Some(self.base_info.create.as_ref()?.content.creators())
}
pub(super) fn guest_access(&self) -> &GuestAccess {
match &self.base_info.guest_access {
Some(MinimalStateEvent::Original(ev)) => &ev.content.guest_access,
_ => &GuestAccess::Forbidden,
}
self.base_info
.guest_access
.as_ref()
.and_then(|event| event.content.guest_access.as_ref())
.unwrap_or(&GuestAccess::Forbidden)
}
/// Returns the history visibility for this room.
///
/// Returns None if the event was never seen during sync.
pub fn history_visibility(&self) -> Option<&HistoryVisibility> {
match &self.base_info.history_visibility {
Some(MinimalStateEvent::Original(ev)) => Some(&ev.content.history_visibility),
_ => None,
}
Some(&self.base_info.history_visibility.as_ref()?.content.history_visibility)
}
/// Returns the history visibility for this room, or a sensible default.
@@ -1059,40 +1045,33 @@ impl RoomInfo {
///
/// [spec]: https://spec.matrix.org/latest/client-server-api/#server-behaviour-7
pub fn history_visibility_or_default(&self) -> &HistoryVisibility {
match &self.base_info.history_visibility {
Some(MinimalStateEvent::Original(ev)) => &ev.content.history_visibility,
_ => &HistoryVisibility::Shared,
}
self.history_visibility().unwrap_or(&HistoryVisibility::Shared)
}
/// Return the join rule for this room, if the `m.room.join_rules` event is
/// available.
pub fn join_rule(&self) -> Option<&JoinRule> {
match &self.base_info.join_rules {
Some(MinimalStateEvent::Original(ev)) => Some(&ev.content.join_rule),
_ => None,
}
Some(&self.base_info.join_rules.as_ref()?.content.join_rule)
}
/// Get the name of this room.
pub fn name(&self) -> Option<&str> {
let name = &self.base_info.name.as_ref()?.as_original()?.content.name;
(!name.is_empty()).then_some(name)
self.base_info.name.as_ref()?.content.name.as_deref().filter(|name| !name.is_empty())
}
/// Get the content of the `m.room.create` event if any.
pub fn create(&self) -> Option<&RoomCreateWithCreatorEventContent> {
Some(&self.base_info.create.as_ref()?.as_original()?.content)
Some(&self.base_info.create.as_ref()?.content)
}
/// Get the content of the `m.room.tombstone` event if any.
pub fn tombstone(&self) -> Option<&RoomTombstoneEventContent> {
Some(&self.base_info.tombstone.as_ref()?.as_original()?.content)
pub fn tombstone(&self) -> Option<&PossiblyRedactedRoomTombstoneEventContent> {
Some(&self.base_info.tombstone.as_ref()?.content)
}
/// Returns the topic for this room, if set.
pub fn topic(&self) -> Option<&str> {
Some(&self.base_info.topic.as_ref()?.as_original()?.content.topic)
self.base_info.topic.as_ref()?.content.topic.as_deref()
}
/// Get a list of all the valid (non expired) matrixRTC memberships and
@@ -1104,15 +1083,9 @@ impl RoomInfo {
.base_info
.rtc_member_events
.iter()
.filter_map(|(user_id, ev)| {
ev.as_original().map(|ev| {
ev.content
.active_memberships(None)
.into_iter()
.map(move |m| (user_id.clone(), m))
})
.flat_map(|(state_key, ev)| {
ev.content.active_memberships(None).into_iter().map(move |m| (state_key.clone(), m))
})
.flatten()
.collect::<Vec<_>>();
v.sort_by_key(|(_, m)| m.created_ts());
v
+7 -7
View File
@@ -12,9 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
use std::ops::Not;
use ruma::{OwnedRoomId, events::room::tombstone::RoomTombstoneEventContent};
use ruma::{OwnedRoomId, events::room::tombstone::PossiblyRedactedRoomTombstoneEventContent};
use super::Room;
@@ -36,7 +34,7 @@ impl Room {
/// event has been received. It's faster than using this method.
///
/// [`m.room.tombstone`]: https://spec.matrix.org/v1.14/client-server-api/#mroomtombstone
pub fn tombstone_content(&self) -> Option<RoomTombstoneEventContent> {
pub fn tombstone_content(&self) -> Option<PossiblyRedactedRoomTombstoneEventContent> {
self.info.read().tombstone().cloned()
}
@@ -48,9 +46,11 @@ impl Room {
///
/// [`m.room.tombstone`]: https://spec.matrix.org/v1.14/client-server-api/#mroomtombstone
pub fn successor_room(&self) -> Option<SuccessorRoom> {
self.tombstone_content().map(|tombstone_event| SuccessorRoom {
room_id: tombstone_event.replacement_room,
reason: tombstone_event.body.is_empty().not().then_some(tombstone_event.body),
self.tombstone_content().and_then(|tombstone_event| {
Some(SuccessorRoom {
room_id: tombstone_event.replacement_room?,
reason: tombstone_event.body.filter(|body| !body.is_empty()),
})
})
}
@@ -193,14 +193,11 @@ impl AmbiguityCache {
let display_name = if let Some(d) = changes
.profiles
.get(room_id)
.and_then(|p| p.get(user_id)?.as_original()?.content.displayname.as_deref())
.and_then(|p| p.get(user_id)?.content.displayname.as_deref())
{
Some(d.to_owned())
} else if let Some(d) = self
.store
.get_profile(room_id, user_id)
.await?
.and_then(|p| p.into_original()?.content.displayname)
} else if let Some(d) =
self.store.get_profile(room_id, user_id).await?.and_then(|p| p.content.displayname)
{
Some(d)
} else {
@@ -1227,7 +1227,7 @@ impl StateStoreIntegrationTests for DynStateStore {
// The profile for the invited user has been updated.
let invited_member_event = self.get_profile(room_id, invited_user_id).await?.unwrap();
assert_eq!(
invited_member_event.as_original().unwrap().content.displayname.as_deref(),
invited_member_event.content.displayname.as_deref(),
Some("example after update")
);
assert!(self.get_member_event(room_id, invited_user_id).await?.is_some());
@@ -20,27 +20,24 @@ use matrix_sdk_common::deserialized_responses::TimelineEvent;
use ruma::{
OwnedRoomId, OwnedUserId, RoomId,
events::{
EmptyStateKey, RedactContent, StateEventContent, StateEventType,
direct::OwnedDirectUserIdentifier,
room::{
avatar::RoomAvatarEventContent,
canonical_alias::RoomCanonicalAliasEventContent,
create::RoomCreateEventContent,
encryption::RoomEncryptionEventContent,
guest_access::RoomGuestAccessEventContent,
history_visibility::RoomHistoryVisibilityEventContent,
join_rules::RoomJoinRulesEventContent,
name::{RedactedRoomNameEventContent, RoomNameEventContent},
tombstone::RoomTombstoneEventContent,
topic::RoomTopicEventContent,
avatar::PossiblyRedactedRoomAvatarEventContent,
canonical_alias::PossiblyRedactedRoomCanonicalAliasEventContent,
create::RoomCreateEventContent, encryption::RoomEncryptionEventContent,
guest_access::PossiblyRedactedRoomGuestAccessEventContent,
history_visibility::PossiblyRedactedRoomHistoryVisibilityEventContent,
join_rules::PossiblyRedactedRoomJoinRulesEventContent,
name::PossiblyRedactedRoomNameEventContent,
tombstone::PossiblyRedactedRoomTombstoneEventContent,
topic::PossiblyRedactedRoomTopicEventContent,
},
},
room_version_rules::RedactionRules,
};
use serde::{Deserialize, Serialize};
use crate::{
MinimalStateEvent, OriginalMinimalStateEvent, RoomInfo, RoomState,
MinimalStateEvent, RoomInfo, RoomState,
deserialized_responses::SyncOrStrippedState,
latest_event::LatestEventValue,
room::{BaseRoomInfo, RoomSummary, SyncInfo},
@@ -143,17 +140,18 @@ fn encryption_state_default() -> bool {
/// [`BaseRoomInfo`] version 1.
#[derive(Clone, Debug, Serialize, Deserialize)]
struct BaseRoomInfoV1 {
avatar: Option<MinimalStateEvent<RoomAvatarEventContent>>,
canonical_alias: Option<MinimalStateEvent<RoomCanonicalAliasEventContent>>,
avatar: Option<MinimalStateEvent<PossiblyRedactedRoomAvatarEventContent>>,
canonical_alias: Option<MinimalStateEvent<PossiblyRedactedRoomCanonicalAliasEventContent>>,
dm_targets: HashSet<OwnedUserId>,
encryption: Option<RoomEncryptionEventContent>,
guest_access: Option<MinimalStateEvent<RoomGuestAccessEventContent>>,
history_visibility: Option<MinimalStateEvent<RoomHistoryVisibilityEventContent>>,
join_rules: Option<MinimalStateEvent<RoomJoinRulesEventContent>>,
guest_access: Option<MinimalStateEvent<PossiblyRedactedRoomGuestAccessEventContent>>,
history_visibility:
Option<MinimalStateEvent<PossiblyRedactedRoomHistoryVisibilityEventContent>>,
join_rules: Option<MinimalStateEvent<PossiblyRedactedRoomJoinRulesEventContent>>,
max_power_level: i64,
name: Option<MinimalStateEvent<RoomNameEventContentV1>>,
tombstone: Option<MinimalStateEvent<RoomTombstoneEventContent>>,
topic: Option<MinimalStateEvent<RoomTopicEventContent>>,
name: Option<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>,
tombstone: Option<MinimalStateEvent<PossiblyRedactedRoomTombstoneEventContent>>,
topic: Option<MinimalStateEvent<PossiblyRedactedRoomTopicEventContent>>,
}
impl BaseRoomInfoV1 {
@@ -180,15 +178,6 @@ impl BaseRoomInfoV1 {
SyncOrStrippedState::Sync(e) => e.into(),
SyncOrStrippedState::Stripped(e) => e.into(),
});
let name = name.map(|name| match name {
MinimalStateEvent::Original(ev) => {
MinimalStateEvent::Original(OriginalMinimalStateEvent {
content: ev.content.into(),
event_id: ev.event_id,
})
}
MinimalStateEvent::Redacted(ev) => MinimalStateEvent::Redacted(ev),
});
let mut converted_dm_targets = HashSet::new();
for dm_target in dm_targets {
@@ -212,31 +201,3 @@ impl BaseRoomInfoV1 {
})
}
}
/// [`RoomNameEventContent`] version 1, with an optional `name`.
#[derive(Clone, Debug, Serialize, Deserialize)]
struct RoomNameEventContentV1 {
name: Option<String>,
}
impl StateEventContent for RoomNameEventContentV1 {
type StateKey = EmptyStateKey;
fn event_type(&self) -> StateEventType {
StateEventType::RoomName
}
}
impl RedactContent for RoomNameEventContentV1 {
type Redacted = RedactedRoomNameEventContent;
fn redact(self, _rules: &RedactionRules) -> Self::Redacted {
RedactedRoomNameEventContent::new()
}
}
impl From<RoomNameEventContentV1> for RoomNameEventContent {
fn from(value: RoomNameEventContentV1) -> Self {
RoomNameEventContent::new(value.name.unwrap_or_default())
}
}
+163 -235
View File
@@ -1,87 +1,34 @@
use ruma::{
EventId, OwnedEventId, assign,
OwnedEventId,
events::{
AnySyncStateEvent, AnySyncTimelineEvent, RedactContent, RedactedStateEventContent,
StateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
SyncStateEvent,
AnySyncStateEvent, AnySyncTimelineEvent, PossiblyRedactedStateEventContent, RedactContent,
RedactedStateEventContent, StateEventType, StaticEventContent, StaticStateEventContent,
StrippedStateEvent, SyncStateEvent,
room::{
avatar::{RoomAvatarEventContent, StrippedRoomAvatarEvent},
canonical_alias::{RoomCanonicalAliasEventContent, StrippedRoomCanonicalAliasEvent},
create::{StrippedRoomCreateEvent, SyncRoomCreateEvent},
guest_access::{
RedactedRoomGuestAccessEventContent, RoomGuestAccessEventContent,
StrippedRoomGuestAccessEvent,
},
history_visibility::{
RoomHistoryVisibilityEventContent, StrippedRoomHistoryVisibilityEvent,
},
join_rules::{RoomJoinRulesEventContent, StrippedRoomJoinRulesEvent},
member::{MembershipState, RoomMemberEventContent},
name::{RedactedRoomNameEventContent, RoomNameEventContent, StrippedRoomNameEvent},
tombstone::{
RedactedRoomTombstoneEventContent, RoomTombstoneEventContent,
StrippedRoomTombstoneEvent,
},
topic::{RedactedRoomTopicEventContent, RoomTopicEventContent, StrippedRoomTopicEvent},
member::PossiblyRedactedRoomMemberEventContent,
},
},
room_version_rules::RedactionRules,
serde::Raw,
};
use serde::{Deserialize, Serialize, de::DeserializeOwned};
use serde::{Deserialize, Serialize};
use tracing::{error, warn};
use crate::room::RoomCreateWithCreatorEventContent;
// #[serde(bound)] instead of DeserializeOwned in type where clause does not
// work, it can only be a single bound that replaces the default and if a helper
// trait is used, the compiler still complains about Deserialize not being
// implemented for C::Redacted.
//
// It is unclear why a Serialize bound on C::Redacted is not also required.
/// A minimal state event.
///
/// This type can hold a possibly-redacted state event with an optional
/// event ID. The event ID is optional so this type can also hold events from
/// invited rooms, where event IDs are not available.
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(bound(
serialize = "C: Serialize, C::Redacted: Serialize",
deserialize = "C: DeserializeOwned, C::Redacted: DeserializeOwned"
))]
pub enum MinimalStateEvent<C: StateEventContent + RedactContent>
where
C::Redacted: RedactedStateEventContent,
{
/// An unredacted event.
Original(OriginalMinimalStateEvent<C>),
/// A redacted event.
Redacted(RedactedMinimalStateEvent<C::Redacted>),
}
/// An unredacted minimal state event.
///
/// For more details see [`MinimalStateEvent`].
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct OriginalMinimalStateEvent<C>
where
C: StateEventContent,
{
/// The event's content.
pub content: C,
/// The event's ID, if known.
pub event_id: Option<OwnedEventId>,
}
/// A redacted minimal state event.
///
/// For more details see [`MinimalStateEvent`].
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct RedactedMinimalStateEvent<C>
where
C: RedactedStateEventContent,
{
#[serde(
bound(serialize = "C: Serialize + Clone"),
from = "MinimalStateEventSerdeHelper<C>",
into = "MinimalStateEventSerdeHelper<C>"
)]
pub struct MinimalStateEvent<C: PossiblyRedactedStateEventContent + RedactContent> {
/// The event's content.
pub content: C,
/// The event's ID, if known.
@@ -90,34 +37,9 @@ where
impl<C> MinimalStateEvent<C>
where
C: StateEventContent + RedactContent,
C::Redacted: RedactedStateEventContent,
C: PossiblyRedactedStateEventContent + RedactContent,
C::Redacted: Into<C>,
{
/// Get the inner event's ID.
pub fn event_id(&self) -> Option<&EventId> {
match self {
MinimalStateEvent::Original(ev) => ev.event_id.as_deref(),
MinimalStateEvent::Redacted(ev) => ev.event_id.as_deref(),
}
}
/// Returns the inner event, if it isn't redacted.
pub fn as_original(&self) -> Option<&OriginalMinimalStateEvent<C>> {
match self {
MinimalStateEvent::Original(ev) => Some(ev),
MinimalStateEvent::Redacted(_) => None,
}
}
/// Converts `self` to the inner `OriginalMinimalStateEvent<C>`, if it isn't
/// redacted.
pub fn into_original(self) -> Option<OriginalMinimalStateEvent<C>> {
match self {
MinimalStateEvent::Original(ev) => Some(ev),
MinimalStateEvent::Redacted(_) => None,
}
}
/// Redacts this event.
///
/// Does nothing if it is already redacted.
@@ -125,63 +47,105 @@ where
where
C: Clone,
{
if let MinimalStateEvent::Original(ev) = self {
*self = MinimalStateEvent::Redacted(RedactedMinimalStateEvent {
content: ev.content.clone().redact(rules),
event_id: ev.event_id.clone(),
});
self.content = self.content.clone().redact(rules).into()
}
}
/// Helper type to (de)serialize [`MinimalStateEvent`].
#[derive(Serialize, Deserialize)]
enum MinimalStateEventSerdeHelper<C> {
/// Previous variant for a non-redacted event.
Original(MinimalStateEventSerdeHelperInner<C>),
/// Previous variant for a redacted event.
Redacted(MinimalStateEventSerdeHelperInner<C>),
/// New variant.
PossiblyRedacted(MinimalStateEventSerdeHelperInner<C>),
}
impl<C> From<MinimalStateEventSerdeHelper<C>> for MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEventSerdeHelper<C>) -> Self {
match value {
MinimalStateEventSerdeHelper::Original(event) => event,
MinimalStateEventSerdeHelper::Redacted(event) => event,
MinimalStateEventSerdeHelper::PossiblyRedacted(event) => event,
}
.into()
}
}
impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelper<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEvent<C>) -> Self {
Self::PossiblyRedacted(value.into())
}
}
#[derive(Serialize, Deserialize)]
struct MinimalStateEventSerdeHelperInner<C> {
content: C,
event_id: Option<OwnedEventId>,
}
impl<C> From<MinimalStateEventSerdeHelperInner<C>> for MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEventSerdeHelperInner<C>) -> Self {
let MinimalStateEventSerdeHelperInner { content, event_id } = value;
Self { content, event_id }
}
}
impl<C> From<MinimalStateEvent<C>> for MinimalStateEventSerdeHelperInner<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(value: MinimalStateEvent<C>) -> Self {
let MinimalStateEvent { content, event_id } = value;
Self { content, event_id }
}
}
/// A minimal `m.room.member` event.
pub type MinimalRoomMemberEvent = MinimalStateEvent<RoomMemberEventContent>;
pub type MinimalRoomMemberEvent = MinimalStateEvent<PossiblyRedactedRoomMemberEventContent>;
impl MinimalRoomMemberEvent {
/// Obtain the membership state, regardless of whether this event is
/// redacted.
pub fn membership(&self) -> &MembershipState {
match self {
MinimalStateEvent::Original(ev) => &ev.content.membership,
MinimalStateEvent::Redacted(ev) => &ev.content.membership,
impl<C1, C2> From<SyncStateEvent<C1>> for MinimalStateEvent<C2>
where
C1: StaticStateEventContent + RedactContent + Into<C2>,
C1::Redacted: RedactedStateEventContent + Into<C2>,
C2: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(ev: SyncStateEvent<C1>) -> Self {
match ev {
SyncStateEvent::Original(ev) => {
Self { content: ev.content.into(), event_id: Some(ev.event_id) }
}
SyncStateEvent::Redacted(ev) => {
Self { content: ev.content.into(), event_id: Some(ev.event_id) }
}
}
}
}
impl<C> From<SyncStateEvent<C>> for MinimalStateEvent<C>
impl<C1, C2> From<&SyncStateEvent<C1>> for MinimalStateEvent<C2>
where
C: StaticStateEventContent + RedactContent,
C::Redacted: RedactedStateEventContent,
C1: Clone + StaticStateEventContent + RedactContent + Into<C2>,
C1::Redacted: Clone + RedactedStateEventContent + Into<C2>,
C2: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(ev: SyncStateEvent<C>) -> Self {
fn from(ev: &SyncStateEvent<C1>) -> Self {
match ev {
SyncStateEvent::Original(ev) => Self::Original(OriginalMinimalStateEvent {
content: ev.content,
event_id: Some(ev.event_id),
}),
SyncStateEvent::Redacted(ev) => Self::Redacted(RedactedMinimalStateEvent {
content: ev.content,
event_id: Some(ev.event_id),
}),
}
}
}
impl<C> From<&SyncStateEvent<C>> for MinimalStateEvent<C>
where
C: Clone + StaticStateEventContent + RedactContent,
C::Redacted: Clone + RedactedStateEventContent,
{
fn from(ev: &SyncStateEvent<C>) -> Self {
match ev {
SyncStateEvent::Original(ev) => Self::Original(OriginalMinimalStateEvent {
content: ev.content.clone(),
event_id: Some(ev.event_id.clone()),
}),
SyncStateEvent::Redacted(ev) => Self::Redacted(RedactedMinimalStateEvent {
content: ev.content.clone(),
event_id: Some(ev.event_id.clone()),
}),
SyncStateEvent::Original(ev) => {
Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
}
SyncStateEvent::Redacted(ev) => {
Self { content: ev.content.clone().into(), event_id: Some(ev.event_id.clone()) }
}
}
}
}
@@ -189,48 +153,39 @@ where
impl From<&SyncRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorEventContent> {
fn from(ev: &SyncRoomCreateEvent) -> Self {
match ev {
SyncStateEvent::Original(ev) => Self::Original(OriginalMinimalStateEvent {
SyncStateEvent::Original(ev) => Self {
content: RoomCreateWithCreatorEventContent::from_event_content(
ev.content.clone(),
ev.sender.clone(),
),
event_id: Some(ev.event_id.clone()),
}),
SyncStateEvent::Redacted(ev) => Self::Redacted(RedactedMinimalStateEvent {
},
SyncStateEvent::Redacted(ev) => Self {
content: RoomCreateWithCreatorEventContent::from_event_content(
ev.content.clone(),
ev.sender.clone(),
),
event_id: Some(ev.event_id.clone()),
}),
},
}
}
}
impl From<&StrippedRoomAvatarEvent> for MinimalStateEvent<RoomAvatarEventContent> {
fn from(event: &StrippedRoomAvatarEvent) -> Self {
let content = assign!(RoomAvatarEventContent::new(), {
info: event.content.info.clone(),
url: event.content.url.clone(),
});
// event might actually be redacted, there is no way to tell for
// stripped state events.
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
impl<C> From<StrippedStateEvent<C>> for MinimalStateEvent<C>
where
C: PossiblyRedactedStateEventContent + RedactContent,
{
fn from(event: StrippedStateEvent<C>) -> Self {
Self { content: event.content, event_id: None }
}
}
impl From<&StrippedRoomNameEvent> for MinimalStateEvent<RoomNameEventContent> {
fn from(event: &StrippedRoomNameEvent) -> Self {
match event.content.name.clone() {
Some(name) => {
let content = RoomNameEventContent::new(name);
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
None => {
let content = RedactedRoomNameEventContent::new();
Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
}
}
impl<C> From<&StrippedStateEvent<C>> for MinimalStateEvent<C>
where
C: Clone + PossiblyRedactedStateEventContent + RedactContent,
{
fn from(event: &StrippedStateEvent<C>) -> Self {
Self { content: event.content.clone(), event_id: None }
}
}
@@ -240,80 +195,7 @@ impl From<&StrippedRoomCreateEvent> for MinimalStateEvent<RoomCreateWithCreatorE
event.content.clone(),
event.sender.clone(),
);
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
}
impl From<&StrippedRoomHistoryVisibilityEvent>
for MinimalStateEvent<RoomHistoryVisibilityEventContent>
{
fn from(event: &StrippedRoomHistoryVisibilityEvent) -> Self {
let content =
RoomHistoryVisibilityEventContent::new(event.content.history_visibility.clone());
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
}
impl From<&StrippedRoomGuestAccessEvent> for MinimalStateEvent<RoomGuestAccessEventContent> {
fn from(event: &StrippedRoomGuestAccessEvent) -> Self {
match &event.content.guest_access {
Some(guest_access) => {
let content = RoomGuestAccessEventContent::new(guest_access.clone());
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
None => {
let content = RedactedRoomGuestAccessEventContent::new();
Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
}
}
}
}
impl From<&StrippedRoomJoinRulesEvent> for MinimalStateEvent<RoomJoinRulesEventContent> {
fn from(event: &StrippedRoomJoinRulesEvent) -> Self {
let content = RoomJoinRulesEventContent::new(event.content.join_rule.clone());
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
}
impl From<&StrippedRoomCanonicalAliasEvent> for MinimalStateEvent<RoomCanonicalAliasEventContent> {
fn from(event: &StrippedRoomCanonicalAliasEvent) -> Self {
let content = assign!(RoomCanonicalAliasEventContent::new(), {
alias: event.content.alias.clone(),
alt_aliases: event.content.alt_aliases.clone(),
});
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
}
impl From<&StrippedRoomTopicEvent> for MinimalStateEvent<RoomTopicEventContent> {
fn from(event: &StrippedRoomTopicEvent) -> Self {
match &event.content.topic {
Some(topic) => {
let content = RoomTopicEventContent::new(topic.clone());
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
None => {
let content = RedactedRoomTopicEventContent::new();
Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
}
}
}
}
impl From<&StrippedRoomTombstoneEvent> for MinimalStateEvent<RoomTombstoneEventContent> {
fn from(event: &StrippedRoomTombstoneEvent) -> Self {
match (&event.content.body, &event.content.replacement_room) {
(Some(body), Some(replacement_room)) => {
let content =
RoomTombstoneEventContent::new(body.clone(), replacement_room.clone());
Self::Original(OriginalMinimalStateEvent { content, event_id: None })
}
_ => {
let content = RedactedRoomTombstoneEventContent::new();
Self::Redacted(RedactedMinimalStateEvent { content, event_id: None })
}
}
Self { content, event_id: None }
}
}
@@ -449,3 +331,49 @@ struct StateEventWithKeysDeHelper {
/// other messages in the timeline.
state_key: Option<String>,
}
#[cfg(test)]
mod tests {
use ruma::{event_id, events::room::name::PossiblyRedactedRoomNameEventContent};
use super::MinimalStateEvent;
#[test]
fn test_backward_compatible_deserialize_minimal_state_event() {
let event_id = event_id!("$event");
// The old format with `Original` and `Redacted` variants works.
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"Original":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name.as_deref(), Some("My Room"));
assert_eq!(event.event_id.as_deref(), Some(event_id));
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"Redacted":{"content":{},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name, None);
assert_eq!(event.event_id.as_deref(), Some(event_id));
// The new format works.
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"PossiblyRedacted":{"content":{"name":"My Room"},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name.as_deref(), Some("My Room"));
assert_eq!(event.event_id.as_deref(), Some(event_id));
let event =
serde_json::from_str::<MinimalStateEvent<PossiblyRedactedRoomNameEventContent>>(
r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#,
)
.unwrap();
assert_eq!(event.content.name, None);
assert_eq!(event.event_id.as_deref(), Some(event_id));
}
}
+4 -1
View File
@@ -1331,7 +1331,10 @@ async fn test_receive_room_tombstone_event_via_sync() {
.await;
// The room info is set and the valid state event is in the store.
assert_eq!(room.tombstone_content().unwrap().replacement_room, tombstone_replacement);
assert_eq!(
room.tombstone_content().unwrap().replacement_room.as_deref(),
Some(tombstone_replacement)
);
assert_matches!(
room.get_state_event_static::<RoomTombstoneEventContent>().await,
Ok(Some(RawSyncOrStrippedState::Sync(raw_event)))