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