ruma_common/push/
iter.rs

1use indexmap::set::{IntoIter as IndexSetIntoIter, Iter as IndexSetIter};
2
3use super::{
4    condition, Action, ConditionalPushRule, FlattenedJson, PatternedPushRule, PushConditionRoomCtx,
5    Ruleset, SimplePushRule,
6};
7use crate::{OwnedRoomId, OwnedUserId};
8
9/// The kinds of push rules that are available.
10#[derive(Clone, Debug)]
11#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
12pub enum AnyPushRule {
13    /// Rules that override all other kinds.
14    Override(ConditionalPushRule),
15
16    /// Content-specific rules.
17    Content(PatternedPushRule),
18
19    /// Post-content specific rules.
20    #[cfg(feature = "unstable-msc4306")]
21    PostContent(ConditionalPushRule),
22
23    /// Room-specific rules.
24    Room(SimplePushRule<OwnedRoomId>),
25
26    /// Sender-specific rules.
27    Sender(SimplePushRule<OwnedUserId>),
28
29    /// Lowest priority rules.
30    Underride(ConditionalPushRule),
31}
32
33impl AnyPushRule {
34    /// Convert `AnyPushRule` to `AnyPushRuleRef`.
35    pub fn as_ref(&self) -> AnyPushRuleRef<'_> {
36        match self {
37            Self::Override(o) => AnyPushRuleRef::Override(o),
38            Self::Content(c) => AnyPushRuleRef::Content(c),
39            #[cfg(feature = "unstable-msc4306")]
40            Self::PostContent(c) => AnyPushRuleRef::PostContent(c),
41            Self::Room(r) => AnyPushRuleRef::Room(r),
42            Self::Sender(s) => AnyPushRuleRef::Sender(s),
43            Self::Underride(u) => AnyPushRuleRef::Underride(u),
44        }
45    }
46
47    /// Get the `enabled` flag of the push rule.
48    pub fn enabled(&self) -> bool {
49        self.as_ref().enabled()
50    }
51
52    /// Get the `actions` of the push rule.
53    pub fn actions(&self) -> &[Action] {
54        self.as_ref().actions()
55    }
56
57    /// Whether an event that matches the push rule should be highlighted.
58    pub fn triggers_highlight(&self) -> bool {
59        self.as_ref().triggers_highlight()
60    }
61
62    /// Whether an event that matches the push rule should trigger a notification (either in-app or
63    /// remote / push).
64    pub fn triggers_notification(&self) -> bool {
65        self.as_ref().triggers_notification()
66    }
67
68    /// Whether an event that matches the push rule should trigger a remote notification.
69    #[cfg(feature = "unstable-msc3768")]
70    pub fn triggers_remote_notification(&self) -> bool {
71        self.as_ref().triggers_remote_notification()
72    }
73
74    /// The sound that should be played when an event matches the push rule, if any.
75    pub fn triggers_sound(&self) -> Option<&str> {
76        self.as_ref().triggers_sound()
77    }
78
79    /// Get the `rule_id` of the push rule.
80    pub fn rule_id(&self) -> &str {
81        self.as_ref().rule_id()
82    }
83
84    /// Whether the push rule is a server-default rule.
85    pub fn is_server_default(&self) -> bool {
86        self.as_ref().is_server_default()
87    }
88
89    /// Check if the push rule applies to the event.
90    ///
91    /// # Arguments
92    ///
93    /// * `event` - The flattened JSON representation of a room message event.
94    /// * `context` - The context of the room at the time of the event.
95    pub async fn applies(&self, event: &FlattenedJson, context: &PushConditionRoomCtx) -> bool {
96        self.as_ref().applies(event, context).await
97    }
98}
99
100/// Iterator type for `Ruleset`
101#[derive(Debug)]
102pub struct RulesetIntoIter {
103    content: IndexSetIntoIter<PatternedPushRule>,
104    #[cfg(feature = "unstable-msc4306")]
105    postcontent: IndexSetIntoIter<ConditionalPushRule>,
106    override_: IndexSetIntoIter<ConditionalPushRule>,
107    room: IndexSetIntoIter<SimplePushRule<OwnedRoomId>>,
108    sender: IndexSetIntoIter<SimplePushRule<OwnedUserId>>,
109    underride: IndexSetIntoIter<ConditionalPushRule>,
110}
111
112impl Iterator for RulesetIntoIter {
113    type Item = AnyPushRule;
114
115    fn next(&mut self) -> Option<Self::Item> {
116        let it = self
117            .override_
118            .next()
119            .map(AnyPushRule::Override)
120            .or_else(|| self.content.next().map(AnyPushRule::Content));
121
122        #[cfg(feature = "unstable-msc4306")]
123        let it = it.or_else(|| self.postcontent.next().map(AnyPushRule::PostContent));
124
125        it.or_else(|| self.room.next().map(AnyPushRule::Room))
126            .or_else(|| self.sender.next().map(AnyPushRule::Sender))
127            .or_else(|| self.underride.next().map(AnyPushRule::Underride))
128    }
129}
130
131impl IntoIterator for Ruleset {
132    type Item = AnyPushRule;
133    type IntoIter = RulesetIntoIter;
134
135    fn into_iter(self) -> Self::IntoIter {
136        RulesetIntoIter {
137            content: self.content.into_iter(),
138            #[cfg(feature = "unstable-msc4306")]
139            postcontent: self.postcontent.into_iter(),
140            override_: self.override_.into_iter(),
141            room: self.room.into_iter(),
142            sender: self.sender.into_iter(),
143            underride: self.underride.into_iter(),
144        }
145    }
146}
147
148/// Reference to any kind of push rule.
149#[derive(Clone, Copy, Debug)]
150#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
151pub enum AnyPushRuleRef<'a> {
152    /// Rules that override all other kinds.
153    Override(&'a ConditionalPushRule),
154
155    /// Content-specific rules.
156    Content(&'a PatternedPushRule),
157
158    /// Post-content specific rules.
159    #[cfg(feature = "unstable-msc4306")]
160    PostContent(&'a ConditionalPushRule),
161
162    /// Room-specific rules.
163    Room(&'a SimplePushRule<OwnedRoomId>),
164
165    /// Sender-specific rules.
166    Sender(&'a SimplePushRule<OwnedUserId>),
167
168    /// Lowest priority rules.
169    Underride(&'a ConditionalPushRule),
170}
171
172impl<'a> AnyPushRuleRef<'a> {
173    /// Convert `AnyPushRuleRef` to `AnyPushRule` by cloning the inner value.
174    pub fn to_owned(self) -> AnyPushRule {
175        match self {
176            Self::Override(o) => AnyPushRule::Override(o.clone()),
177            Self::Content(c) => AnyPushRule::Content(c.clone()),
178            #[cfg(feature = "unstable-msc4306")]
179            Self::PostContent(c) => AnyPushRule::PostContent(c.clone()),
180            Self::Room(r) => AnyPushRule::Room(r.clone()),
181            Self::Sender(s) => AnyPushRule::Sender(s.clone()),
182            Self::Underride(u) => AnyPushRule::Underride(u.clone()),
183        }
184    }
185
186    /// Get the `enabled` flag of the push rule.
187    pub fn enabled(self) -> bool {
188        match self {
189            Self::Override(rule) => rule.enabled,
190            Self::Underride(rule) => rule.enabled,
191            Self::Content(rule) => rule.enabled,
192            #[cfg(feature = "unstable-msc4306")]
193            Self::PostContent(rule) => rule.enabled,
194            Self::Room(rule) => rule.enabled,
195            Self::Sender(rule) => rule.enabled,
196        }
197    }
198
199    /// Get the `actions` of the push rule.
200    pub fn actions(self) -> &'a [Action] {
201        match self {
202            Self::Override(rule) => &rule.actions,
203            Self::Underride(rule) => &rule.actions,
204            Self::Content(rule) => &rule.actions,
205            #[cfg(feature = "unstable-msc4306")]
206            Self::PostContent(rule) => &rule.actions,
207            Self::Room(rule) => &rule.actions,
208            Self::Sender(rule) => &rule.actions,
209        }
210    }
211
212    /// Whether an event that matches the push rule should be highlighted.
213    pub fn triggers_highlight(self) -> bool {
214        self.actions().iter().any(|a| a.is_highlight())
215    }
216
217    /// Whether an event that matches the push rule should trigger a notification (either in-app or
218    /// remote / push).
219    pub fn triggers_notification(self) -> bool {
220        self.actions().iter().any(|a| a.should_notify())
221    }
222
223    /// Whether an event that matches the push rule should trigger a remote notification.
224    #[cfg(feature = "unstable-msc3768")]
225    pub fn triggers_remote_notification(self) -> bool {
226        self.actions().iter().any(|a| a.should_notify_remote())
227    }
228
229    /// The sound that should be played when an event matches the push rule, if any.
230    pub fn triggers_sound(self) -> Option<&'a str> {
231        self.actions().iter().find_map(|a| a.sound())
232    }
233
234    /// Get the `rule_id` of the push rule.
235    pub fn rule_id(self) -> &'a str {
236        match self {
237            Self::Override(rule) => &rule.rule_id,
238            Self::Underride(rule) => &rule.rule_id,
239            Self::Content(rule) => &rule.rule_id,
240            #[cfg(feature = "unstable-msc4306")]
241            Self::PostContent(rule) => &rule.rule_id,
242            Self::Room(rule) => rule.rule_id.as_ref(),
243            Self::Sender(rule) => rule.rule_id.as_ref(),
244        }
245    }
246
247    /// Whether the push rule is a server-default rule.
248    pub fn is_server_default(self) -> bool {
249        match self {
250            Self::Override(rule) => rule.default,
251            Self::Underride(rule) => rule.default,
252            Self::Content(rule) => rule.default,
253            #[cfg(feature = "unstable-msc4306")]
254            Self::PostContent(rule) => rule.default,
255            Self::Room(rule) => rule.default,
256            Self::Sender(rule) => rule.default,
257        }
258    }
259
260    /// Check if the push rule applies to the event.
261    ///
262    /// # Arguments
263    ///
264    /// * `event` - The flattened JSON representation of a room message event.
265    /// * `context` - The context of the room at the time of the event.
266    pub async fn applies(self, event: &FlattenedJson, context: &PushConditionRoomCtx) -> bool {
267        if event.get_str("sender").is_some_and(|sender| sender == context.user_id) {
268            return false;
269        }
270
271        match self {
272            Self::Override(rule) => rule.applies(event, context).await,
273            Self::Underride(rule) => rule.applies(event, context).await,
274            Self::Content(rule) => rule.applies_to("content.body", event, context),
275            #[cfg(feature = "unstable-msc4306")]
276            Self::PostContent(rule) => rule.applies(event, context).await,
277            Self::Room(rule) => {
278                rule.enabled
279                    && condition::check_event_match(
280                        event,
281                        "room_id",
282                        rule.rule_id.as_ref(),
283                        context,
284                    )
285            }
286            Self::Sender(rule) => {
287                rule.enabled
288                    && condition::check_event_match(event, "sender", rule.rule_id.as_ref(), context)
289            }
290        }
291    }
292}
293
294/// Iterator type for `Ruleset`
295#[derive(Debug)]
296pub struct RulesetIter<'a> {
297    content: IndexSetIter<'a, PatternedPushRule>,
298    #[cfg(feature = "unstable-msc4306")]
299    postcontent: IndexSetIter<'a, ConditionalPushRule>,
300    override_: IndexSetIter<'a, ConditionalPushRule>,
301    room: IndexSetIter<'a, SimplePushRule<OwnedRoomId>>,
302    sender: IndexSetIter<'a, SimplePushRule<OwnedUserId>>,
303    underride: IndexSetIter<'a, ConditionalPushRule>,
304}
305
306impl<'a> Iterator for RulesetIter<'a> {
307    type Item = AnyPushRuleRef<'a>;
308
309    fn next(&mut self) -> Option<Self::Item> {
310        let it = self
311            .override_
312            .next()
313            .map(AnyPushRuleRef::Override)
314            .or_else(|| self.content.next().map(AnyPushRuleRef::Content));
315
316        #[cfg(feature = "unstable-msc4306")]
317        let it = it.or_else(|| self.postcontent.next().map(AnyPushRuleRef::PostContent));
318
319        it.or_else(|| self.room.next().map(AnyPushRuleRef::Room))
320            .or_else(|| self.sender.next().map(AnyPushRuleRef::Sender))
321            .or_else(|| self.underride.next().map(AnyPushRuleRef::Underride))
322    }
323}
324
325impl<'a> IntoIterator for &'a Ruleset {
326    type Item = AnyPushRuleRef<'a>;
327    type IntoIter = RulesetIter<'a>;
328
329    fn into_iter(self) -> Self::IntoIter {
330        RulesetIter {
331            content: self.content.iter(),
332            #[cfg(feature = "unstable-msc4306")]
333            postcontent: self.postcontent.iter(),
334            override_: self.override_.iter(),
335            room: self.room.iter(),
336            sender: self.sender.iter(),
337            underride: self.underride.iter(),
338        }
339    }
340}