ruma_common/push/
predefined.rs

1//! Constructors for [predefined push rules].
2//!
3//! [predefined push rules]: https://spec.matrix.org/latest/client-server-api/#predefined-rules
4
5use ruma_macros::StringEnum;
6
7use super::{
8    Action::*, ConditionalPushRule, PatternedPushRule, PushCondition::*, RoomMemberCountIs,
9    RuleKind, Ruleset, Tweak,
10};
11use crate::{power_levels::NotificationPowerLevelsKey, PrivOwnedStr, UserId};
12
13impl Ruleset {
14    /// The list of all [predefined push rules].
15    ///
16    /// # Parameters
17    ///
18    /// - `user_id`: the user for which to generate the default rules. Some rules depend on the
19    ///   user's ID (for instance those to send notifications when they are mentioned).
20    ///
21    /// [predefined push rules]: https://spec.matrix.org/latest/client-server-api/#predefined-rules
22    pub fn server_default(user_id: &UserId) -> Self {
23        Self {
24            content: [
25                #[allow(deprecated)]
26                PatternedPushRule::contains_user_name(user_id),
27            ]
28            .into(),
29            override_: [
30                ConditionalPushRule::master(),
31                ConditionalPushRule::suppress_notices(),
32                ConditionalPushRule::invite_for_me(user_id),
33                ConditionalPushRule::member_event(),
34                ConditionalPushRule::is_user_mention(user_id),
35                #[allow(deprecated)]
36                ConditionalPushRule::contains_display_name(),
37                ConditionalPushRule::is_room_mention(),
38                #[allow(deprecated)]
39                ConditionalPushRule::roomnotif(),
40                ConditionalPushRule::tombstone(),
41                ConditionalPushRule::reaction(),
42                ConditionalPushRule::server_acl(),
43                ConditionalPushRule::suppress_edits(),
44                #[cfg(feature = "unstable-msc3930")]
45                ConditionalPushRule::poll_response(),
46            ]
47            .into(),
48            underride: [
49                #[cfg(feature = "unstable-msc4306")]
50                ConditionalPushRule::unsubscribed_thread(),
51                #[cfg(feature = "unstable-msc4306")]
52                ConditionalPushRule::subscribed_thread(),
53                ConditionalPushRule::call(),
54                ConditionalPushRule::encrypted_room_one_to_one(),
55                ConditionalPushRule::room_one_to_one(),
56                ConditionalPushRule::message(),
57                ConditionalPushRule::encrypted(),
58                #[cfg(feature = "unstable-msc3930")]
59                ConditionalPushRule::poll_start_one_to_one(),
60                #[cfg(feature = "unstable-msc3930")]
61                ConditionalPushRule::poll_start(),
62                #[cfg(feature = "unstable-msc3930")]
63                ConditionalPushRule::poll_end_one_to_one(),
64                #[cfg(feature = "unstable-msc3930")]
65                ConditionalPushRule::poll_end(),
66            ]
67            .into(),
68            ..Default::default()
69        }
70    }
71
72    /// Update this ruleset with the given server-default push rules.
73    ///
74    /// This will replace the server-default rules in this ruleset (with `default` set to `true`)
75    /// with the given ones while keeping the `enabled` and `actions` fields in the same state.
76    ///
77    /// The default rules in this ruleset that are not in the new server-default rules are removed.
78    ///
79    /// # Parameters
80    ///
81    /// - `server_default`: the new server-default push rules. This ruleset must not contain
82    ///   non-default rules.
83    pub fn update_with_server_default(&mut self, mut new_server_default: Ruleset) {
84        // Copy the default rules states from the old rules to the new rules and remove the
85        // server-default rules from the old rules.
86        macro_rules! copy_rules_state {
87            ($new_ruleset:ident, $old_ruleset:ident, @fields $($field_name:ident),+) => {
88                $(
89                    $new_ruleset.$field_name = $new_ruleset
90                        .$field_name
91                        .into_iter()
92                        .map(|mut new_rule| {
93                            if let Some(old_rule) =
94                                $old_ruleset.$field_name.shift_take(new_rule.rule_id.as_str())
95                            {
96                                new_rule.enabled = old_rule.enabled;
97                                new_rule.actions = old_rule.actions;
98                            }
99
100                            new_rule
101                        })
102                        .collect();
103                )+
104            };
105        }
106        copy_rules_state!(new_server_default, self, @fields override_, content, room, sender, underride);
107
108        // Remove the remaining server-default rules from the old rules.
109        macro_rules! remove_remaining_default_rules {
110            ($ruleset:ident, @fields $($field_name:ident),+) => {
111                $(
112                    $ruleset.$field_name.retain(|rule| !rule.default);
113                )+
114            };
115        }
116        remove_remaining_default_rules!(self, @fields override_, content, room, sender, underride);
117
118        // `.m.rule.master` comes before all other push rules, while the other server-default push
119        // rules come after.
120        if let Some(master_rule) =
121            new_server_default.override_.shift_take(PredefinedOverrideRuleId::Master.as_str())
122        {
123            let (pos, _) = self.override_.insert_full(master_rule);
124            self.override_.move_index(pos, 0);
125        }
126
127        // Merge the new server-default rules into the old rules.
128        macro_rules! merge_rules {
129            ($old_ruleset:ident, $new_ruleset:ident, @fields $($field_name:ident),+) => {
130                $(
131                    $old_ruleset.$field_name.extend($new_ruleset.$field_name);
132                )+
133            };
134        }
135        merge_rules!(self, new_server_default, @fields override_, content, room, sender, underride);
136    }
137}
138
139/// Default override push rules
140impl ConditionalPushRule {
141    /// Matches all events, this can be enabled to turn off all push notifications other than those
142    /// generated by override rules set by the user.
143    pub fn master() -> Self {
144        Self {
145            actions: vec![],
146            default: true,
147            enabled: false,
148            rule_id: PredefinedOverrideRuleId::Master.to_string(),
149            conditions: vec![],
150        }
151    }
152
153    /// Matches messages with a `msgtype` of `notice`.
154    pub fn suppress_notices() -> Self {
155        Self {
156            actions: vec![],
157            default: true,
158            enabled: true,
159            rule_id: PredefinedOverrideRuleId::SuppressNotices.to_string(),
160            conditions: vec![EventMatch {
161                key: "content.msgtype".into(),
162                pattern: "m.notice".into(),
163            }],
164        }
165    }
166
167    /// Matches any invites to a new room for this user.
168    pub fn invite_for_me(user_id: &UserId) -> Self {
169        Self {
170            actions: vec![
171                Notify,
172                SetTweak(Tweak::Sound("default".into())),
173                SetTweak(Tweak::Highlight(false)),
174            ],
175            default: true,
176            enabled: true,
177            rule_id: PredefinedOverrideRuleId::InviteForMe.to_string(),
178            conditions: vec![
179                EventMatch { key: "type".into(), pattern: "m.room.member".into() },
180                EventMatch { key: "content.membership".into(), pattern: "invite".into() },
181                EventMatch { key: "state_key".into(), pattern: user_id.to_string() },
182            ],
183        }
184    }
185
186    /// Matches any `m.room.member_event`.
187    pub fn member_event() -> Self {
188        Self {
189            actions: vec![],
190            default: true,
191            enabled: true,
192            rule_id: PredefinedOverrideRuleId::MemberEvent.to_string(),
193            conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.member".into() }],
194        }
195    }
196
197    /// Matches any message which contains the user’s Matrix ID in the list of `user_ids` under the
198    /// `m.mentions` property.
199    pub fn is_user_mention(user_id: &UserId) -> Self {
200        Self {
201            actions: vec![
202                Notify,
203                SetTweak(Tweak::Sound("default".to_owned())),
204                SetTweak(Tweak::Highlight(true)),
205            ],
206            default: true,
207            enabled: true,
208            rule_id: PredefinedOverrideRuleId::IsUserMention.to_string(),
209            conditions: vec![EventPropertyContains {
210                key: r"content.m\.mentions.user_ids".to_owned(),
211                value: user_id.as_str().into(),
212            }],
213        }
214    }
215
216    /// Matches any message whose content is unencrypted and contains the user's current display
217    /// name in the room in which it was sent.
218    ///
219    /// Since Matrix 1.7, this rule only matches if the event's content does not contain an
220    /// `m.mentions` property.
221    #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
222    pub fn contains_display_name() -> Self {
223        #[allow(deprecated)]
224        Self {
225            actions: vec![
226                Notify,
227                SetTweak(Tweak::Sound("default".into())),
228                SetTweak(Tweak::Highlight(true)),
229            ],
230            default: true,
231            enabled: true,
232            rule_id: PredefinedOverrideRuleId::ContainsDisplayName.to_string(),
233            conditions: vec![ContainsDisplayName],
234        }
235    }
236
237    /// Matches any state event whose type is `m.room.tombstone`. This
238    /// is intended to notify users of a room when it is upgraded,
239    /// similar to what an `@room` notification would accomplish.
240    pub fn tombstone() -> Self {
241        Self {
242            actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
243            default: true,
244            enabled: true,
245            rule_id: PredefinedOverrideRuleId::Tombstone.to_string(),
246            conditions: vec![
247                EventMatch { key: "type".into(), pattern: "m.room.tombstone".into() },
248                EventMatch { key: "state_key".into(), pattern: "".into() },
249            ],
250        }
251    }
252
253    /// Matches any message from a sender with the proper power level with the `room` property of
254    /// the `m.mentions` property set to `true`.
255    pub fn is_room_mention() -> Self {
256        Self {
257            actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
258            default: true,
259            enabled: true,
260            rule_id: PredefinedOverrideRuleId::IsRoomMention.to_string(),
261            conditions: vec![
262                EventPropertyIs { key: r"content.m\.mentions.room".to_owned(), value: true.into() },
263                SenderNotificationPermission { key: NotificationPowerLevelsKey::Room },
264            ],
265        }
266    }
267
268    /// Matches any message whose content is unencrypted and contains the text `@room`, signifying
269    /// the whole room should be notified of the event.
270    ///
271    /// Since Matrix 1.7, this rule only matches if the event's content does not contain an
272    /// `m.mentions` property.
273    #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_room_mention() instead."]
274    pub fn roomnotif() -> Self {
275        #[allow(deprecated)]
276        Self {
277            actions: vec![Notify, SetTweak(Tweak::Highlight(true))],
278            default: true,
279            enabled: true,
280            rule_id: PredefinedOverrideRuleId::RoomNotif.to_string(),
281            conditions: vec![
282                EventMatch { key: "content.body".into(), pattern: "@room".into() },
283                SenderNotificationPermission { key: "room".into() },
284            ],
285        }
286    }
287
288    /// Matches [reactions] to a message.
289    ///
290    /// [reactions]: https://spec.matrix.org/latest/client-server-api/#event-annotations-and-reactions
291    pub fn reaction() -> Self {
292        Self {
293            actions: vec![],
294            default: true,
295            enabled: true,
296            rule_id: PredefinedOverrideRuleId::Reaction.to_string(),
297            conditions: vec![EventMatch { key: "type".into(), pattern: "m.reaction".into() }],
298        }
299    }
300
301    /// Matches [room server ACLs].
302    ///
303    /// [room server ACLs]: https://spec.matrix.org/latest/client-server-api/#server-access-control-lists-acls-for-rooms
304    pub fn server_acl() -> Self {
305        Self {
306            actions: vec![],
307            default: true,
308            enabled: true,
309            rule_id: PredefinedOverrideRuleId::RoomServerAcl.to_string(),
310            conditions: vec![
311                EventMatch { key: "type".into(), pattern: "m.room.server_acl".into() },
312                EventMatch { key: "state_key".into(), pattern: "".into() },
313            ],
314        }
315    }
316
317    /// Matches [event replacements].
318    ///
319    /// [event replacements]: https://spec.matrix.org/latest/client-server-api/#event-replacements
320    pub fn suppress_edits() -> Self {
321        Self {
322            actions: vec![],
323            default: true,
324            enabled: true,
325            rule_id: PredefinedOverrideRuleId::SuppressEdits.to_string(),
326            conditions: vec![EventPropertyIs {
327                key: r"content.m\.relates_to.rel_type".to_owned(),
328                value: "m.replace".into(),
329            }],
330        }
331    }
332
333    /// Matches a poll response event sent in any room.
334    ///
335    /// This rule uses the unstable prefixes defined in [MSC3381] and [MSC3930].
336    ///
337    /// [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
338    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
339    #[cfg(feature = "unstable-msc3930")]
340    pub fn poll_response() -> Self {
341        Self {
342            rule_id: PredefinedOverrideRuleId::PollResponse.to_string(),
343            default: true,
344            enabled: true,
345            conditions: vec![EventPropertyIs {
346                key: "type".to_owned(),
347                value: "org.matrix.msc3381.poll.response".into(),
348            }],
349            actions: vec![],
350        }
351    }
352}
353
354/// Default content push rules
355impl PatternedPushRule {
356    /// Matches any message whose content is unencrypted and contains the local part of the user's
357    /// Matrix ID, separated by word boundaries.
358    ///
359    /// Since Matrix 1.7, this rule only matches if the event's content does not contain an
360    /// `m.mentions` property.
361    #[deprecated = "Since Matrix 1.7. Use the m.mentions property with ConditionalPushRule::is_user_mention() instead."]
362    pub fn contains_user_name(user_id: &UserId) -> Self {
363        #[allow(deprecated)]
364        Self {
365            rule_id: PredefinedContentRuleId::ContainsUserName.to_string(),
366            enabled: true,
367            default: true,
368            pattern: user_id.localpart().into(),
369            actions: vec![
370                Notify,
371                SetTweak(Tweak::Sound("default".into())),
372                SetTweak(Tweak::Highlight(true)),
373            ],
374        }
375    }
376}
377
378/// Default underrides push rules
379impl ConditionalPushRule {
380    /// Matches any incoming VOIP call.
381    pub fn call() -> Self {
382        Self {
383            rule_id: PredefinedUnderrideRuleId::Call.to_string(),
384            default: true,
385            enabled: true,
386            conditions: vec![EventMatch { key: "type".into(), pattern: "m.call.invite".into() }],
387            actions: vec![
388                Notify,
389                SetTweak(Tweak::Sound("ring".into())),
390                SetTweak(Tweak::Highlight(false)),
391            ],
392        }
393    }
394
395    /// Matches any encrypted event sent in a room with exactly two members.
396    ///
397    /// Unlike other push rules, this rule cannot be matched against the content of the event by
398    /// nature of it being encrypted. This causes the rule to be an "all or nothing" match where it
399    /// either matches all events that are encrypted (in 1:1 rooms) or none.
400    pub fn encrypted_room_one_to_one() -> Self {
401        Self {
402            rule_id: PredefinedUnderrideRuleId::EncryptedRoomOneToOne.to_string(),
403            default: true,
404            enabled: true,
405            conditions: vec![
406                RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
407                EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() },
408            ],
409            actions: vec![
410                Notify,
411                SetTweak(Tweak::Sound("default".into())),
412                SetTweak(Tweak::Highlight(false)),
413            ],
414        }
415    }
416
417    /// Matches any message sent in a room with exactly two members.
418    pub fn room_one_to_one() -> Self {
419        Self {
420            rule_id: PredefinedUnderrideRuleId::RoomOneToOne.to_string(),
421            default: true,
422            enabled: true,
423            conditions: vec![
424                RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
425                EventMatch { key: "type".into(), pattern: "m.room.message".into() },
426            ],
427            actions: vec![
428                Notify,
429                SetTweak(Tweak::Sound("default".into())),
430                SetTweak(Tweak::Highlight(false)),
431            ],
432        }
433    }
434
435    /// Matches all chat messages.
436    pub fn message() -> Self {
437        Self {
438            rule_id: PredefinedUnderrideRuleId::Message.to_string(),
439            default: true,
440            enabled: true,
441            conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.message".into() }],
442            actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
443        }
444    }
445
446    /// Matches all encrypted events.
447    ///
448    /// Unlike other push rules, this rule cannot be matched against the content of the event by
449    /// nature of it being encrypted. This causes the rule to be an "all or nothing" match where it
450    /// either matches all events that are encrypted (in group rooms) or none.
451    pub fn encrypted() -> Self {
452        Self {
453            rule_id: PredefinedUnderrideRuleId::Encrypted.to_string(),
454            default: true,
455            enabled: true,
456            conditions: vec![EventMatch { key: "type".into(), pattern: "m.room.encrypted".into() }],
457            actions: vec![Notify, SetTweak(Tweak::Highlight(false))],
458        }
459    }
460
461    /// Matches a poll start event sent in a room with exactly two members.
462    ///
463    /// This rule uses the unstable prefixes defined in [MSC3381] and [MSC3930].
464    ///
465    /// [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
466    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
467    #[cfg(feature = "unstable-msc3930")]
468    pub fn poll_start_one_to_one() -> Self {
469        Self {
470            rule_id: PredefinedUnderrideRuleId::PollStartOneToOne.to_string(),
471            default: true,
472            enabled: true,
473            conditions: vec![
474                RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
475                EventPropertyIs {
476                    key: "type".to_owned(),
477                    value: "org.matrix.msc3381.poll.start".into(),
478                },
479            ],
480            actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
481        }
482    }
483
484    /// Matches a poll start event sent in any room.
485    ///
486    /// This rule uses the unstable prefixes defined in [MSC3381] and [MSC3930].
487    ///
488    /// [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
489    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
490    #[cfg(feature = "unstable-msc3930")]
491    pub fn poll_start() -> Self {
492        Self {
493            rule_id: PredefinedUnderrideRuleId::PollStart.to_string(),
494            default: true,
495            enabled: true,
496            conditions: vec![EventPropertyIs {
497                key: "type".to_owned(),
498                value: "org.matrix.msc3381.poll.start".into(),
499            }],
500            actions: vec![Notify],
501        }
502    }
503
504    /// Matches a poll end event sent in a room with exactly two members.
505    ///
506    /// This rule uses the unstable prefixes defined in [MSC3381] and [MSC3930].
507    ///
508    /// [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
509    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
510    #[cfg(feature = "unstable-msc3930")]
511    pub fn poll_end_one_to_one() -> Self {
512        Self {
513            rule_id: PredefinedUnderrideRuleId::PollEndOneToOne.to_string(),
514            default: true,
515            enabled: true,
516            conditions: vec![
517                RoomMemberCount { is: RoomMemberCountIs::from(js_int::uint!(2)) },
518                EventPropertyIs {
519                    key: "type".to_owned(),
520                    value: "org.matrix.msc3381.poll.end".into(),
521                },
522            ],
523            actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
524        }
525    }
526
527    /// Matches a poll end event sent in any room.
528    ///
529    /// This rule uses the unstable prefixes defined in [MSC3381] and [MSC3930].
530    ///
531    /// [MSC3381]: https://github.com/matrix-org/matrix-spec-proposals/pull/3381
532    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
533    #[cfg(feature = "unstable-msc3930")]
534    pub fn poll_end() -> Self {
535        Self {
536            rule_id: PredefinedUnderrideRuleId::PollEnd.to_string(),
537            default: true,
538            enabled: true,
539            conditions: vec![EventPropertyIs {
540                key: "type".to_owned(),
541                value: "org.matrix.msc3381.poll.end".into(),
542            }],
543            actions: vec![Notify],
544        }
545    }
546
547    /// Matches an event that's part of a thread, that is *not* subscribed to, by the current user.
548    ///
549    /// Thread subscriptions are defined in [MSC4306].
550    ///
551    /// [MSC4306]: https://github.com/matrix-org/matrix-spec-proposals/pull/4306
552    #[cfg(feature = "unstable-msc4306")]
553    pub fn unsubscribed_thread() -> Self {
554        Self {
555            rule_id: PredefinedUnderrideRuleId::UnsubscribedThread.to_string(),
556            default: true,
557            enabled: true,
558            conditions: vec![ThreadSubscription { subscribed: false }],
559            actions: vec![],
560        }
561    }
562
563    /// Matches an event that's part of a thread, that *is* subscribed to, by the current user.
564    ///
565    /// Thread subscriptions are defined in [MSC4306].
566    ///
567    /// [MSC4306]: https://github.com/matrix-org/matrix-spec-proposals/pull/4306
568    #[cfg(feature = "unstable-msc4306")]
569    pub fn subscribed_thread() -> Self {
570        Self {
571            rule_id: PredefinedUnderrideRuleId::SubscribedThread.to_string(),
572            default: true,
573            enabled: true,
574            conditions: vec![ThreadSubscription { subscribed: true }],
575            actions: vec![Notify, SetTweak(Tweak::Sound("default".into()))],
576        }
577    }
578}
579
580/// The rule IDs of the predefined server push rules.
581#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
582#[non_exhaustive]
583pub enum PredefinedRuleId {
584    /// User-configured rules that override all other kinds.
585    Override(PredefinedOverrideRuleId),
586
587    /// Lowest priority user-defined rules.
588    Underride(PredefinedUnderrideRuleId),
589
590    /// Content-specific rules.
591    Content(PredefinedContentRuleId),
592}
593
594impl PredefinedRuleId {
595    /// Creates a string slice from this `PredefinedRuleId`.
596    pub fn as_str(&self) -> &str {
597        match self {
598            Self::Override(id) => id.as_str(),
599            Self::Underride(id) => id.as_str(),
600            Self::Content(id) => id.as_str(),
601        }
602    }
603
604    /// Get the kind of this `PredefinedRuleId`.
605    pub fn kind(&self) -> RuleKind {
606        match self {
607            Self::Override(id) => id.kind(),
608            Self::Underride(id) => id.kind(),
609            Self::Content(id) => id.kind(),
610        }
611    }
612}
613
614impl AsRef<str> for PredefinedRuleId {
615    fn as_ref(&self) -> &str {
616        self.as_str()
617    }
618}
619
620/// The rule IDs of the predefined override server push rules.
621#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
622#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
623#[ruma_enum(rename_all = ".m.rule.snake_case")]
624#[non_exhaustive]
625pub enum PredefinedOverrideRuleId {
626    /// `.m.rule.master`
627    Master,
628
629    /// `.m.rule.suppress_notices`
630    SuppressNotices,
631
632    /// `.m.rule.invite_for_me`
633    InviteForMe,
634
635    /// `.m.rule.member_event`
636    MemberEvent,
637
638    /// `.m.rule.is_user_mention`
639    IsUserMention,
640
641    /// `.m.rule.contains_display_name`
642    #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
643    ContainsDisplayName,
644
645    /// `.m.rule.is_room_mention`
646    IsRoomMention,
647
648    /// `.m.rule.roomnotif`
649    #[ruma_enum(rename = ".m.rule.roomnotif")]
650    #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsRoomMention instead."]
651    RoomNotif,
652
653    /// `.m.rule.tombstone`
654    Tombstone,
655
656    /// `.m.rule.reaction`
657    Reaction,
658
659    /// `.m.rule.room.server_acl`
660    #[ruma_enum(rename = ".m.rule.room.server_acl")]
661    RoomServerAcl,
662
663    /// `.m.rule.suppress_edits`
664    SuppressEdits,
665
666    /// `.m.rule.poll_response`
667    ///
668    /// This uses the unstable prefix defined in [MSC3930].
669    ///
670    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
671    #[cfg(feature = "unstable-msc3930")]
672    #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_response")]
673    PollResponse,
674
675    #[doc(hidden)]
676    _Custom(PrivOwnedStr),
677}
678
679impl PredefinedOverrideRuleId {
680    /// Get the kind of this `PredefinedOverrideRuleId`.
681    pub fn kind(&self) -> RuleKind {
682        RuleKind::Override
683    }
684}
685
686/// The rule IDs of the predefined underride server push rules.
687#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
688#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
689#[ruma_enum(rename_all = ".m.rule.snake_case")]
690#[non_exhaustive]
691pub enum PredefinedUnderrideRuleId {
692    /// `.m.rule.call`
693    Call,
694
695    /// `.m.rule.encrypted_room_one_to_one`
696    EncryptedRoomOneToOne,
697
698    /// `.m.rule.room_one_to_one`
699    RoomOneToOne,
700
701    /// `.m.rule.message`
702    Message,
703
704    /// `.m.rule.encrypted`
705    Encrypted,
706
707    /// `.m.rule.poll_start_one_to_one`
708    ///
709    /// This uses the unstable prefix defined in [MSC3930].
710    ///
711    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
712    #[cfg(feature = "unstable-msc3930")]
713    #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start_one_to_one")]
714    PollStartOneToOne,
715
716    /// `.m.rule.poll_start`
717    ///
718    /// This uses the unstable prefix defined in [MSC3930].
719    ///
720    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
721    #[cfg(feature = "unstable-msc3930")]
722    #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_start")]
723    PollStart,
724
725    /// `.m.rule.poll_end_one_to_one`
726    ///
727    /// This uses the unstable prefix defined in [MSC3930].
728    ///
729    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
730    #[cfg(feature = "unstable-msc3930")]
731    #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end_one_to_one")]
732    PollEndOneToOne,
733
734    /// `.m.rule.poll_end`
735    ///
736    /// This uses the unstable prefix defined in [MSC3930].
737    ///
738    /// [MSC3930]: https://github.com/matrix-org/matrix-spec-proposals/pull/3930
739    #[cfg(feature = "unstable-msc3930")]
740    #[ruma_enum(rename = ".org.matrix.msc3930.rule.poll_end")]
741    PollEnd,
742
743    /// `.m.rule.unsubscribed_thread`
744    ///
745    /// This uses the unstable prefix defined in [MSC4306].
746    ///
747    /// [MSC4306]: https://github.com/matrix-org/matrix-spec-proposals/pull/4306
748    #[cfg(feature = "unstable-msc4306")]
749    #[ruma_enum(rename = ".io.element.msc4306.rule.unsubscribed_thread")]
750    UnsubscribedThread,
751
752    /// `.m.rule.subscribed_thread`
753    ///
754    /// This uses the unstable prefix defined in [MSC4306].
755    ///
756    /// [MSC4306]: https://github.com/matrix-org/matrix-spec-proposals/pull/4306
757    #[cfg(feature = "unstable-msc4306")]
758    #[ruma_enum(rename = ".io.element.msc4306.rule.subscribed_thread")]
759    SubscribedThread,
760
761    #[doc(hidden)]
762    _Custom(PrivOwnedStr),
763}
764
765impl PredefinedUnderrideRuleId {
766    /// Get the kind of this `PredefinedUnderrideRuleId`.
767    pub fn kind(&self) -> RuleKind {
768        RuleKind::Underride
769    }
770}
771
772/// The rule IDs of the predefined content server push rules.
773#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
774#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, StringEnum)]
775#[ruma_enum(rename_all = ".m.rule.snake_case")]
776#[non_exhaustive]
777pub enum PredefinedContentRuleId {
778    /// `.m.rule.contains_user_name`
779    #[deprecated = "Since Matrix 1.7. Use the m.mentions property with PredefinedOverrideRuleId::IsUserMention instead."]
780    ContainsUserName,
781
782    #[doc(hidden)]
783    _Custom(PrivOwnedStr),
784}
785
786impl PredefinedContentRuleId {
787    /// Get the kind of this `PredefinedContentRuleId`.
788    pub fn kind(&self) -> RuleKind {
789        RuleKind::Content
790    }
791}
792
793#[cfg(test)]
794mod tests {
795    use assert_matches2::assert_matches;
796    use assign::assign;
797
798    use super::PredefinedOverrideRuleId;
799    use crate::{
800        push::{Action, ConditionalPushRule, ConditionalPushRuleInit, Ruleset},
801        user_id,
802    };
803
804    #[test]
805    fn update_with_server_default() {
806        let user_rule_id = "user_always_true";
807        let default_rule_id = ".default_always_true";
808
809        let override_ = [
810            // Default `.m.rule.master` push rule with non-default state.
811            assign!(ConditionalPushRule::master(), { enabled: true, actions: vec![Action::Notify]}),
812            // User-defined push rule.
813            ConditionalPushRuleInit {
814                actions: vec![],
815                default: false,
816                enabled: false,
817                rule_id: user_rule_id.to_owned(),
818                conditions: vec![],
819            }
820            .into(),
821            // Old server-default push rule.
822            ConditionalPushRuleInit {
823                actions: vec![],
824                default: true,
825                enabled: true,
826                rule_id: default_rule_id.to_owned(),
827                conditions: vec![],
828            }
829            .into(),
830        ]
831        .into_iter()
832        .collect();
833        let mut ruleset = Ruleset { override_, ..Default::default() };
834
835        let new_server_default = Ruleset::server_default(user_id!("@user:localhost"));
836
837        ruleset.update_with_server_default(new_server_default);
838
839        // Master rule is in first position.
840        let master_rule = &ruleset.override_[0];
841        assert_eq!(master_rule.rule_id, PredefinedOverrideRuleId::Master.as_str());
842
843        // `enabled` and `actions` have been copied from the old rules.
844        assert!(master_rule.enabled);
845        assert_eq!(master_rule.actions.len(), 1);
846        assert_matches!(&master_rule.actions[0], Action::Notify);
847
848        // Non-server-default rule is still present and hasn't changed.
849        let user_rule = ruleset.override_.get(user_rule_id).unwrap();
850        assert!(!user_rule.enabled);
851        assert_eq!(user_rule.actions.len(), 0);
852
853        // Old server-default rule is gone.
854        assert_matches!(ruleset.override_.get(default_rule_id), None);
855
856        // New server-default rule is present and hasn't changed.
857        let member_event_rule =
858            ruleset.override_.get(PredefinedOverrideRuleId::MemberEvent.as_str()).unwrap();
859        assert!(member_event_rule.enabled);
860        assert_eq!(member_event_rule.actions.len(), 0);
861    }
862}