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