Skip to main content

ruma_client_api/
filter.rs

1//! Endpoints for event filters.
2
3pub mod create_filter;
4pub mod get_filter;
5
6mod lazy_load;
7mod url;
8
9use js_int::UInt;
10use ruma_common::{OwnedRoomId, OwnedUserId, serde::StringEnum};
11use serde::{Deserialize, Serialize};
12
13pub use self::{lazy_load::LazyLoadOptions, url::UrlFilter};
14use crate::PrivOwnedStr;
15
16/// Format to use for returned events.
17#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
18#[derive(Clone, Default, StringEnum)]
19#[ruma_enum(rename_all = "snake_case")]
20#[non_exhaustive]
21pub enum EventFormat {
22    /// Client format, as described in the Client API.
23    #[default]
24    Client,
25
26    /// Raw events from federation.
27    Federation,
28
29    #[doc(hidden)]
30    _Custom(PrivOwnedStr),
31}
32
33/// Filters to be applied to room events.
34#[derive(Clone, Debug, Default, Deserialize, Serialize)]
35#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
36pub struct RoomEventFilter {
37    /// A list of event types to exclude.
38    ///
39    /// If this list is absent then no event types are excluded. A matching type will be excluded
40    /// even if it is listed in the 'types' filter. A '*' can be used as a wildcard to match any
41    /// sequence of characters.
42    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
43    pub not_types: Vec<String>,
44
45    /// A list of room IDs to exclude.
46    ///
47    /// If this list is absent then no rooms are excluded. A matching room will be excluded even if
48    /// it is listed in the 'rooms' filter.
49    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
50    pub not_rooms: Vec<OwnedRoomId>,
51
52    /// The maximum number of events to return.
53    #[serde(skip_serializing_if = "Option::is_none")]
54    pub limit: Option<UInt>,
55
56    /// A list of room IDs to include.
57    ///
58    /// If this list is absent then all rooms are included.
59    #[serde(skip_serializing_if = "Option::is_none")]
60    pub rooms: Option<Vec<OwnedRoomId>>,
61
62    /// A list of sender IDs to exclude.
63    ///
64    /// If this list is absent then no senders are excluded. A matching sender will be excluded
65    /// even if it is listed in the 'senders' filter.
66    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
67    pub not_senders: Vec<OwnedUserId>,
68
69    /// A list of senders IDs to include.
70    ///
71    /// If this list is absent then all senders are included.
72    #[serde(skip_serializing_if = "Option::is_none")]
73    pub senders: Option<Vec<OwnedUserId>>,
74
75    /// A list of event types to include.
76    ///
77    /// If this list is absent then all event types are included. A '*' can be used as a wildcard
78    /// to match any sequence of characters.
79    #[serde(skip_serializing_if = "Option::is_none")]
80    pub types: Option<Vec<String>>,
81
82    /// Controls whether to include events with a URL key in their content.
83    ///
84    /// * `None`: No filtering
85    /// * `Some(EventsWithUrl)`: Only events with a URL
86    /// * `Some(EventsWithoutUrl)`: Only events without a URL
87    #[serde(rename = "contains_url", skip_serializing_if = "Option::is_none")]
88    pub url_filter: Option<UrlFilter>,
89
90    /// Options to control lazy-loading of membership events.
91    ///
92    /// Defaults to `LazyLoadOptions::Disabled`.
93    #[serde(flatten)]
94    pub lazy_load_options: LazyLoadOptions,
95
96    /// Whether to enable [per-thread notification counts].
97    ///
98    /// Only applies to the [`sync_events`] endpoint.
99    ///
100    /// [per-thread notification counts]: https://spec.matrix.org/v1.19/client-server-api/#receiving-notifications
101    /// [`sync_events`]: crate::sync::sync_events
102    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
103    pub unread_thread_notifications: bool,
104}
105
106impl RoomEventFilter {
107    /// Creates an empty `RoomEventFilter`.
108    ///
109    /// You can also use the [`Default`] implementation.
110    pub fn empty() -> Self {
111        Self::default()
112    }
113
114    /// Creates a new `RoomEventFilter` that can be used to ignore all room events.
115    pub fn ignore_all() -> Self {
116        Self { types: Some(vec![]), ..Default::default() }
117    }
118
119    /// Creates a new `RoomEventFilter` with [room member lazy-loading] enabled.
120    ///
121    /// Redundant membership events are disabled.
122    ///
123    /// [room member lazy-loading]: https://spec.matrix.org/v1.19/client-server-api/#lazy-loading-room-members
124    pub fn with_lazy_loading() -> Self {
125        Self {
126            lazy_load_options: LazyLoadOptions::Enabled { include_redundant_members: false },
127            ..Default::default()
128        }
129    }
130
131    /// Returns `true` if all fields are empty.
132    pub fn is_empty(&self) -> bool {
133        let Self {
134            not_types,
135            not_rooms,
136            limit,
137            rooms,
138            not_senders,
139            senders,
140            types,
141            url_filter,
142            lazy_load_options,
143            unread_thread_notifications,
144        } = self;
145        not_types.is_empty()
146            && not_rooms.is_empty()
147            && limit.is_none()
148            && rooms.is_none()
149            && not_senders.is_empty()
150            && senders.is_none()
151            && types.is_none()
152            && url_filter.is_none()
153            && lazy_load_options.is_disabled()
154            && !unread_thread_notifications
155    }
156}
157
158/// Filters to be applied to room data.
159#[derive(Clone, Debug, Default, Deserialize, Serialize)]
160#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
161pub struct RoomFilter {
162    /// Include rooms that the user has left in the sync.
163    ///
164    /// Defaults to `false`.
165    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
166    pub include_leave: bool,
167
168    /// The per user account data to include for rooms.
169    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
170    pub account_data: RoomEventFilter,
171
172    /// The message and state update events to include for rooms.
173    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
174    pub timeline: RoomEventFilter,
175
176    /// The events that aren't recorded in the room history, e.g. typing and receipts, to include
177    /// for rooms.
178    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
179    pub ephemeral: RoomEventFilter,
180
181    /// The state events to include for rooms.
182    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
183    pub state: RoomEventFilter,
184
185    /// A list of room IDs to exclude.
186    ///
187    /// If this list is absent then no rooms are excluded. A matching room will be excluded even if
188    /// it is listed in the 'rooms' filter. This filter is applied before the filters in
189    /// `ephemeral`, `state`, `timeline` or `account_data`.
190    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
191    pub not_rooms: Vec<OwnedRoomId>,
192
193    /// A list of room IDs to include.
194    ///
195    /// If this list is absent then all rooms are included. This filter is applied before the
196    /// filters in `ephemeral`, `state`, `timeline` or `account_data`.
197    #[serde(skip_serializing_if = "Option::is_none")]
198    pub rooms: Option<Vec<OwnedRoomId>>,
199}
200
201impl RoomFilter {
202    /// Creates an empty `RoomFilter`.
203    ///
204    /// You can also use the [`Default`] implementation.
205    pub fn empty() -> Self {
206        Self::default()
207    }
208
209    /// Creates a new `RoomFilter` that can be used to ignore all room events (of any type).
210    pub fn ignore_all() -> Self {
211        Self { rooms: Some(vec![]), ..Default::default() }
212    }
213
214    /// Creates a new `RoomFilter` with [room member lazy-loading] enabled.
215    ///
216    /// Redundant membership events are disabled.
217    ///
218    /// [room member lazy-loading]: https://spec.matrix.org/v1.19/client-server-api/#lazy-loading-room-members
219    pub fn with_lazy_loading() -> Self {
220        Self { state: RoomEventFilter::with_lazy_loading(), ..Default::default() }
221    }
222
223    /// Returns `true` if all fields are empty.
224    pub fn is_empty(&self) -> bool {
225        let Self { include_leave, account_data, timeline, ephemeral, state, not_rooms, rooms } =
226            self;
227        !include_leave
228            && account_data.is_empty()
229            && timeline.is_empty()
230            && ephemeral.is_empty()
231            && state.is_empty()
232            && not_rooms.is_empty()
233            && rooms.is_none()
234    }
235}
236
237/// Filter for non-room data.
238#[derive(Clone, Debug, Default, Deserialize, Serialize)]
239#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
240pub struct Filter {
241    /// A list of event types to exclude.
242    ///
243    /// If this list is absent then no event types are excluded. A matching type will be excluded
244    /// even if it is listed in the 'types' filter. A '*' can be used as a wildcard to match any
245    /// sequence of characters.
246    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
247    pub not_types: Vec<String>,
248
249    /// The maximum number of events to return.
250    #[serde(skip_serializing_if = "Option::is_none")]
251    pub limit: Option<UInt>,
252
253    /// A list of senders IDs to include.
254    ///
255    /// If this list is absent then all senders are included.
256    #[serde(skip_serializing_if = "Option::is_none")]
257    pub senders: Option<Vec<OwnedUserId>>,
258
259    /// A list of event types to include.
260    ///
261    /// If this list is absent then all event types are included. A '*' can be used as a wildcard
262    /// to match any sequence of characters.
263    #[serde(skip_serializing_if = "Option::is_none")]
264    pub types: Option<Vec<String>>,
265
266    /// A list of sender IDs to exclude.
267    ///
268    /// If this list is absent then no senders are excluded. A matching sender will be excluded
269    /// even if it is listed in the 'senders' filter.
270    #[serde(default, skip_serializing_if = "<[_]>::is_empty")]
271    pub not_senders: Vec<OwnedUserId>,
272}
273
274impl Filter {
275    /// Creates an empty `Filter`.
276    ///
277    /// You can also use the [`Default`] implementation.
278    pub fn empty() -> Self {
279        Self::default()
280    }
281
282    /// Creates a new `Filter` that can be used to ignore all events.
283    pub fn ignore_all() -> Self {
284        Self { types: Some(vec![]), ..Default::default() }
285    }
286
287    /// Returns `true` if all fields are empty.
288    pub fn is_empty(&self) -> bool {
289        let Self { not_types, limit, senders, types, not_senders } = self;
290        not_types.is_empty()
291            && limit.is_none()
292            && senders.is_none()
293            && types.is_none()
294            && not_senders.is_empty()
295    }
296}
297
298/// A filter definition
299#[derive(Clone, Debug, Default, Deserialize, Serialize)]
300#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
301pub struct FilterDefinition {
302    /// List of event fields to include.
303    ///
304    /// If this list is absent then all fields are included. The entries may include '.' characters
305    /// to indicate sub-fields. So ['content.body'] will include the 'body' field of the 'content'
306    /// object. A literal '.' or '\' character in a field name may be escaped using a '\'. A server
307    /// may include more fields than were requested.
308    #[serde(skip_serializing_if = "Option::is_none")]
309    pub event_fields: Option<Vec<String>>,
310
311    /// The format to use for events.
312    ///
313    /// 'client' will return the events in a format suitable for clients. 'federation' will return
314    /// the raw event as received over federation. The default is 'client'.
315    #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
316    pub event_format: EventFormat,
317
318    /// The presence updates to include.
319    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
320    pub presence: Filter,
321
322    /// The user account data that isn't associated with rooms to include.
323    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
324    pub account_data: Filter,
325
326    /// Filters to be applied to room data.
327    #[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
328    pub room: RoomFilter,
329}
330
331impl FilterDefinition {
332    /// Creates an empty `FilterDefinition`.
333    ///
334    /// You can also use the [`Default`] implementation.
335    pub fn empty() -> Self {
336        Self::default()
337    }
338
339    /// Creates a new `FilterDefinition` that can be used to ignore all events.
340    pub fn ignore_all() -> Self {
341        Self {
342            account_data: Filter::ignore_all(),
343            room: RoomFilter::ignore_all(),
344            presence: Filter::ignore_all(),
345            ..Default::default()
346        }
347    }
348
349    /// Creates a new `FilterDefinition` with [room member lazy-loading] enabled.
350    ///
351    /// Redundant membership events are disabled.
352    ///
353    /// [room member lazy-loading]: https://spec.matrix.org/v1.19/client-server-api/#lazy-loading-room-members
354    pub fn with_lazy_loading() -> Self {
355        Self { room: RoomFilter::with_lazy_loading(), ..Default::default() }
356    }
357
358    /// Returns `true` if all fields are empty.
359    pub fn is_empty(&self) -> bool {
360        let Self { event_fields, event_format, presence, account_data, room } = self;
361        event_fields.is_none()
362            && *event_format == EventFormat::Client
363            && presence.is_empty()
364            && account_data.is_empty()
365            && room.is_empty()
366    }
367}
368
369macro_rules! can_be_empty {
370    ($ty:ident) => {
371        impl ruma_common::serde::CanBeEmpty for $ty {
372            fn is_empty(&self) -> bool {
373                self.is_empty()
374            }
375        }
376    };
377}
378
379can_be_empty!(Filter);
380can_be_empty!(FilterDefinition);
381can_be_empty!(RoomEventFilter);
382can_be_empty!(RoomFilter);
383
384#[cfg(test)]
385mod tests {
386    use ruma_common::canonical_json::assert_to_canonical_json_eq;
387    use serde_json::{
388        from_str as from_json_str, from_value as from_json_value, json, to_string as to_json_string,
389    };
390
391    use super::{
392        Filter, FilterDefinition, LazyLoadOptions, RoomEventFilter, RoomFilter, UrlFilter,
393    };
394
395    #[test]
396    fn default_filters_are_empty() {
397        assert_to_canonical_json_eq!(Filter::default(), json!({}));
398        assert_to_canonical_json_eq!(FilterDefinition::default(), json!({}));
399        assert_to_canonical_json_eq!(RoomEventFilter::default(), json!({}));
400        assert_to_canonical_json_eq!(RoomFilter::default(), json!({}));
401    }
402
403    #[test]
404    fn filter_definition_roundtrip() {
405        let filter = FilterDefinition::default();
406        assert_to_canonical_json_eq!(filter, json!({}));
407
408        let filter_str = to_json_string(&filter).unwrap();
409        let incoming_filter = from_json_str::<FilterDefinition>(&filter_str).unwrap();
410        assert!(incoming_filter.is_empty());
411    }
412
413    #[test]
414    fn room_filter_definition_roundtrip() {
415        let filter = RoomFilter::default();
416        assert_to_canonical_json_eq!(filter, json!({}));
417
418        let filter_str = to_json_string(&filter).unwrap();
419        let incoming_room_filter = from_json_str::<RoomFilter>(&filter_str).unwrap();
420        assert!(incoming_room_filter.is_empty());
421    }
422
423    #[test]
424    fn issue_366() {
425        let obj = json!({
426            "lazy_load_members": true,
427            "filter_json": { "contains_url": true, "types": ["m.room.message"] },
428            "types": ["m.room.message"],
429            "not_types": [],
430            "rooms": null,
431            "not_rooms": [],
432            "senders": null,
433            "not_senders": [],
434            "contains_url": true,
435        });
436
437        let filter: RoomEventFilter = from_json_value(obj).unwrap();
438
439        assert_eq!(filter.types, Some(vec!["m.room.message".to_owned()]));
440        assert_eq!(filter.not_types, vec![""; 0]);
441        assert_eq!(filter.rooms, None);
442        assert_eq!(filter.not_rooms, vec![""; 0]);
443        assert_eq!(filter.senders, None);
444        assert_eq!(filter.not_senders, vec![""; 0]);
445        assert_eq!(filter.limit, None);
446        assert_eq!(filter.url_filter, Some(UrlFilter::EventsWithUrl));
447        assert_eq!(
448            filter.lazy_load_options,
449            LazyLoadOptions::Enabled { include_redundant_members: false }
450        );
451    }
452}