From 08b3c0e47e1febb8476d466962fc3f5c170e651a Mon Sep 17 00:00:00 2001 From: Alfonso Grillo Date: Tue, 3 Oct 2023 10:08:17 +0200 Subject: [PATCH] Feature: add API for setting underride push rule's actions (#2644) --- bindings/matrix-sdk-ffi/src/error.rs | 4 +- crates/matrix-sdk/Cargo.toml | 2 +- crates/matrix-sdk/src/error.rs | 10 +- .../src/notification_settings/mod.rs | 108 +++++++++++++++++- .../notification_settings/rule_commands.rs | 10 +- .../src/notification_settings/rules.rs | 13 ++- 6 files changed, 126 insertions(+), 21 deletions(-) diff --git a/bindings/matrix-sdk-ffi/src/error.rs b/bindings/matrix-sdk-ffi/src/error.rs index 5b33e7559..c880a961a 100644 --- a/bindings/matrix-sdk-ffi/src/error.rs +++ b/bindings/matrix-sdk-ffi/src/error.rs @@ -147,7 +147,7 @@ pub enum NotificationSettingsError { InvalidRoomId(String), /// Rule not found #[error("Rule not found")] - RuleNotFound, + RuleNotFound(String), /// Unable to add push rule. #[error("Unable to add push rule")] UnableToAddPushRule, @@ -165,7 +165,7 @@ pub enum NotificationSettingsError { impl From for NotificationSettingsError { fn from(value: SdkNotificationSettingsError) -> Self { match value { - SdkNotificationSettingsError::RuleNotFound => Self::RuleNotFound, + SdkNotificationSettingsError::RuleNotFound(rule_id) => Self::RuleNotFound(rule_id), SdkNotificationSettingsError::UnableToAddPushRule => Self::UnableToAddPushRule, SdkNotificationSettingsError::UnableToRemovePushRule => Self::UnableToRemovePushRule, SdkNotificationSettingsError::UnableToSavePushRules => Self::UnableToSavePushRules, diff --git a/crates/matrix-sdk/Cargo.toml b/crates/matrix-sdk/Cargo.toml index ef5a8b57d..f394d509e 100644 --- a/crates/matrix-sdk/Cargo.toml +++ b/crates/matrix-sdk/Cargo.toml @@ -88,7 +88,7 @@ matrix-sdk-sqlite = { version = "0.1.0", path = "../matrix-sdk-sqlite", default- mime = "0.3.16" mime2ext = "0.1.52" rand = { version = "0.8.5", optional = true } -ruma = { workspace = true, features = ["rand", "unstable-msc2448", "unstable-msc2965"] } +ruma = { workspace = true, features = ["rand", "unstable-msc2448", "unstable-msc2965", "unstable-msc3930"] } serde = { workspace = true } serde_html_form = { workspace = true } serde_json = { workspace = true } diff --git a/crates/matrix-sdk/src/error.rs b/crates/matrix-sdk/src/error.rs index d2a87392f..40ac14ca8 100644 --- a/crates/matrix-sdk/src/error.rs +++ b/crates/matrix-sdk/src/error.rs @@ -34,7 +34,7 @@ use ruma::{ error::{FromHttpResponseError, IntoHttpError}, }, events::tag::InvalidUserTagName, - push::{InsertPushRuleError, RemovePushRuleError, RuleNotFoundError}, + push::{InsertPushRuleError, RemovePushRuleError}, IdParseError, }; use serde_json::Error as JsonError; @@ -453,7 +453,7 @@ pub enum NotificationSettingsError { UnableToUpdatePushRule, /// Rule not found #[error("Rule not found")] - RuleNotFound, + RuleNotFound(String), /// Unable to save the push rules #[error("Unable to save push rules")] UnableToSavePushRules, @@ -471,12 +471,6 @@ impl From for NotificationSettingsError { } } -impl From for NotificationSettingsError { - fn from(_: RuleNotFoundError) -> Self { - Self::RuleNotFound - } -} - #[derive(Debug, Error)] #[error("expected: {expected}, got: {got:?}")] pub struct WrongRoomState { diff --git a/crates/matrix-sdk/src/notification_settings/mod.rs b/crates/matrix-sdk/src/notification_settings/mod.rs index 56264a166..f60efb91c 100644 --- a/crates/matrix-sdk/src/notification_settings/mod.rs +++ b/crates/matrix-sdk/src/notification_settings/mod.rs @@ -7,7 +7,7 @@ use ruma::{ delete_pushrule, set_pushrule, set_pushrule_actions, set_pushrule_enabled, }, events::push_rules::PushRulesEvent, - push::{Action, RuleKind, Ruleset, Tweak}, + push::{Action, PredefinedUnderrideRuleId, RuleKind, Ruleset, Tweak}, RoomId, }; use tokio::sync::RwLock; @@ -54,7 +54,7 @@ impl From for IsEncrypted { } /// Whether or not a room is a `one-to-one` -#[derive(Debug)] +#[derive(Debug, Clone)] pub enum IsOneToOne { /// A room is a `one-to-one` room if it has exactly two members. Yes, @@ -186,9 +186,10 @@ impl NotificationSettings { is_one_to_one: IsOneToOne, mode: RoomNotificationMode, ) -> Result<(), NotificationSettingsError> { - let rules = self.rules.read().await.clone(); - let rule_id = rules::get_predefined_underride_room_rule_id(is_encrypted, is_one_to_one); - let mut rule_commands = RuleCommands::new(rules.ruleset); + let rule_ids = vec![ + rules::get_predefined_underride_room_rule_id(is_encrypted, is_one_to_one.clone()), + is_one_to_one.into(), + ]; let actions = match mode { RoomNotificationMode::AllMessages => { @@ -199,6 +200,28 @@ impl NotificationSettings { } }; + for rule_id in rule_ids { + self.set_underride_push_rule_actions(rule_id, actions.clone()).await? + } + + Ok(()) + } + + /// Sets the push rule actions for a given underride push rule. + /// [Underride rules](https://spec.matrix.org/v1.8/client-server-api/#push-rules) are the lowest priority push rules + /// + /// # Arguments + /// + /// * `rule_id` - the identifier of the push rule + /// * `actions` - the actions to set for the push rule + pub async fn set_underride_push_rule_actions( + &self, + rule_id: PredefinedUnderrideRuleId, + actions: Vec, + ) -> Result<(), NotificationSettingsError> { + let rules = self.rules.read().await.clone(); + let mut rule_commands = RuleCommands::new(rules.ruleset); + rule_commands.set_rule_actions(RuleKind::Underride, rule_id.as_str(), actions)?; self.run_server_commands(&rule_commands).await?; @@ -926,6 +949,67 @@ mod tests { Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await; let client = logged_in_client(Some(server.uri())).await; + // If the initial mode is `AllMessages` + let mut ruleset = get_server_default_ruleset(); + ruleset + .set_actions( + RuleKind::Underride, + PredefinedUnderrideRuleId::Message, + vec![Action::Notify], + ) + .unwrap(); + + ruleset + .set_actions( + RuleKind::Underride, + PredefinedUnderrideRuleId::PollStart, + vec![Action::Notify], + ) + .unwrap(); + + let settings = NotificationSettings::new(client, ruleset); + assert_eq!( + settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await, + RoomNotificationMode::AllMessages + ); + + // After setting the default mode to `MentionsAndKeywordsOnly` + settings + .set_default_room_notification_mode( + IsEncrypted::No, + IsOneToOne::No, + RoomNotificationMode::MentionsAndKeywordsOnly, + ) + .await + .unwrap(); + + // The list of actions for this rule must be empty + assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::Message), + Some(AnyPushRuleRef::Underride(rule)) => { + assert!(rule.actions.is_empty()); + } + ); + + assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::PollStart), + Some(AnyPushRuleRef::Underride(rule)) => { + assert!(rule.actions.is_empty()); + } + ); + + // and the new mode returned by `get_default_room_notification_mode()` should + // reflect the change. + assert_matches!( + settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::No).await, + RoomNotificationMode::MentionsAndKeywordsOnly + ); + } + + #[async_test] + async fn test_set_default_room_notification_mode_one_to_one() { + let server = MockServer::start().await; + Mock::given(method("PUT")).respond_with(ResponseTemplate::new(200)).mount(&server).await; + let client = logged_in_client(Some(server.uri())).await; + // If the initial mode is `AllMessages` let mut ruleset = get_server_default_ruleset(); ruleset @@ -936,6 +1020,14 @@ mod tests { ) .unwrap(); + ruleset + .set_actions( + RuleKind::Underride, + PredefinedUnderrideRuleId::PollStartOneToOne, + vec![Action::Notify], + ) + .unwrap(); + let settings = NotificationSettings::new(client, ruleset); assert_eq!( settings.get_default_room_notification_mode(IsEncrypted::No, IsOneToOne::Yes).await, @@ -959,6 +1051,12 @@ mod tests { } ); + assert_matches!(settings.rules.read().await.ruleset.get(RuleKind::Underride, PredefinedUnderrideRuleId::PollStartOneToOne), + Some(AnyPushRuleRef::Underride(rule)) => { + assert!(rule.actions.is_empty()); + } + ); + // and the new mode returned by `get_default_room_notification_mode()` should // reflect the change. assert_matches!( diff --git a/crates/matrix-sdk/src/notification_settings/rule_commands.rs b/crates/matrix-sdk/src/notification_settings/rule_commands.rs index cec18c00c..347e2f37a 100644 --- a/crates/matrix-sdk/src/notification_settings/rule_commands.rs +++ b/crates/matrix-sdk/src/notification_settings/rule_commands.rs @@ -73,7 +73,9 @@ impl RuleCommands { rule_id: &str, enabled: bool, ) -> Result<(), NotificationSettingsError> { - self.rules.set_enabled(kind.clone(), rule_id, enabled)?; + self.rules + .set_enabled(kind.clone(), rule_id, enabled) + .map_err(|_| NotificationSettingsError::RuleNotFound(rule_id.to_owned()))?; self.commands.push(Command::SetPushRuleEnabled { scope: RuleScope::Global, kind, @@ -163,7 +165,9 @@ impl RuleCommands { rule_id: &str, actions: Vec, ) -> Result<(), NotificationSettingsError> { - self.rules.set_actions(kind.clone(), rule_id, actions.clone())?; + self.rules + .set_actions(kind.clone(), rule_id, actions.clone()) + .map_err(|_| NotificationSettingsError::RuleNotFound(rule_id.to_owned()))?; self.commands.push(Command::SetPushRuleActions { scope: RuleScope::Global, kind, @@ -349,7 +353,7 @@ mod tests { let mut rule_commands = RuleCommands::new(ruleset); assert_eq!( rule_commands.set_rule_enabled(RuleKind::Room, "unknown_rule_id", true), - Err(NotificationSettingsError::RuleNotFound) + Err(NotificationSettingsError::RuleNotFound("unknown_rule_id".to_string())) ); } diff --git a/crates/matrix-sdk/src/notification_settings/rules.rs b/crates/matrix-sdk/src/notification_settings/rules.rs index 3c06a8271..7bd1f6716 100644 --- a/crates/matrix-sdk/src/notification_settings/rules.rs +++ b/crates/matrix-sdk/src/notification_settings/rules.rs @@ -227,7 +227,7 @@ impl Rules { } else if let Some(rule) = self.ruleset.get(kind, rule_id) { Ok(rule.enabled()) } else { - Err(NotificationSettingsError::RuleNotFound) + Err(NotificationSettingsError::RuleNotFound(rule_id.to_owned())) } } @@ -276,6 +276,15 @@ pub(crate) fn get_predefined_underride_room_rule_id( } } +impl From for PredefinedUnderrideRuleId { + fn from(is_one_to_one: IsOneToOne) -> Self { + match is_one_to_one { + IsOneToOne::Yes => Self::PollStartOneToOne, + IsOneToOne::No => Self::PollStart, + } + } +} + #[cfg(test)] pub(crate) mod tests { use imbl::HashSet; @@ -539,7 +548,7 @@ pub(crate) mod tests { assert_eq!( rules.is_enabled(RuleKind::Override, "unknown_rule_id"), - Err(NotificationSettingsError::RuleNotFound) + Err(NotificationSettingsError::RuleNotFound("unknown_rule_id".to_string())) ); }