Skip to main content

ruma_common/push/
predefined.rs

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