diff --git a/crates/ruma-common/CHANGELOG.md b/crates/ruma-common/CHANGELOG.md index 2d8483fc..d9259cc4 100644 --- a/crates/ruma-common/CHANGELOG.md +++ b/crates/ruma-common/CHANGELOG.md @@ -9,6 +9,7 @@ Bug fixes: * Fix deserialization of `RoomMessageEventContent` and `RoomEncryptedEventContent` when there is no relation * Fix deserialization of `StateUnsigned` when the `prev_content` is redacted +* Allow to deserialize `PushCondition` with unknown kind Breaking changes: diff --git a/crates/ruma-common/src/push.rs b/crates/ruma-common/src/push.rs index 9a8b994c..f13e5789 100644 --- a/crates/ruma-common/src/push.rs +++ b/crates/ruma-common/src/push.rs @@ -39,6 +39,7 @@ pub use self::{ action::{Action, Tweak}, condition::{ ComparisonOperator, FlattenedJson, PushCondition, PushConditionRoomCtx, RoomMemberCountIs, + _CustomPushCondition, }, iter::{AnyPushRule, AnyPushRuleRef, RulesetIntoIter, RulesetIter}, predefined::{ diff --git a/crates/ruma-common/src/push/condition.rs b/crates/ruma-common/src/push/condition.rs index fdbd403d..4df06d8d 100644 --- a/crates/ruma-common/src/push/condition.rs +++ b/crates/ruma-common/src/push/condition.rs @@ -13,6 +13,7 @@ use crate::{power_levels::NotificationPowerLevels, serde::Raw, OwnedRoomId, Owne #[cfg(feature = "unstable-msc3931")] use crate::{PrivOwnedStr, RoomVersionId}; +mod push_condition_serde; mod room_member_count_is; pub use room_member_count_is::{ComparisonOperator, RoomMemberCountIs}; @@ -57,9 +58,8 @@ impl RoomVersionFeature { } /// A condition that must apply for an associated push rule's action to be taken. -#[derive(Clone, Debug, Deserialize, Serialize)] +#[derive(Clone, Debug)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -#[serde(tag = "kind", rename_all = "snake_case")] pub enum PushCondition { /// A glob pattern match on a field of the event. EventMatch { @@ -95,11 +95,13 @@ pub enum PushCondition { /// Apply the rule only to rooms that support a given feature. #[cfg(feature = "unstable-msc3931")] - #[serde(rename = "org.matrix.msc3931.room_version_supports")] RoomVersionSupports { /// The feature the room must support for the push rule to apply. feature: RoomVersionFeature, }, + + #[doc(hidden)] + _Custom(_CustomPushCondition), } pub(super) fn check_event_match( @@ -168,10 +170,24 @@ impl PushCondition { } RoomVersionFeature::_Custom(_) => false, }, + Self::_Custom(_) => false, } } } +/// An unknown push condition. +#[doc(hidden)] +#[derive(Clone, Debug, Deserialize, Serialize)] +#[allow(clippy::exhaustive_structs)] +pub struct _CustomPushCondition { + /// The kind of the condition. + kind: String, + + /// The additional fields that the condition contains. + #[serde(flatten)] + data: BTreeMap, +} + /// The context of the room associated to an event to be able to test all push conditions. #[derive(Clone, Debug)] #[allow(clippy::exhaustive_structs)] diff --git a/crates/ruma-common/src/push/condition/push_condition_serde.rs b/crates/ruma-common/src/push/condition/push_condition_serde.rs new file mode 100644 index 00000000..0fcdd7d0 --- /dev/null +++ b/crates/ruma-common/src/push/condition/push_condition_serde.rs @@ -0,0 +1,131 @@ +use serde::{de, Deserialize, Serialize, Serializer}; +use serde_json::value::RawValue as RawJsonValue; + +use crate::serde::from_raw_json_value; + +#[cfg(feature = "unstable-msc3931")] +use super::RoomVersionFeature; +use super::{PushCondition, RoomMemberCountIs}; + +impl Serialize for PushCondition { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + PushCondition::_Custom(custom) => custom.serialize(serializer), + _ => PushConditionSerDeHelper::from(self.clone()).serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for PushCondition { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let ExtractKind { kind } = from_raw_json_value(&json)?; + + match kind.as_ref() { + "event_match" + | "contains_display_name" + | "room_member_count" + | "sender_notification_permission" => { + let helper: PushConditionSerDeHelper = from_raw_json_value(&json)?; + Ok(helper.into()) + } + #[cfg(feature = "unstable-msc3931")] + "org.matrix.msc3931.room_version_supports" => { + let helper: PushConditionSerDeHelper = from_raw_json_value(&json)?; + Ok(helper.into()) + } + _ => from_raw_json_value(&json).map(Self::_Custom), + } + } +} + +#[derive(Deserialize)] +struct ExtractKind { + kind: String, +} + +#[derive(Serialize, Deserialize)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +#[serde(tag = "kind", rename_all = "snake_case")] +enum PushConditionSerDeHelper { + /// A glob pattern match on a field of the event. + EventMatch { + /// The dot-separated field of the event to match. + key: String, + + /// The glob-style pattern to match against. + /// + /// Patterns with no special glob characters should be treated as having asterisks + /// prepended and appended when testing the condition. + pattern: String, + }, + + /// Matches unencrypted messages where `content.body` contains the owner's display name in that + /// room. + ContainsDisplayName, + + /// Matches the current number of members in the room. + RoomMemberCount { + /// The condition on the current number of members in the room. + is: RoomMemberCountIs, + }, + + /// Takes into account the current power levels in the room, ensuring the sender of the event + /// has high enough power to trigger the notification. + SenderNotificationPermission { + /// The field in the power level event the user needs a minimum power level for. + /// + /// Fields must be specified under the `notifications` property in the power level event's + /// `content`. + key: String, + }, + + /// Apply the rule only to rooms that support a given feature. + #[cfg(feature = "unstable-msc3931")] + #[serde(rename = "org.matrix.msc3931.room_version_supports")] + RoomVersionSupports { + /// The feature the room must support for the push rule to apply. + feature: RoomVersionFeature, + }, +} + +impl From for PushCondition { + fn from(value: PushConditionSerDeHelper) -> Self { + match value { + PushConditionSerDeHelper::EventMatch { key, pattern } => { + Self::EventMatch { key, pattern } + } + PushConditionSerDeHelper::ContainsDisplayName => Self::ContainsDisplayName, + PushConditionSerDeHelper::RoomMemberCount { is } => Self::RoomMemberCount { is }, + PushConditionSerDeHelper::SenderNotificationPermission { key } => { + Self::SenderNotificationPermission { key } + } + #[cfg(feature = "unstable-msc3931")] + PushConditionSerDeHelper::RoomVersionSupports { feature } => { + Self::RoomVersionSupports { feature } + } + } + } +} + +impl From for PushConditionSerDeHelper { + fn from(value: PushCondition) -> Self { + match value { + PushCondition::EventMatch { key, pattern } => Self::EventMatch { key, pattern }, + PushCondition::ContainsDisplayName => Self::ContainsDisplayName, + PushCondition::RoomMemberCount { is } => Self::RoomMemberCount { is }, + PushCondition::SenderNotificationPermission { key } => { + Self::SenderNotificationPermission { key } + } + #[cfg(feature = "unstable-msc3931")] + PushCondition::RoomVersionSupports { feature } => Self::RoomVersionSupports { feature }, + PushCondition::_Custom(_) => unimplemented!(), + } + } +}