From 9ad7cf9662f3c0df4441e30b52fd4aa6bfa262a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 30 Jan 2026 11:33:11 +0100 Subject: [PATCH] refactor(base): Use PossiblyRedactedStateEventContent bound in MinimalStateEvent MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- crates/matrix-sdk-base/src/lib.rs | 5 +- .../src/response_processors/state_events.rs | 2 +- crates/matrix-sdk-base/src/room/create.rs | 15 +- .../matrix-sdk-base/src/room/display_name.rs | 30 +- crates/matrix-sdk-base/src/room/members.rs | 4 +- crates/matrix-sdk-base/src/room/mod.rs | 7 +- crates/matrix-sdk-base/src/room/room_info.rs | 147 +++---- crates/matrix-sdk-base/src/room/tombstone.rs | 14 +- .../src/store/ambiguity_map.rs | 9 +- .../src/store/integration_tests.rs | 2 +- .../src/store/migration_helpers.rs | 77 +--- crates/matrix-sdk-base/src/utils.rs | 398 +++++++----------- crates/matrix-sdk/tests/integration/sync.rs | 5 +- 13 files changed, 289 insertions(+), 426 deletions(-) diff --git a/crates/matrix-sdk-base/src/lib.rs b/crates/matrix-sdk-base/src/lib.rs index dd7c68f33..fddb67fb0 100644 --- a/crates/matrix-sdk-base/src/lib.rs +++ b/crates/matrix-sdk-base/src/lib.rs @@ -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!(); diff --git a/crates/matrix-sdk-base/src/response_processors/state_events.rs b/crates/matrix-sdk-base/src/response_processors/state_events.rs index 90cd2586b..911a061e9 100644 --- a/crates/matrix-sdk-base/src/response_processors/state_events.rs +++ b/crates/matrix-sdk-base/src/response_processors/state_events.rs @@ -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) diff --git a/crates/matrix-sdk-base/src/room/create.rs b/crates/matrix-sdk-base/src/room/create.rs index 92764726e..20d7479ee 100644 --- a/crates/matrix-sdk-base/src/room/create.rs +++ b/crates/matrix-sdk-base/src/room/create.rs @@ -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 = ::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 = ::StateKey; + + fn event_type(&self) -> StateEventType { + RoomCreateWithCreatorEventContent::TYPE.into() + } +} diff --git a/crates/matrix-sdk-base/src/room/display_name.rs b/crates/matrix-sdk-base/src/room/display_name.rs index 1c782f84c..f3fd35915 100644 --- a/crates/matrix-sdk-base/src/room/display_name.rs +++ b/crates/matrix-sdk-base/src/room/display_name.rs @@ -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, Room) { @@ -584,22 +585,22 @@ mod tests { } fn make_canonical_alias_event() -> MinimalStateEvent { - 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 { - MinimalStateEvent::Original(OriginalMinimalStateEvent { - content: RoomNameEventContent::new(name.to_owned()), + fn make_name_event_with(name: &str) -> MinimalStateEvent { + MinimalStateEvent { + content: RoomNameEventContent::new(name.to_owned()).into(), event_id: None, - }) + } } - fn make_name_event() -> MinimalStateEvent { + fn make_name_event() -> MinimalStateEvent { 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); diff --git a/crates/matrix-sdk-base/src/room/members.rs b/crates/matrix-sdk-base/src/room/members.rs index ad6bfb8c5..7d5bf112b 100644 --- a/crates/matrix-sdk-base/src/room/members.rs +++ b/crates/matrix-sdk-base/src/room/members.rs @@ -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() } diff --git a/crates/matrix-sdk-base/src/room/mod.rs b/crates/matrix-sdk-base/src/room/mod.rs index f03cbbeac..02390bad5 100644 --- a/crates/matrix-sdk-base/src/room/mod.rs +++ b/crates/matrix-sdk-base/src/room/mod.rs @@ -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 { - 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. diff --git a/crates/matrix-sdk-base/src/room/room_info.rs b/crates/matrix-sdk-base/src/room/room_info.rs index 67572f9c5..6954ec195 100644 --- a/crates/matrix-sdk-base/src/room/room_info.rs +++ b/crates/matrix-sdk-base/src/room/room_info.rs @@ -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>, + pub(crate) avatar: Option>, /// The canonical alias of this room. - pub(crate) canonical_alias: Option>, + pub(crate) canonical_alias: + Option>, /// The `m.room.create` event content of this room. pub(crate) create: Option>, /// 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, /// The guest access policy of this room. - pub(crate) guest_access: Option>, + pub(crate) guest_access: Option>, /// The history visibility policy of this room. - pub(crate) history_visibility: Option>, + pub(crate) history_visibility: + Option>, /// The join rule policy of this room. - pub(crate) join_rules: Option>, + pub(crate) join_rules: Option>, /// 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>, + pub(crate) name: Option>, /// The `m.room.tombstone` event content of this room. - pub(crate) tombstone: Option>, + pub(crate) tombstone: Option>, /// The topic of this room. - pub(crate) topic: Option>, + pub(crate) topic: Option>, /// 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>, + BTreeMap>, /// 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) { 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> { - 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::>(); v.sort_by_key(|(_, m)| m.created_ts()); v diff --git a/crates/matrix-sdk-base/src/room/tombstone.rs b/crates/matrix-sdk-base/src/room/tombstone.rs index 88c831138..704063e1c 100644 --- a/crates/matrix-sdk-base/src/room/tombstone.rs +++ b/crates/matrix-sdk-base/src/room/tombstone.rs @@ -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 { + pub fn tombstone_content(&self) -> Option { 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 { - 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()), + }) }) } diff --git a/crates/matrix-sdk-base/src/store/ambiguity_map.rs b/crates/matrix-sdk-base/src/store/ambiguity_map.rs index 896a947df..8080022c9 100644 --- a/crates/matrix-sdk-base/src/store/ambiguity_map.rs +++ b/crates/matrix-sdk-base/src/store/ambiguity_map.rs @@ -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 { diff --git a/crates/matrix-sdk-base/src/store/integration_tests.rs b/crates/matrix-sdk-base/src/store/integration_tests.rs index 2cc066e81..2207446f0 100644 --- a/crates/matrix-sdk-base/src/store/integration_tests.rs +++ b/crates/matrix-sdk-base/src/store/integration_tests.rs @@ -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()); diff --git a/crates/matrix-sdk-base/src/store/migration_helpers.rs b/crates/matrix-sdk-base/src/store/migration_helpers.rs index 6b086250b..309842458 100644 --- a/crates/matrix-sdk-base/src/store/migration_helpers.rs +++ b/crates/matrix-sdk-base/src/store/migration_helpers.rs @@ -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>, - canonical_alias: Option>, + avatar: Option>, + canonical_alias: Option>, dm_targets: HashSet, encryption: Option, - guest_access: Option>, - history_visibility: Option>, - join_rules: Option>, + guest_access: Option>, + history_visibility: + Option>, + join_rules: Option>, max_power_level: i64, - name: Option>, - tombstone: Option>, - topic: Option>, + name: Option>, + tombstone: Option>, + topic: Option>, } 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, -} - -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 for RoomNameEventContent { - fn from(value: RoomNameEventContentV1) -> Self { - RoomNameEventContent::new(value.name.unwrap_or_default()) - } -} diff --git a/crates/matrix-sdk-base/src/utils.rs b/crates/matrix-sdk-base/src/utils.rs index c60da6d73..3615518ae 100644 --- a/crates/matrix-sdk-base/src/utils.rs +++ b/crates/matrix-sdk-base/src/utils.rs @@ -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 -where - C::Redacted: RedactedStateEventContent, -{ - /// An unredacted event. - Original(OriginalMinimalStateEvent), - /// A redacted event. - Redacted(RedactedMinimalStateEvent), -} - -/// An unredacted minimal state event. -/// -/// For more details see [`MinimalStateEvent`]. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct OriginalMinimalStateEvent -where - C: StateEventContent, -{ - /// The event's content. - pub content: C, - /// The event's ID, if known. - pub event_id: Option, -} - -/// A redacted minimal state event. -/// -/// For more details see [`MinimalStateEvent`]. -#[derive(Clone, Debug, Deserialize, Serialize)] -pub struct RedactedMinimalStateEvent -where - C: RedactedStateEventContent, -{ +#[serde( + bound(serialize = "C: Serialize + Clone"), + from = "MinimalStateEventSerdeHelper", + into = "MinimalStateEventSerdeHelper" +)] +pub struct MinimalStateEvent { /// The event's content. pub content: C, /// The event's ID, if known. @@ -90,34 +37,9 @@ where impl MinimalStateEvent where - C: StateEventContent + RedactContent, - C::Redacted: RedactedStateEventContent, + C: PossiblyRedactedStateEventContent + RedactContent, + C::Redacted: Into, { - /// 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> { - match self { - MinimalStateEvent::Original(ev) => Some(ev), - MinimalStateEvent::Redacted(_) => None, - } - } - - /// Converts `self` to the inner `OriginalMinimalStateEvent`, if it isn't - /// redacted. - pub fn into_original(self) -> Option> { - 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 { + /// Previous variant for a non-redacted event. + Original(MinimalStateEventSerdeHelperInner), + /// Previous variant for a redacted event. + Redacted(MinimalStateEventSerdeHelperInner), + /// New variant. + PossiblyRedacted(MinimalStateEventSerdeHelperInner), +} + +impl From> for MinimalStateEvent +where + C: PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(value: MinimalStateEventSerdeHelper) -> Self { + match value { + MinimalStateEventSerdeHelper::Original(event) => event, + MinimalStateEventSerdeHelper::Redacted(event) => event, + MinimalStateEventSerdeHelper::PossiblyRedacted(event) => event, } + .into() + } +} + +impl From> for MinimalStateEventSerdeHelper +where + C: PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(value: MinimalStateEvent) -> Self { + Self::PossiblyRedacted(value.into()) + } +} + +#[derive(Serialize, Deserialize)] +struct MinimalStateEventSerdeHelperInner { + content: C, + event_id: Option, +} + +impl From> for MinimalStateEvent +where + C: PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(value: MinimalStateEventSerdeHelperInner) -> Self { + let MinimalStateEventSerdeHelperInner { content, event_id } = value; + Self { content, event_id } + } +} + +impl From> for MinimalStateEventSerdeHelperInner +where + C: PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(value: MinimalStateEvent) -> Self { + let MinimalStateEvent { content, event_id } = value; + Self { content, event_id } } } /// A minimal `m.room.member` event. -pub type MinimalRoomMemberEvent = MinimalStateEvent; +pub type MinimalRoomMemberEvent = MinimalStateEvent; -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 From> for MinimalStateEvent +where + C1: StaticStateEventContent + RedactContent + Into, + C1::Redacted: RedactedStateEventContent + Into, + C2: PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(ev: SyncStateEvent) -> 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 From> for MinimalStateEvent +impl From<&SyncStateEvent> for MinimalStateEvent where - C: StaticStateEventContent + RedactContent, - C::Redacted: RedactedStateEventContent, + C1: Clone + StaticStateEventContent + RedactContent + Into, + C1::Redacted: Clone + RedactedStateEventContent + Into, + C2: PossiblyRedactedStateEventContent + RedactContent, { - fn from(ev: SyncStateEvent) -> Self { + fn from(ev: &SyncStateEvent) -> 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 From<&SyncStateEvent> for MinimalStateEvent -where - C: Clone + StaticStateEventContent + RedactContent, - C::Redacted: Clone + RedactedStateEventContent, -{ - fn from(ev: &SyncStateEvent) -> 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 { 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 { - 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 From> for MinimalStateEvent +where + C: PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(event: StrippedStateEvent) -> Self { + Self { content: event.content, event_id: None } } } -impl From<&StrippedRoomNameEvent> for MinimalStateEvent { - 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 From<&StrippedStateEvent> for MinimalStateEvent +where + C: Clone + PossiblyRedactedStateEventContent + RedactContent, +{ + fn from(event: &StrippedStateEvent) -> Self { + Self { content: event.content.clone(), event_id: None } } } @@ -240,80 +195,7 @@ impl From<&StrippedRoomCreateEvent> for MinimalStateEvent - for MinimalStateEvent -{ - 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 { - 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 { - 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 { - 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 { - 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 { - 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, } + +#[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::>( + 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::>( + 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::>( + 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::>( + r#"{"PossiblyRedacted":{"content":{},"event_id":"$event"}}"#, + ) + .unwrap(); + assert_eq!(event.content.name, None); + assert_eq!(event.event_id.as_deref(), Some(event_id)); + } +} diff --git a/crates/matrix-sdk/tests/integration/sync.rs b/crates/matrix-sdk/tests/integration/sync.rs index 056f9a902..4361d4743 100644 --- a/crates/matrix-sdk/tests/integration/sync.rs +++ b/crates/matrix-sdk/tests/integration/sync.rs @@ -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::().await, Ok(Some(RawSyncOrStrippedState::Sync(raw_event)))