matrix_sdk/widget/
filter.rs1use ruma::events::{MessageLikeEventType, StateEventType, TimelineEventType};
16use serde::Deserialize;
17
18#[derive(Clone, Debug)]
20#[cfg_attr(test, derive(PartialEq))]
21pub enum EventFilter {
22 MessageLike(MessageLikeEventFilter),
24 State(StateEventFilter),
26}
27
28impl EventFilter {
29 pub(super) fn matches(&self, matrix_event: &MatrixEventFilterInput) -> bool {
30 match self {
31 EventFilter::MessageLike(message_filter) => message_filter.matches(matrix_event),
32 EventFilter::State(state_filter) => state_filter.matches(matrix_event),
33 }
34 }
35
36 pub(super) fn matches_state_event_with_any_state_key(
37 &self,
38 event_type: &StateEventType,
39 ) -> bool {
40 matches!(
41 self,
42 Self::State(filter) if filter.matches_state_event_with_any_state_key(event_type)
43 )
44 }
45
46 pub(super) fn matches_message_like_event_type(
47 &self,
48 event_type: &MessageLikeEventType,
49 ) -> bool {
50 matches!(
51 self,
52 Self::MessageLike(filter) if filter.matches_message_like_event_type(event_type)
53 )
54 }
55}
56
57#[derive(Clone, Debug)]
59#[cfg_attr(test, derive(PartialEq))]
60pub enum MessageLikeEventFilter {
61 WithType(MessageLikeEventType),
63 RoomMessageWithMsgtype(String),
65}
66
67impl MessageLikeEventFilter {
68 fn matches(&self, matrix_event: &MatrixEventFilterInput) -> bool {
69 if matrix_event.state_key.is_some() {
70 return false;
72 }
73
74 match self {
75 MessageLikeEventFilter::WithType(event_type) => {
76 matrix_event.event_type == TimelineEventType::from(event_type.clone())
77 }
78 MessageLikeEventFilter::RoomMessageWithMsgtype(msgtype) => {
79 matrix_event.event_type == TimelineEventType::RoomMessage
80 && matrix_event.content.msgtype.as_ref() == Some(msgtype)
81 }
82 }
83 }
84
85 fn matches_message_like_event_type(&self, event_type: &MessageLikeEventType) -> bool {
86 match self {
87 MessageLikeEventFilter::WithType(filter_event_type) => filter_event_type == event_type,
88 MessageLikeEventFilter::RoomMessageWithMsgtype(_) => {
89 event_type == &MessageLikeEventType::RoomMessage
90 }
91 }
92 }
93}
94
95#[derive(Clone, Debug)]
97#[cfg_attr(test, derive(PartialEq))]
98pub enum StateEventFilter {
99 WithType(StateEventType),
101 WithTypeAndStateKey(StateEventType, String),
103}
104
105impl StateEventFilter {
106 fn matches(&self, matrix_event: &MatrixEventFilterInput) -> bool {
107 let Some(state_key) = &matrix_event.state_key else {
108 return false;
110 };
111
112 match self {
113 StateEventFilter::WithType(event_type) => {
114 matrix_event.event_type == TimelineEventType::from(event_type.clone())
115 }
116 StateEventFilter::WithTypeAndStateKey(event_type, filter_state_key) => {
117 matrix_event.event_type == TimelineEventType::from(event_type.clone())
118 && state_key == filter_state_key
119 }
120 }
121 }
122
123 fn matches_state_event_with_any_state_key(&self, event_type: &StateEventType) -> bool {
124 matches!(self, Self::WithType(ty) if ty == event_type)
125 }
126}
127
128#[derive(Debug, Deserialize)]
129pub(super) struct MatrixEventFilterInput {
130 #[serde(rename = "type")]
131 pub(super) event_type: TimelineEventType,
132 pub(super) state_key: Option<String>,
133 pub(super) content: MatrixEventContent,
134}
135
136#[derive(Debug, Default, Deserialize)]
137pub(super) struct MatrixEventContent {
138 pub(super) msgtype: Option<String>,
139}
140
141#[cfg(test)]
142mod tests {
143 use ruma::events::{MessageLikeEventType, StateEventType, TimelineEventType};
144
145 use super::{
146 EventFilter, MatrixEventContent, MatrixEventFilterInput, MessageLikeEventFilter,
147 StateEventFilter,
148 };
149
150 fn message_event(event_type: TimelineEventType) -> MatrixEventFilterInput {
151 MatrixEventFilterInput { event_type, state_key: None, content: Default::default() }
152 }
153
154 fn message_event_with_msgtype(
155 event_type: TimelineEventType,
156 msgtype: String,
157 ) -> MatrixEventFilterInput {
158 MatrixEventFilterInput {
159 event_type,
160 state_key: None,
161 content: MatrixEventContent { msgtype: Some(msgtype) },
162 }
163 }
164
165 fn state_event(event_type: TimelineEventType, state_key: String) -> MatrixEventFilterInput {
166 MatrixEventFilterInput {
167 event_type,
168 state_key: Some(state_key),
169 content: Default::default(),
170 }
171 }
172
173 fn room_message_text_event_filter() -> EventFilter {
175 EventFilter::MessageLike(MessageLikeEventFilter::RoomMessageWithMsgtype(
176 "m.text".to_owned(),
177 ))
178 }
179
180 #[test]
181 fn text_event_filter_matches_text_event() {
182 assert!(room_message_text_event_filter().matches(&message_event_with_msgtype(
183 TimelineEventType::RoomMessage,
184 "m.text".to_owned()
185 )));
186 }
187
188 #[test]
189 fn text_event_filter_does_not_match_image_event() {
190 assert!(!room_message_text_event_filter().matches(&message_event_with_msgtype(
191 TimelineEventType::RoomMessage,
192 "m.image".to_owned()
193 )));
194 }
195
196 #[test]
197 fn text_event_filter_does_not_match_custom_event_with_msgtype() {
198 assert!(!room_message_text_event_filter().matches(&message_event_with_msgtype(
199 "io.element.message".into(),
200 "m.text".to_owned()
201 )));
202 }
203
204 fn reaction_event_filter() -> EventFilter {
206 EventFilter::MessageLike(MessageLikeEventFilter::WithType(MessageLikeEventType::Reaction))
207 }
208
209 #[test]
210 fn reaction_event_filter_matches_reaction() {
211 assert!(reaction_event_filter().matches(&message_event(TimelineEventType::Reaction)));
212 }
213
214 #[test]
215 fn reaction_event_filter_does_not_match_room_message() {
216 assert!(!reaction_event_filter().matches(&message_event_with_msgtype(
217 TimelineEventType::RoomMessage,
218 "m.text".to_owned()
219 )));
220 }
221
222 #[test]
223 fn reaction_event_filter_does_not_match_state_event() {
224 assert!(!reaction_event_filter().matches(&state_event(
225 TimelineEventType::Reaction,
229 "".to_owned()
230 )));
231 }
232
233 #[test]
234 fn reaction_event_filter_does_not_match_state_event_any_key() {
235 assert!(
236 !reaction_event_filter().matches_state_event_with_any_state_key(&"m.reaction".into())
237 );
238 }
239
240 fn self_member_event_filter() -> EventFilter {
242 EventFilter::State(StateEventFilter::WithTypeAndStateKey(
243 StateEventType::RoomMember,
244 "@self:example.me".to_owned(),
245 ))
246 }
247
248 #[test]
249 fn self_member_event_filter_matches_self_member_event() {
250 assert!(self_member_event_filter()
251 .matches(&state_event(TimelineEventType::RoomMember, "@self:example.me".to_owned())));
252 }
253
254 #[test]
255 fn self_member_event_filter_does_not_match_somebody_elses_member_event() {
256 assert!(!self_member_event_filter().matches(&state_event(
257 TimelineEventType::RoomMember,
258 "@somebody_else.example.me".to_owned()
259 )));
260 }
261
262 #[test]
263 fn self_member_event_filter_does_not_match_unrelated_state_event_with_same_state_key() {
264 assert!(!self_member_event_filter().matches(&state_event(
265 TimelineEventType::from("io.element.test_state_event"),
266 "@self.example.me".to_owned()
267 )));
268 }
269
270 #[test]
271 fn self_member_event_filter_does_not_match_reaction_event() {
272 assert!(!self_member_event_filter().matches(&message_event(TimelineEventType::Reaction)));
273 }
274
275 #[test]
276 fn self_member_event_filter_only_matches_specific_state_key() {
277 assert!(!self_member_event_filter()
278 .matches_state_event_with_any_state_key(&StateEventType::RoomMember));
279 }
280
281 fn member_event_filter() -> EventFilter {
283 EventFilter::State(StateEventFilter::WithType(StateEventType::RoomMember))
284 }
285
286 #[test]
287 fn member_event_filter_matches_some_member_event() {
288 assert!(member_event_filter()
289 .matches(&state_event(TimelineEventType::RoomMember, "@foo.bar.baz".to_owned())));
290 }
291
292 #[test]
293 fn member_event_filter_does_not_match_room_name_event() {
294 assert!(!member_event_filter()
295 .matches(&state_event(TimelineEventType::RoomName, "".to_owned())));
296 }
297
298 #[test]
299 fn member_event_filter_does_not_match_reaction_event() {
300 assert!(!member_event_filter().matches(&message_event(TimelineEventType::Reaction)));
301 }
302
303 #[test]
304 fn member_event_filter_matches_any_state_key() {
305 assert!(member_event_filter()
306 .matches_state_event_with_any_state_key(&StateEventType::RoomMember));
307 }
308
309 fn topic_event_filter() -> EventFilter {
311 EventFilter::State(StateEventFilter::WithTypeAndStateKey(
312 StateEventType::RoomTopic,
313 "".to_owned(),
314 ))
315 }
316
317 #[test]
318 fn topic_event_filter_does_not_match_any_state_key() {
319 assert!(!topic_event_filter()
320 .matches_state_event_with_any_state_key(&StateEventType::RoomTopic));
321 }
322
323 fn room_message_custom_event_filter() -> EventFilter {
325 EventFilter::MessageLike(MessageLikeEventFilter::RoomMessageWithMsgtype(
326 "m.custom".to_owned(),
327 ))
328 }
329
330 fn room_message_filter() -> EventFilter {
332 EventFilter::MessageLike(MessageLikeEventFilter::WithType(
333 MessageLikeEventType::RoomMessage,
334 ))
335 }
336
337 #[test]
338 fn room_message_event_type_matches_room_message_text_event_filter() {
339 assert!(room_message_text_event_filter()
340 .matches_message_like_event_type(&MessageLikeEventType::RoomMessage));
341 }
342
343 #[test]
344 fn reaction_event_type_does_not_match_room_message_text_event_filter() {
345 assert!(!room_message_text_event_filter()
346 .matches_message_like_event_type(&MessageLikeEventType::Reaction));
347 }
348
349 #[test]
350 fn room_message_event_type_matches_room_message_custom_event_filter() {
351 assert!(room_message_custom_event_filter()
352 .matches_message_like_event_type(&MessageLikeEventType::RoomMessage));
353 }
354
355 #[test]
356 fn reaction_event_type_does_not_match_room_message_custom_event_filter() {
357 assert!(!room_message_custom_event_filter()
358 .matches_message_like_event_type(&MessageLikeEventType::Reaction));
359 }
360
361 #[test]
362 fn room_message_event_type_matches_room_message_event_filter() {
363 assert!(room_message_filter()
364 .matches_message_like_event_type(&MessageLikeEventType::RoomMessage));
365 }
366
367 #[test]
368 fn reaction_event_type_does_not_match_room_message_event_filter() {
369 assert!(
370 !room_message_filter().matches_message_like_event_type(&MessageLikeEventType::Reaction)
371 );
372 }
373}