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}