matrix_sdk/notification_settings/
rule_commands.rs

1use ruma::{
2    push::{
3        Action, PredefinedContentRuleId, PredefinedOverrideRuleId, RemovePushRuleError, RuleKind,
4        Ruleset,
5    },
6    RoomId,
7};
8
9use super::command::Command;
10use crate::NotificationSettingsError;
11
12/// A `RuleCommand` allows to generate a list of `Command` needed to modify a
13/// `Ruleset`
14#[derive(Clone, Debug)]
15pub(crate) struct RuleCommands {
16    pub(crate) commands: Vec<Command>,
17    pub(crate) rules: Ruleset,
18}
19
20impl RuleCommands {
21    pub(crate) fn new(rules: Ruleset) -> Self {
22        RuleCommands { commands: vec![], rules }
23    }
24
25    /// Insert a new rule
26    pub(crate) fn insert_rule(
27        &mut self,
28        kind: RuleKind,
29        room_id: &RoomId,
30        notify: bool,
31    ) -> Result<(), NotificationSettingsError> {
32        let command = match kind {
33            RuleKind::Room => Command::SetRoomPushRule { room_id: room_id.to_owned(), notify },
34            RuleKind::Override => Command::SetOverridePushRule {
35                rule_id: room_id.to_string(),
36                room_id: room_id.to_owned(),
37                notify,
38            },
39            _ => {
40                return Err(NotificationSettingsError::InvalidParameter(
41                    "cannot insert a rule for this kind.".to_owned(),
42                ))
43            }
44        };
45
46        self.rules.insert(command.to_push_rule()?, None, None)?;
47        self.commands.push(command);
48
49        Ok(())
50    }
51
52    /// Insert a new rule for a keyword.
53    pub(crate) fn insert_keyword_rule(
54        &mut self,
55        keyword: String,
56    ) -> Result<(), NotificationSettingsError> {
57        let command = Command::SetKeywordPushRule { keyword };
58
59        self.rules.insert(command.to_push_rule()?, None, None)?;
60        self.commands.push(command);
61
62        Ok(())
63    }
64
65    /// Delete a rule
66    pub(crate) fn delete_rule(
67        &mut self,
68        kind: RuleKind,
69        rule_id: String,
70    ) -> Result<(), RemovePushRuleError> {
71        self.rules.remove(kind.clone(), &rule_id)?;
72        self.commands.push(Command::DeletePushRule { kind, rule_id });
73
74        Ok(())
75    }
76
77    fn set_enabled_internal(
78        &mut self,
79        kind: RuleKind,
80        rule_id: &str,
81        enabled: bool,
82    ) -> Result<(), NotificationSettingsError> {
83        self.rules
84            .set_enabled(kind.clone(), rule_id, enabled)
85            .map_err(|_| NotificationSettingsError::RuleNotFound(rule_id.to_owned()))?;
86        self.commands.push(Command::SetPushRuleEnabled {
87            kind,
88            rule_id: rule_id.to_owned(),
89            enabled,
90        });
91        Ok(())
92    }
93
94    /// Set whether a rule is enabled
95    pub(crate) fn set_rule_enabled(
96        &mut self,
97        kind: RuleKind,
98        rule_id: &str,
99        enabled: bool,
100    ) -> Result<(), NotificationSettingsError> {
101        if rule_id == PredefinedOverrideRuleId::IsRoomMention.as_str() {
102            // Handle specific case for `PredefinedOverrideRuleId::IsRoomMention`
103            self.set_room_mention_enabled(enabled)
104        } else if rule_id == PredefinedOverrideRuleId::IsUserMention.as_str() {
105            // Handle specific case for `PredefinedOverrideRuleId::IsUserMention`
106            self.set_user_mention_enabled(enabled)
107        } else {
108            self.set_enabled_internal(kind, rule_id, enabled)
109        }
110    }
111
112    /// Set whether `IsUserMention` is enabled
113    fn set_user_mention_enabled(&mut self, enabled: bool) -> Result<(), NotificationSettingsError> {
114        // Add a command for the `IsUserMention` `Override` rule (MSC3952).
115        // This is a new push rule that may not yet be present.
116        self.set_enabled_internal(
117            RuleKind::Override,
118            PredefinedOverrideRuleId::IsUserMention.as_str(),
119            enabled,
120        )?;
121
122        // For compatibility purpose, we still need to add commands for
123        // `ContainsUserName` and `ContainsDisplayName` (deprecated rules).
124        #[allow(deprecated)]
125        {
126            // `ContainsUserName`
127            self.set_enabled_internal(
128                RuleKind::Content,
129                PredefinedContentRuleId::ContainsUserName.as_str(),
130                enabled,
131            )?;
132
133            // `ContainsDisplayName`
134            self.set_enabled_internal(
135                RuleKind::Override,
136                PredefinedOverrideRuleId::ContainsDisplayName.as_str(),
137                enabled,
138            )?;
139        }
140
141        Ok(())
142    }
143
144    /// Set whether `IsRoomMention` is enabled
145    fn set_room_mention_enabled(&mut self, enabled: bool) -> Result<(), NotificationSettingsError> {
146        // Sets the `IsRoomMention` `Override` rule (MSC3952).
147        // This is a new push rule that may not yet be present.
148        self.set_enabled_internal(
149            RuleKind::Override,
150            PredefinedOverrideRuleId::IsRoomMention.as_str(),
151            enabled,
152        )?;
153
154        // For compatibility purpose, we still need to set `RoomNotif` (deprecated
155        // rule).
156        #[allow(deprecated)]
157        self.set_enabled_internal(
158            RuleKind::Override,
159            PredefinedOverrideRuleId::RoomNotif.as_str(),
160            enabled,
161        )?;
162
163        Ok(())
164    }
165
166    /// Set the actions of the rule from the given kind and with the given
167    /// `rule_id`
168    pub(crate) fn set_rule_actions(
169        &mut self,
170        kind: RuleKind,
171        rule_id: &str,
172        actions: Vec<Action>,
173    ) -> Result<(), NotificationSettingsError> {
174        self.rules
175            .set_actions(kind.clone(), rule_id, actions.clone())
176            .map_err(|_| NotificationSettingsError::RuleNotFound(rule_id.to_owned()))?;
177        self.commands.push(Command::SetPushRuleActions {
178            kind,
179            rule_id: rule_id.to_owned(),
180            actions,
181        });
182        Ok(())
183    }
184}
185
186#[cfg(test)]
187mod tests {
188    use assert_matches::assert_matches;
189    use matrix_sdk_test::async_test;
190    use ruma::{
191        push::{
192            Action, NewPushRule, NewSimplePushRule, PredefinedContentRuleId,
193            PredefinedOverrideRuleId, PredefinedUnderrideRuleId, RemovePushRuleError, RuleKind,
194            Ruleset, Tweak,
195        },
196        OwnedRoomId, RoomId, UserId,
197    };
198
199    use super::RuleCommands;
200    use crate::{error::NotificationSettingsError, notification_settings::command::Command};
201
202    fn get_server_default_ruleset() -> Ruleset {
203        let user_id = UserId::parse("@user:matrix.org").unwrap();
204        Ruleset::server_default(&user_id)
205    }
206
207    fn get_test_room_id() -> OwnedRoomId {
208        RoomId::parse("!AAAaAAAAAaaAAaaaaa:matrix.org").unwrap()
209    }
210
211    #[async_test]
212    async fn test_insert_rule_room() {
213        let room_id = get_test_room_id();
214        let mut rule_commands = RuleCommands::new(get_server_default_ruleset());
215        rule_commands.insert_rule(RuleKind::Room, &room_id, true).unwrap();
216
217        // A rule must have been inserted in the ruleset.
218        assert!(rule_commands.rules.get(RuleKind::Room, &room_id).is_some());
219
220        // Exactly one command must have been created.
221        assert_eq!(rule_commands.commands.len(), 1);
222        assert_matches!(&rule_commands.commands[0],
223            Command::SetRoomPushRule { room_id: command_room_id, notify } => {
224                assert_eq!(command_room_id, &room_id);
225                assert!(notify);
226            }
227        );
228    }
229
230    #[async_test]
231    async fn test_insert_rule_override() {
232        let room_id = get_test_room_id();
233        let mut rule_commands = RuleCommands::new(get_server_default_ruleset());
234        rule_commands.insert_rule(RuleKind::Override, &room_id, true).unwrap();
235
236        // A rule must have been inserted in the ruleset.
237        assert!(rule_commands.rules.get(RuleKind::Override, &room_id).is_some());
238
239        // Exactly one command must have been created.
240        assert_eq!(rule_commands.commands.len(), 1);
241        assert_matches!(&rule_commands.commands[0],
242            Command::SetOverridePushRule {room_id: command_room_id, rule_id, notify } => {
243                assert_eq!(command_room_id, &room_id);
244                assert_eq!(rule_id, room_id.as_str());
245                assert!(notify);
246            }
247        );
248    }
249
250    #[async_test]
251    async fn test_insert_rule_unsupported() {
252        let room_id = get_test_room_id();
253        let mut rule_commands = RuleCommands::new(get_server_default_ruleset());
254
255        assert_matches!(
256            rule_commands.insert_rule(RuleKind::Underride, &room_id, true),
257            Err(NotificationSettingsError::InvalidParameter(_)) => {}
258        );
259
260        assert_matches!(
261            rule_commands.insert_rule(RuleKind::Content, &room_id, true),
262            Err(NotificationSettingsError::InvalidParameter(_)) => {}
263        );
264
265        assert_matches!(
266            rule_commands.insert_rule(RuleKind::Sender, &room_id, true),
267            Err(NotificationSettingsError::InvalidParameter(_)) => {}
268        );
269    }
270
271    #[async_test]
272    async fn test_delete_rule() {
273        let room_id = get_test_room_id();
274        let mut ruleset = get_server_default_ruleset();
275
276        let new_rule = NewSimplePushRule::new(room_id.to_owned(), vec![]);
277        ruleset.insert(NewPushRule::Room(new_rule), None, None).unwrap();
278
279        let mut rule_commands = RuleCommands::new(ruleset);
280
281        // Delete must succeed.
282        rule_commands.delete_rule(RuleKind::Room, room_id.to_string()).unwrap();
283
284        // The ruleset must have been updated.
285        assert!(rule_commands.rules.get(RuleKind::Room, &room_id).is_none());
286
287        // Exactly one command must have been created.
288        assert_eq!(rule_commands.commands.len(), 1);
289        assert_matches!(&rule_commands.commands[0],
290            Command::DeletePushRule { kind, rule_id } => {
291                assert_eq!(kind, &RuleKind::Room);
292                assert_eq!(rule_id, room_id.as_str());
293            }
294        );
295    }
296
297    #[async_test]
298    async fn test_delete_rule_errors() {
299        let room_id = get_test_room_id();
300        let ruleset = get_server_default_ruleset();
301
302        let mut rule_commands = RuleCommands::new(ruleset);
303
304        // Deletion should fail if an attempt is made to delete a rule that does not
305        // exist.
306        assert_matches!(
307            rule_commands.delete_rule(RuleKind::Room, room_id.to_string()),
308            Err(RemovePushRuleError::NotFound) => {}
309        );
310
311        // Deletion should fail if an attempt is made to delete a default server rule.
312        assert_matches!(
313            rule_commands.delete_rule(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention.to_string()),
314            Err(RemovePushRuleError::ServerDefault) => {}
315        );
316
317        assert!(rule_commands.commands.is_empty());
318    }
319
320    #[async_test]
321    async fn test_set_rule_enabled() {
322        let mut ruleset = get_server_default_ruleset();
323
324        // Initialize with `Reaction` rule disabled.
325        ruleset.set_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction, false).unwrap();
326
327        let mut rule_commands = RuleCommands::new(ruleset);
328        rule_commands
329            .set_rule_enabled(RuleKind::Override, PredefinedOverrideRuleId::Reaction.as_str(), true)
330            .unwrap();
331
332        // The ruleset must have been updated
333        let rule = rule_commands
334            .rules
335            .get(RuleKind::Override, PredefinedOverrideRuleId::Reaction.as_str())
336            .unwrap();
337        assert!(rule.enabled());
338
339        // Exactly one command must have been created.
340        assert_eq!(rule_commands.commands.len(), 1);
341        assert_matches!(&rule_commands.commands[0],
342            Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
343                assert_eq!(kind, &RuleKind::Override);
344                assert_eq!(rule_id, PredefinedOverrideRuleId::Reaction.as_str());
345                assert!(enabled);
346            }
347        );
348    }
349
350    #[async_test]
351    async fn test_set_rule_enabled_not_found() {
352        let ruleset = get_server_default_ruleset();
353        let mut rule_commands = RuleCommands::new(ruleset);
354        assert_eq!(
355            rule_commands.set_rule_enabled(RuleKind::Room, "unknown_rule_id", true),
356            Err(NotificationSettingsError::RuleNotFound("unknown_rule_id".to_owned()))
357        );
358    }
359
360    #[async_test]
361    async fn test_set_rule_enabled_user_mention() {
362        let mut ruleset = get_server_default_ruleset();
363        let mut rule_commands = RuleCommands::new(ruleset.clone());
364
365        ruleset
366            .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention, false)
367            .unwrap();
368
369        #[allow(deprecated)]
370        {
371            ruleset
372                .set_enabled(
373                    RuleKind::Override,
374                    PredefinedOverrideRuleId::ContainsDisplayName,
375                    false,
376                )
377                .unwrap();
378            ruleset
379                .set_enabled(RuleKind::Content, PredefinedContentRuleId::ContainsUserName, false)
380                .unwrap();
381        }
382
383        // Enable the user mention rule.
384        rule_commands
385            .set_rule_enabled(
386                RuleKind::Override,
387                PredefinedOverrideRuleId::IsUserMention.as_str(),
388                true,
389            )
390            .unwrap();
391
392        // The ruleset must have been updated.
393        assert!(rule_commands
394            .rules
395            .get(RuleKind::Override, PredefinedOverrideRuleId::IsUserMention)
396            .unwrap()
397            .enabled());
398        #[allow(deprecated)]
399        {
400            assert!(rule_commands
401                .rules
402                .get(RuleKind::Override, PredefinedOverrideRuleId::ContainsDisplayName)
403                .unwrap()
404                .enabled());
405            assert!(rule_commands
406                .rules
407                .get(RuleKind::Content, PredefinedContentRuleId::ContainsUserName)
408                .unwrap()
409                .enabled());
410        }
411
412        // Three commands are expected.
413        assert_eq!(rule_commands.commands.len(), 3);
414
415        assert_matches!(&rule_commands.commands[0],
416            Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
417                assert_eq!(kind, &RuleKind::Override);
418                assert_eq!(rule_id, PredefinedOverrideRuleId::IsUserMention.as_str());
419                assert!(enabled);
420            }
421        );
422
423        #[allow(deprecated)]
424        {
425            assert_matches!(&rule_commands.commands[1],
426                Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
427                    assert_eq!(kind, &RuleKind::Content);
428                    assert_eq!(rule_id, PredefinedContentRuleId::ContainsUserName.as_str());
429                    assert!(enabled);
430                }
431            );
432
433            assert_matches!(&rule_commands.commands[2],
434                Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
435                    assert_eq!(kind, &RuleKind::Override);
436                    assert_eq!(rule_id, PredefinedOverrideRuleId::ContainsDisplayName.as_str());
437                    assert!(enabled);
438                }
439            );
440        }
441    }
442
443    #[async_test]
444    async fn test_set_rule_enabled_room_mention() {
445        let mut ruleset = get_server_default_ruleset();
446        let mut rule_commands = RuleCommands::new(ruleset.clone());
447
448        ruleset
449            .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention, false)
450            .unwrap();
451
452        #[allow(deprecated)]
453        {
454            ruleset
455                .set_enabled(RuleKind::Override, PredefinedOverrideRuleId::RoomNotif, false)
456                .unwrap();
457        }
458
459        rule_commands
460            .set_rule_enabled(
461                RuleKind::Override,
462                PredefinedOverrideRuleId::IsRoomMention.as_str(),
463                true,
464            )
465            .unwrap();
466
467        // The ruleset must have been updated.
468        assert!(rule_commands
469            .rules
470            .get(RuleKind::Override, PredefinedOverrideRuleId::IsRoomMention)
471            .unwrap()
472            .enabled());
473        #[allow(deprecated)]
474        {
475            assert!(rule_commands
476                .rules
477                .get(RuleKind::Override, PredefinedOverrideRuleId::RoomNotif)
478                .unwrap()
479                .enabled());
480        }
481
482        // Two commands are expected.
483        assert_eq!(rule_commands.commands.len(), 2);
484
485        assert_matches!(&rule_commands.commands[0],
486            Command::SetPushRuleEnabled {  kind, rule_id, enabled } => {
487                assert_eq!(kind, &RuleKind::Override);
488                assert_eq!(rule_id, PredefinedOverrideRuleId::IsRoomMention.as_str());
489                assert!(enabled);
490            }
491        );
492
493        #[allow(deprecated)]
494        {
495            assert_matches!(&rule_commands.commands[1],
496                Command::SetPushRuleEnabled { kind, rule_id, enabled } => {
497                    assert_eq!(kind, &RuleKind::Override);
498                    assert_eq!(rule_id, PredefinedOverrideRuleId::RoomNotif.as_str());
499                    assert!(enabled);
500                }
501            );
502        }
503    }
504
505    #[async_test]
506    async fn test_set_rule_actions() {
507        let mut ruleset = get_server_default_ruleset();
508        let mut rule_commands = RuleCommands::new(ruleset.clone());
509
510        // Starting with an empty action list for `PredefinedUnderrideRuleId::Message`.
511        ruleset
512            .set_actions(RuleKind::Underride, PredefinedUnderrideRuleId::Message, vec![])
513            .unwrap();
514
515        // After setting a list of actions
516        rule_commands
517            .set_rule_actions(
518                RuleKind::Underride,
519                PredefinedUnderrideRuleId::Message.as_str(),
520                vec![Action::Notify, Action::SetTweak(Tweak::Sound("default".into()))],
521            )
522            .unwrap();
523
524        // The ruleset must have been updated
525        let actions = rule_commands
526            .rules
527            .get(RuleKind::Underride, PredefinedUnderrideRuleId::Message)
528            .unwrap()
529            .actions();
530        assert_eq!(actions.len(), 2);
531
532        // and a `SetPushRuleActions` command must have been added
533        assert_eq!(rule_commands.commands.len(), 1);
534        assert_matches!(&rule_commands.commands[0],
535            Command::SetPushRuleActions { kind, rule_id, actions } => {
536                assert_eq!(kind, &RuleKind::Underride);
537                assert_eq!(rule_id, PredefinedUnderrideRuleId::Message.as_str());
538                assert_eq!(actions.len(), 2);
539                assert_matches!(&actions[0], Action::Notify);
540                assert_matches!(&actions[1], Action::SetTweak(Tweak::Sound(sound)) => {
541                    assert_eq!(sound, "default");
542                });
543            }
544        );
545    }
546}