diff --git a/ruma-common/CHANGELOG.md b/ruma-common/CHANGELOG.md index 763f7cd0..f72e1381 100644 --- a/ruma-common/CHANGELOG.md +++ b/ruma-common/CHANGELOG.md @@ -17,6 +17,7 @@ Improvements: * Add `push::{PusherData, PushFormat}` (moved from `ruma_client_api::r0::push`) * Add `authentication::TokenType` (moved from `ruma_client_api::r0::account:request_openid_token`) +* Add `push::AnyPushRule` and `push::Ruleset::into_iter` # 0.2.0 diff --git a/ruma-common/src/push.rs b/ruma-common/src/push.rs index 0bbb0fa4..da9954ce 100644 --- a/ruma-common/src/push.rs +++ b/ruma-common/src/push.rs @@ -13,23 +13,6 @@ //! - room rules //! - sender rules //! - underride rules -//! -//! Each of these kind of rule has a corresponding type that is -//! just a wrapper around another type: -//! -//! - `SimplePushRule` for room and sender rules -//! - `ConditionalPushRule` for override and underride rules: push rules that may depend on a -//! condition -//! - `PatternedPushRules` for content rules, that can filter events based on a pattern to trigger -//! the rule or not -//! -//! Having these wrapper types allows to tell at the type level what kind of rule you are -//! handling, and makes sure the `Ruleset::add` method adds your rule to the correct field -//! of `Ruleset`, and that rules that are not of the same kind are never mixed even if they share -//! the same representation. -//! -//! It is still possible to write code that is generic over a representation by manipulating -//! `SimplePushRule`, `ConditonalPushRule` or `PatternedPushRule` directly, instead of the wrappers. use std::hash::{Hash, Hasher}; @@ -57,24 +40,24 @@ pub use self::{ #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct Ruleset { /// These rules configure behavior for (unencrypted) messages that match certain patterns. - pub content: IndexSet, + pub content: IndexSet, /// These user-configured rules are given the highest priority. /// /// This field is named `override_` instead of `override` because the latter is a reserved /// keyword in Rust. #[serde(rename = "override")] - pub override_: IndexSet, + pub override_: IndexSet, /// These rules change the behavior of all messages for a given room. - pub room: IndexSet, + pub room: IndexSet, /// These rules configure notification behavior for messages from a specific Matrix user ID. - pub sender: IndexSet, + pub sender: IndexSet, /// These rules are identical to override rules, but have a lower priority than `content`, /// `room` and `sender` rules. - pub underride: IndexSet, + pub underride: IndexSet, } impl Ruleset { @@ -88,126 +71,98 @@ impl Ruleset { /// Returns `true` if the new rule was correctly added, and `false` /// if a rule with the same `rule_id` is already present for this kind /// of rule. - pub fn add(&mut self, rule: R) -> bool { - rule.add_to(self) + pub fn add(&mut self, rule: AnyPushRule) -> bool { + match rule { + AnyPushRule::Override(r) => self.override_.insert(r), + AnyPushRule::Underride(r) => self.underride.insert(r), + AnyPushRule::Content(r) => self.content.insert(r), + AnyPushRule::Room(r) => self.room.insert(r), + AnyPushRule::Sender(r) => self.sender.insert(r), + } } } /// Iterator type for `Ruleset` #[derive(Debug)] pub struct RulesetIter { - content: IndexSetIter, - override_: IndexSetIter, - room: IndexSetIter, - sender: IndexSetIter, - underride: IndexSetIter, + content: IndexSetIter, + override_: IndexSetIter, + room: IndexSetIter, + sender: IndexSetIter, + underride: IndexSetIter, } -// impl Iterator for RulesetIter { -// type Item = AnyPushRule; +impl Iterator for RulesetIter { + type Item = AnyPushRule; -// fn next(&mut self) -> Option { -// self.override_ -// .next() -// .map(|x| x.0.into()) -// .or_else(|| self.content.next().map(|x| x.0.into())) -// .or_else(|| self.room.next().map(|x| x.0.into())) -// .or_else(|| self.sender.next().map(|x| x.0.into())) -// .or_else(|| self.underride.next().map(|x| x.0.into())) -// } -// } - -// impl IntoIterator for Ruleset { -// type Item = AnyPushRule; -// type IntoIter = RulesetIter; - -// fn into_iter(self) -> Self::IntoIter { -// RulesetIter { -// content: self.content.into_iter(), -// override_: self.override_.into_iter(), -// room: self.room.into_iter(), -// sender: self.sender.into_iter(), -// underride: self.underride.into_iter(), -// } -// } -// } - -/// A trait for types that can be added in a Ruleset -pub trait RulesetMember: private::Sealed { - /// Adds a value in the correct field of a Ruleset. - #[doc(hidden)] - fn add_to(self, ruleset: &mut Ruleset) -> bool; + fn next(&mut self) -> Option { + self.override_ + .next() + .map(AnyPushRule::Override) + .or_else(|| self.content.next().map(AnyPushRule::Content)) + .or_else(|| self.room.next().map(AnyPushRule::Room)) + .or_else(|| self.sender.next().map(AnyPushRule::Sender)) + .or_else(|| self.underride.next().map(AnyPushRule::Underride)) + } } -mod private { - // See - pub trait Sealed {} - impl Sealed for super::OverridePushRule {} - impl Sealed for super::UnderridePushRule {} - impl Sealed for super::ContentPushRule {} - impl Sealed for super::RoomPushRule {} - impl Sealed for super::SenderPushRule {} +impl IntoIterator for Ruleset { + type Item = AnyPushRule; + type IntoIter = RulesetIter; + + fn into_iter(self) -> Self::IntoIter { + RulesetIter { + content: self.content.into_iter(), + override_: self.override_.into_iter(), + room: self.room.into_iter(), + sender: self.sender.into_iter(), + underride: self.underride.into_iter(), + } + } } -/// Creates a new wrapper type around a PushRule-like type -/// to make it possible to tell what kind of rule it is -/// even if the inner type is the same. -/// -/// For instance, override and underride rules are both -/// represented as `ConditionalPushRule`s, so it is impossible -/// to tell if a rule is an override or an underride rule when -/// all you have is a `ConditionalPushRule`. With these wrapper types -/// it becomes possible. -macro_rules! rulekind { - ($name:ident, $inner:ty, $field:ident) => { - #[derive(Clone, Debug, Serialize, Deserialize)] - #[doc = "Wrapper type to disambiguate the kind of the wrapped rule"] - pub struct $name(pub $inner); +/// The kinds of push rules that are available. +#[derive(Clone, Debug)] +pub enum AnyPushRule { + /// Rules that override all other kinds. + Override(ConditionalPushRule), - impl RulesetMember for $name { - fn add_to(self, ruleset: &mut Ruleset) -> bool { - ruleset.$field.insert(self) - } - } + /// Content-specific rules. + Content(PatternedPushRule), - impl Extend<$name> for Ruleset { - fn extend>(&mut self, iter: T) { - for rule in iter { - rule.add_to(self); - } - } - } + /// Room-specific rules. + Room(SimplePushRule), - // The following trait are needed to be able to make - // an IndexSet of the new type + /// Sender-specific rules. + Sender(SimplePushRule), - impl Hash for $name { - fn hash(&self, state: &mut H) { - self.0.rule_id.hash(state); - } - } - - impl PartialEq for $name { - fn eq(&self, other: &Self) -> bool { - self.0.rule_id == other.0.rule_id - } - } - - impl Eq for $name {} - - impl Equivalent<$name> for str { - fn equivalent(&self, key: &$name) -> bool { - self == key.0.rule_id - } - } - }; + /// Lowest priority rules. + Underride(ConditionalPushRule), } -rulekind!(OverridePushRule, ConditionalPushRule, override_); -rulekind!(UnderridePushRule, ConditionalPushRule, underride); -rulekind!(RoomPushRule, SimplePushRule, room); -rulekind!(SenderPushRule, SimplePushRule, sender); -rulekind!(ContentPushRule, PatternedPushRule, content); +impl AnyPushRule { + /// The `rule_id` of the push rule + pub fn rule_id(&self) -> String { + match self { + Self::Override(rule) => rule.rule_id.clone(), + Self::Underride(rule) => rule.rule_id.clone(), + Self::Content(rule) => rule.rule_id.clone(), + Self::Room(rule) => rule.rule_id.clone(), + Self::Sender(rule) => rule.rule_id.clone(), + } + } +} + +impl Extend for Ruleset { + fn extend(&mut self, iter: T) + where + T: IntoIterator, + { + for rule in iter { + self.add(rule); + } + } +} /// A push rule is a single rule that states under what conditions an event should be passed onto a /// push gateway and how the notification should be presented. @@ -259,6 +214,29 @@ impl From for SimplePushRule { } } +// The following trait are needed to be able to make +// an IndexSet of the type + +impl Hash for SimplePushRule { + fn hash(&self, state: &mut H) { + self.rule_id.hash(state); + } +} + +impl PartialEq for SimplePushRule { + fn eq(&self, other: &Self) -> bool { + self.rule_id == other.rule_id + } +} + +impl Eq for SimplePushRule {} + +impl Equivalent for str { + fn equivalent(&self, key: &SimplePushRule) -> bool { + self == key.rule_id + } +} + /// Like `SimplePushRule`, but with an additional `conditions` field. /// /// Only applicable to underride and override rules. @@ -319,6 +297,29 @@ impl From for ConditionalPushRule { } } +// The following trait are needed to be able to make +// an IndexSet of the type + +impl Hash for ConditionalPushRule { + fn hash(&self, state: &mut H) { + self.rule_id.hash(state); + } +} + +impl PartialEq for ConditionalPushRule { + fn eq(&self, other: &Self) -> bool { + self.rule_id == other.rule_id + } +} + +impl Eq for ConditionalPushRule {} + +impl Equivalent for str { + fn equivalent(&self, key: &ConditionalPushRule) -> bool { + self == key.rule_id + } +} + /// Like `SimplePushRule`, but with an additional `pattern` field. /// /// Only applicable to content rules. @@ -373,6 +374,29 @@ impl From for PatternedPushRule { } } +// The following trait are needed to be able to make +// an IndexSet of the type + +impl Hash for PatternedPushRule { + fn hash(&self, state: &mut H) { + self.rule_id.hash(state); + } +} + +impl PartialEq for PatternedPushRule { + fn eq(&self, other: &Self) -> bool { + self.rule_id == other.rule_id + } +} + +impl Eq for PatternedPushRule {} + +impl Equivalent for str { + fn equivalent(&self, key: &PatternedPushRule) -> bool { + self == key.rule_id + } +} + /// Information for the pusher implementation itself. #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] diff --git a/ruma-common/src/push/predefined.rs b/ruma-common/src/push/predefined.rs index d72855a1..37a74709 100644 --- a/ruma-common/src/push/predefined.rs +++ b/ruma-common/src/push/predefined.rs @@ -5,8 +5,8 @@ use indexmap::indexset; use ruma_identifiers::UserId; use super::{ - Action::*, ConditionalPushRule, ContentPushRule, OverridePushRule, PatternedPushRule, - PushCondition::*, RoomMemberCountIs, Ruleset, Tweak, UnderridePushRule, + Action::*, ConditionalPushRule, PatternedPushRule, PushCondition::*, RoomMemberCountIs, + Ruleset, Tweak, }; impl Ruleset { @@ -20,34 +20,34 @@ impl Ruleset { /// user's ID (for instance those to send notifications when they are mentioned). pub fn server_default(user_id: &UserId) -> Self { Self { - content: indexset![ContentPushRule::contains_user_name(user_id)], + content: indexset![PatternedPushRule::contains_user_name(user_id)], #[cfg(feature = "unstable-pre-spec")] override_: indexset![ - OverridePushRule::master(), - OverridePushRule::suppress_notices(), - OverridePushRule::invite_for_me(user_id), - OverridePushRule::member_event(), - OverridePushRule::contains_display_name(), - OverridePushRule::tombstone(), - OverridePushRule::roomnotif(), - OverridePushRule::reaction(), + ConditionalPushRule::master(), + ConditionalPushRule::suppress_notices(), + ConditionalPushRule::invite_for_me(user_id), + ConditionalPushRule::member_event(), + ConditionalPushRule::contains_display_name(), + ConditionalPushRule::tombstone(), + ConditionalPushRule::roomnotif(), + ConditionalPushRule::reaction(), ], #[cfg(not(feature = "unstable-pre-spec"))] override_: indexset![ - OverridePushRule::master(), - OverridePushRule::suppress_notices(), - OverridePushRule::invite_for_me(user_id), - OverridePushRule::member_event(), - OverridePushRule::contains_display_name(), - OverridePushRule::tombstone(), - OverridePushRule::roomnotif(), + ConditionalPushRule::master(), + ConditionalPushRule::suppress_notices(), + ConditionalPushRule::invite_for_me(user_id), + ConditionalPushRule::member_event(), + ConditionalPushRule::contains_display_name(), + ConditionalPushRule::tombstone(), + ConditionalPushRule::roomnotif(), ], underride: indexset![ - UnderridePushRule::call(), - UnderridePushRule::encrypted_room_one_to_one(), - UnderridePushRule::room_one_to_one(), - UnderridePushRule::message(), - UnderridePushRule::encrypted(), + ConditionalPushRule::call(), + ConditionalPushRule::encrypted_room_one_to_one(), + ConditionalPushRule::room_one_to_one(), + ConditionalPushRule::message(), + ConditionalPushRule::encrypted(), ], ..Default::default() } @@ -55,22 +55,22 @@ impl Ruleset { } /// Default override push rules -impl OverridePushRule { +impl ConditionalPushRule { /// Matches all events, this can be enabled to turn off all push notifications other than those /// generated by override rules set by the user. pub fn master() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![DontNotify], default: true, enabled: false, rule_id: ".m.rule.master".into(), conditions: vec![], - }) + } } /// Matches messages with a `msgtype` of `notice`. pub fn suppress_notices() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![DontNotify], default: true, enabled: true, @@ -79,12 +79,12 @@ impl OverridePushRule { key: "content.msgtype".into(), pattern: "m.notice".into(), }], - }) + } } /// Matches any invites to a new room for this user. pub fn invite_for_me(user_id: &UserId) -> Self { - Self(ConditionalPushRule { + Self { actions: vec![ Notify, SetTweak(Tweak::Sound("default".into())), @@ -98,24 +98,24 @@ impl OverridePushRule { EventMatch { key: "content.membership".into(), pattern: "invite".into() }, EventMatch { key: "state_key".into(), pattern: user_id.to_string() }, ], - }) + } } /// Matches any `m.room.member_event`. pub fn member_event() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![DontNotify], default: true, enabled: true, rule_id: ".m.rule.member_event".into(), conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.member".into() }], - }) + } } /// Matches any message whose content is unencrypted and contains the user's current display /// name in the room in which it was sent. pub fn contains_display_name() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![ Notify, SetTweak(Tweak::Sound("default".into())), @@ -125,14 +125,14 @@ impl OverridePushRule { enabled: true, rule_id: ".m.rule.contains_display_name".into(), conditions: vec![ContainsDisplayName], - }) + } } /// Matches any state event whose type is `m.room.tombstone`. This /// is intended to notify users of a room when it is upgraded, /// similar to what an `@room` notification would accomplish. pub fn tombstone() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![Notify, SetTweak(Tweak::Highlight(true))], default: true, enabled: false, @@ -141,13 +141,13 @@ impl OverridePushRule { EventMatch { key: "type".into(), pattern: "m.room.tombstone".into() }, EventMatch { key: "state_key".into(), pattern: "".into() }, ], - }) + } } /// Matches any message whose content is unencrypted and contains the text `@room`, signifying /// the whole room should be notified of the event. pub fn roomnotif() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![Notify, SetTweak(Tweak::Highlight(true))], default: true, enabled: true, @@ -156,29 +156,29 @@ impl OverridePushRule { EventMatch { key: "content.body".into(), pattern: "@room".into() }, SenderNotificationPermission { key: "room".into() }, ], - }) + } } /// Matches emoji reactions to a message /// MSC2677: Annotations and Reactions #[cfg(feature = "unstable-pre-spec")] pub fn reaction() -> Self { - Self(ConditionalPushRule { + Self { actions: vec![DontNotify], default: true, enabled: true, rule_id: ".m.rule.reaction".into(), conditions: vec![EventMatch { key: "type".into(), pattern: "m.reaction".into() }], - }) + } } } /// Default content push rules -impl ContentPushRule { +impl PatternedPushRule { /// Matches any message whose content is unencrypted and contains the local part of the user's /// Matrix ID, separated by word boundaries. pub fn contains_user_name(user_id: &UserId) -> Self { - Self(PatternedPushRule { + Self { rule_id: ".m.rules.contains_user_name".into(), enabled: true, default: true, @@ -188,15 +188,15 @@ impl ContentPushRule { SetTweak(Tweak::Sound("default".into())), SetTweak(Tweak::Highlight(true)), ], - }) + } } } /// Default underrides push rules -impl UnderridePushRule { +impl ConditionalPushRule { /// Matches any incoming VOIP call. pub fn call() -> Self { - Self(ConditionalPushRule { + Self { rule_id: ".m.rules.call".into(), default: true, enabled: true, @@ -206,7 +206,7 @@ impl UnderridePushRule { SetTweak(Tweak::Sound("ring".into())), SetTweak(Tweak::Highlight(false)), ], - }) + } } /// Matches any encrypted event sent in a room with exactly two members. Unlike other push @@ -214,7 +214,7 @@ impl UnderridePushRule { /// encrypted. This causes the rule to be an "all or nothing" match where it either matches all /// events that are encrypted (in 1:1 rooms) or none. pub fn encrypted_room_one_to_one() -> Self { - Self(ConditionalPushRule { + Self { rule_id: ".m.rules.encrypted_room_one_to_one".into(), default: true, enabled: true, @@ -227,12 +227,12 @@ impl UnderridePushRule { SetTweak(Tweak::Sound("default".into())), SetTweak(Tweak::Highlight(false)), ], - }) + } } /// Matches any message sent in a room with exactly two members. pub fn room_one_to_one() -> Self { - Self(ConditionalPushRule { + Self { rule_id: ".m.rules.room_one_to_one".into(), default: true, enabled: true, @@ -245,18 +245,18 @@ impl UnderridePushRule { SetTweak(Tweak::Sound("default".into())), SetTweak(Tweak::Highlight(false)), ], - }) + } } /// Matches all chat messages. pub fn message() -> Self { - Self(ConditionalPushRule { + Self { rule_id: ".m.rules.message".into(), default: true, enabled: true, conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.message".into() }], actions: vec![Notify, SetTweak(Tweak::Highlight(false))], - }) + } } /// Matches all encrypted events. Unlike other push rules, this rule cannot be matched against @@ -264,12 +264,12 @@ impl UnderridePushRule { /// "all or nothing" match where it either matches all events that are encrypted (in group /// rooms) or none. pub fn encrypted() -> Self { - Self(ConditionalPushRule { + Self { rule_id: ".m.rules.encrypted".into(), default: true, enabled: true, conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() }], actions: vec![Notify, SetTweak(Tweak::Highlight(false))], - }) + } } }