Skip to main content

palpo_core/push/
conditional_push_rule.rs

1//! Common types for the [push notifications module][push].
2//!
3//! [push]: https://spec.matrix.org/latest/client-server-api/#push-notifications
4//!
5//! ## Understanding the types of this module
6//!
7//! Push rules are grouped in `RuleSet`s, and are grouped in five kinds (for
8//! more details about the different kind of rules, see the `Ruleset` documentation,
9//! or the specification). These five kinds are, by order of priority:
10//!
11//! - override rules
12//! - content rules
13//! - room rules
14//! - sender rules
15//! - underride rules
16
17use std::hash::{Hash, Hasher};
18
19use indexmap::Equivalent;
20use salvo::prelude::*;
21use serde::{Deserialize, Serialize};
22
23use crate::push::FlattenedJson;
24use crate::push::condition::RoomVersionFeature;
25use crate::push::{Action, PredefinedOverrideRuleId, PushCondition, PushConditionRoomCtx, PushRule};
26
27/// Like `SimplePushRule`, but with an additional `conditions` field.
28///
29/// Only applicable to underride and override rules.
30///
31/// To create an instance of this type, first create a `ConditionalPushRuleInit` and convert it via
32/// `ConditionalPushRule::from` / `.into()`.
33#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
34pub struct ConditionalPushRule {
35    /// Actions to determine if and how a notification is delivered for events matching this rule.
36    pub actions: Vec<Action>,
37
38    /// Whether this is a default rule, or has been set explicitly.
39    pub default: bool,
40
41    /// Whether the push rule is enabled or not.
42    pub enabled: bool,
43
44    /// The ID of this rule.
45    pub rule_id: String,
46
47    /// The conditions that must hold true for an event in order for a rule to be applied to an
48    /// event.
49    ///
50    /// A rule with no conditions always matches.
51    #[serde(default)]
52    pub conditions: Vec<PushCondition>,
53}
54
55impl ConditionalPushRule {
56    /// Check if the push rule applies to the event.
57    ///
58    /// # Arguments
59    ///
60    /// * `event` - The flattened JSON representation of a room message event.
61    /// * `context` - The context of the room at the time of the event.
62    pub fn applies(&self, event: &FlattenedJson, context: &PushConditionRoomCtx) -> bool {
63        if !self.enabled {
64            return false;
65        }
66        // These 3 rules always apply.
67        if self.rule_id != PredefinedOverrideRuleId::Master.as_ref() {
68            // Push rules which don't specify a `room_version_supports` condition are assumed
69            // to not support extensible events and are therefore expected to be treated as
70            // disabled when a room version does support extensible events.
71            let room_supports_ext_ev = context
72                .supported_features
73                .contains(&RoomVersionFeature::ExtensibleEvents);
74            let rule_has_room_version_supports = self
75                .conditions
76                .iter()
77                .any(|condition| matches!(condition, PushCondition::RoomVersionSupports { .. }));
78
79            if room_supports_ext_ev && !rule_has_room_version_supports {
80                return false;
81            }
82        }
83
84        // The old mention rules are disabled when an m.mentions field is present.
85        if event.contains_mentions() {
86            return false;
87        }
88
89        self.conditions.iter().all(|cond| cond.applies(event, context))
90    }
91}
92
93/// Initial set of fields of `ConditionalPushRule`.
94///
95/// This struct will not be updated even if additional fields are added to `ConditionalPushRule` in
96/// a new (non-breaking) release of the Matrix specification.
97#[derive(Debug)]
98#[allow(clippy::exhaustive_structs)]
99pub struct ConditionalPushRuleInit {
100    /// Actions to determine if and how a notification is delivered for events matching this rule.
101    pub actions: Vec<Action>,
102
103    /// Whether this is a default rule, or has been set explicitly.
104    pub default: bool,
105
106    /// Whether the push rule is enabled or not.
107    pub enabled: bool,
108
109    /// The ID of this rule.
110    pub rule_id: String,
111
112    /// The conditions that must hold true for an event in order for a rule to be applied to an
113    /// event.
114    ///
115    /// A rule with no conditions always matches.
116    pub conditions: Vec<PushCondition>,
117}
118
119impl From<ConditionalPushRuleInit> for ConditionalPushRule {
120    fn from(init: ConditionalPushRuleInit) -> Self {
121        let ConditionalPushRuleInit {
122            actions,
123            default,
124            enabled,
125            rule_id,
126            conditions,
127        } = init;
128        Self {
129            actions,
130            default,
131            enabled,
132            rule_id,
133            conditions,
134        }
135    }
136}
137
138// The following trait are needed to be able to make
139// an IndexSet of the type
140
141impl Hash for ConditionalPushRule {
142    fn hash<H: Hasher>(&self, state: &mut H) {
143        self.rule_id.hash(state);
144    }
145}
146
147impl PartialEq for ConditionalPushRule {
148    fn eq(&self, other: &Self) -> bool {
149        self.rule_id == other.rule_id
150    }
151}
152
153impl Eq for ConditionalPushRule {}
154
155impl Equivalent<ConditionalPushRule> for str {
156    fn equivalent(&self, key: &ConditionalPushRule) -> bool {
157        self == key.rule_id
158    }
159}
160
161/// A conditional push rule to update or create.
162#[derive(ToSchema, Deserialize, Serialize, Clone, Debug)]
163pub struct NewConditionalPushRule {
164    /// The ID of this rule.
165    pub rule_id: String,
166
167    /// The conditions that must hold true for an event in order for a rule to be applied to an
168    /// event.
169    ///
170    /// A rule with no conditions always matches.
171    #[serde(default)]
172    pub conditions: Vec<PushCondition>,
173
174    /// Actions to determine if and how a notification is delivered for events matching this
175    /// rule.
176    pub actions: Vec<Action>,
177}
178
179impl NewConditionalPushRule {
180    /// Creates a `NewConditionalPushRule` with the given ID, conditions and actions.
181    pub fn new(rule_id: String, conditions: Vec<PushCondition>, actions: Vec<Action>) -> Self {
182        Self {
183            rule_id,
184            conditions,
185            actions,
186        }
187    }
188}
189
190impl From<ConditionalPushRule> for PushRule {
191    fn from(push_rule: ConditionalPushRule) -> Self {
192        let ConditionalPushRule {
193            actions,
194            default,
195            enabled,
196            rule_id,
197            conditions,
198            ..
199        } = push_rule;
200        Self {
201            actions,
202            default,
203            enabled,
204            rule_id,
205            conditions: Some(conditions),
206            pattern: None,
207        }
208    }
209}
210
211impl From<NewConditionalPushRule> for ConditionalPushRule {
212    fn from(new_rule: NewConditionalPushRule) -> Self {
213        let NewConditionalPushRule {
214            rule_id,
215            conditions,
216            actions,
217        } = new_rule;
218        Self {
219            actions,
220            default: false,
221            enabled: true,
222            rule_id,
223            conditions,
224        }
225    }
226}
227
228impl From<ConditionalPushRuleInit> for PushRule {
229    fn from(init: ConditionalPushRuleInit) -> Self {
230        let ConditionalPushRuleInit {
231            actions,
232            default,
233            enabled,
234            rule_id,
235            conditions,
236        } = init;
237        Self {
238            actions,
239            default,
240            enabled,
241            rule_id,
242            pattern: None,
243            conditions: Some(conditions),
244        }
245    }
246}
247impl From<PushRule> for ConditionalPushRule {
248    fn from(push_rule: PushRule) -> Self {
249        let PushRule {
250            actions,
251            default,
252            enabled,
253            rule_id,
254            conditions,
255            ..
256        } = push_rule;
257
258        ConditionalPushRuleInit {
259            actions,
260            default,
261            enabled,
262            rule_id,
263            conditions: conditions.unwrap_or_default(),
264        }
265        .into()
266    }
267}