pub mod create_filter;
pub mod get_filter;
mod lazy_load;
mod url;
use js_int::UInt;
#[cfg(feature = "unstable-msc3440")]
use ruma_common::events::relation::RelationType;
use ruma_common::{
serde::{Incoming, StringEnum},
OwnedRoomId, OwnedUserId,
};
use serde::Serialize;
use crate::PrivOwnedStr;
pub use self::{lazy_load::LazyLoadOptions, url::UrlFilter};
#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))]
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
#[ruma_enum(rename_all = "snake_case")]
#[non_exhaustive]
pub enum EventFormat {
Client,
Federation,
#[doc(hidden)]
_Custom(PrivOwnedStr),
}
impl Default for EventFormat {
fn default() -> Self {
Self::Client
}
}
#[derive(Clone, Debug, Default, Incoming, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[incoming_derive(Clone, Default, Serialize)]
pub struct RoomEventFilter<'a> {
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_types: &'a [String],
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_rooms: &'a [OwnedRoomId],
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<UInt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rooms: Option<&'a [OwnedRoomId]>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_senders: &'a [OwnedUserId],
#[serde(skip_serializing_if = "Option::is_none")]
pub senders: Option<&'a [OwnedUserId]>,
#[serde(skip_serializing_if = "Option::is_none")]
pub types: Option<&'a [String]>,
#[serde(rename = "contains_url", skip_serializing_if = "Option::is_none")]
pub url_filter: Option<UrlFilter>,
#[serde(flatten)]
pub lazy_load_options: LazyLoadOptions,
#[cfg(feature = "unstable-msc3440")]
#[serde(
rename = "io.element.relation_types",
alias = "related_by_rel_types",
default,
skip_serializing_if = "<[_]>::is_empty"
)]
pub related_by_rel_types: &'a [RelationType],
#[cfg(feature = "unstable-msc3440")]
#[serde(
rename = "io.element.relation_senders",
alias = "related_by_senders",
default,
skip_serializing_if = "<[_]>::is_empty"
)]
pub related_by_senders: &'a [OwnedUserId],
}
impl<'a> RoomEventFilter<'a> {
pub fn empty() -> Self {
Self::default()
}
pub fn ignore_all() -> Self {
Self { types: Some(&[]), ..Default::default() }
}
pub fn is_empty(&self) -> bool {
let empty = self.not_types.is_empty()
&& self.not_rooms.is_empty()
&& self.limit.is_none()
&& self.rooms.is_none()
&& self.not_senders.is_empty()
&& self.senders.is_none()
&& self.types.is_none()
&& self.url_filter.is_none()
&& self.lazy_load_options.is_disabled();
#[cfg(not(feature = "unstable-msc3440"))]
return empty;
#[cfg(feature = "unstable-msc3440")]
return empty && self.related_by_rel_types.is_empty() && self.related_by_senders.is_empty();
}
}
impl IncomingRoomEventFilter {
pub fn is_empty(&self) -> bool {
let empty = self.not_types.is_empty()
&& self.not_rooms.is_empty()
&& self.limit.is_none()
&& self.rooms.is_none()
&& self.not_senders.is_empty()
&& self.senders.is_none()
&& self.types.is_none()
&& self.url_filter.is_none()
&& self.lazy_load_options.is_disabled();
#[cfg(not(feature = "unstable-msc3440"))]
return empty;
#[cfg(feature = "unstable-msc3440")]
return empty && self.related_by_rel_types.is_empty() && self.related_by_senders.is_empty();
}
}
#[derive(Clone, Debug, Default, Incoming, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[incoming_derive(Clone, Default, Serialize)]
pub struct RoomFilter<'a> {
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub include_leave: bool,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub account_data: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub timeline: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub ephemeral: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub state: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_rooms: &'a [OwnedRoomId],
#[serde(skip_serializing_if = "Option::is_none")]
pub rooms: Option<&'a [OwnedRoomId]>,
}
impl<'a> RoomFilter<'a> {
pub fn empty() -> Self {
Self::default()
}
pub fn ignore_all() -> Self {
Self { rooms: Some(&[]), ..Default::default() }
}
pub fn is_empty(&self) -> bool {
!self.include_leave
&& self.account_data.is_empty()
&& self.timeline.is_empty()
&& self.ephemeral.is_empty()
&& self.state.is_empty()
&& self.not_rooms.is_empty()
&& self.rooms.is_none()
}
}
impl IncomingRoomFilter {
pub fn is_empty(&self) -> bool {
!self.include_leave
&& self.account_data.is_empty()
&& self.timeline.is_empty()
&& self.ephemeral.is_empty()
&& self.state.is_empty()
&& self.not_rooms.is_empty()
&& self.rooms.is_none()
}
}
#[derive(Clone, Debug, Default, Incoming, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[incoming_derive(Clone, Default, Serialize)]
pub struct Filter<'a> {
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_types: &'a [String],
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<UInt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub senders: Option<&'a [OwnedUserId]>,
#[serde(skip_serializing_if = "Option::is_none")]
pub types: Option<&'a [String]>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_senders: &'a [OwnedUserId],
}
impl<'a> Filter<'a> {
pub fn empty() -> Self {
Self::default()
}
pub fn ignore_all() -> Self {
Self { types: Some(&[]), ..Default::default() }
}
pub fn is_empty(&self) -> bool {
self.not_types.is_empty()
&& self.limit.is_none()
&& self.senders.is_none()
&& self.types.is_none()
&& self.not_senders.is_empty()
}
}
impl IncomingFilter {
pub fn is_empty(&self) -> bool {
self.not_types.is_empty()
&& self.limit.is_none()
&& self.senders.is_none()
&& self.types.is_none()
&& self.not_senders.is_empty()
}
}
#[derive(Clone, Debug, Default, Incoming, Serialize)]
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
#[incoming_derive(Clone, Default, Serialize)]
pub struct FilterDefinition<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
pub event_fields: Option<&'a [String]>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_default")]
pub event_format: EventFormat,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub presence: Filter<'a>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub account_data: Filter<'a>,
#[serde(default, skip_serializing_if = "ruma_common::serde::is_empty")]
pub room: RoomFilter<'a>,
}
impl<'a> FilterDefinition<'a> {
pub fn empty() -> Self {
Self::default()
}
pub fn ignore_all() -> Self {
Self {
account_data: Filter::ignore_all(),
room: RoomFilter::ignore_all(),
presence: Filter::ignore_all(),
..Default::default()
}
}
pub fn is_empty(&self) -> bool {
self.event_fields.is_none()
&& self.event_format == EventFormat::Client
&& self.presence.is_empty()
&& self.account_data.is_empty()
&& self.room.is_empty()
}
}
impl IncomingFilterDefinition {
pub fn is_empty(&self) -> bool {
self.event_fields.is_none()
&& self.event_format == EventFormat::Client
&& self.presence.is_empty()
&& self.account_data.is_empty()
&& self.room.is_empty()
}
}
macro_rules! can_be_empty {
($ty:ident $(<$gen:tt>)?) => {
impl $(<$gen>)? ruma_common::serde::CanBeEmpty for $ty $(<$gen>)? {
fn is_empty(&self) -> bool {
self.is_empty()
}
}
};
}
can_be_empty!(Filter<'a>);
can_be_empty!(FilterDefinition<'a>);
can_be_empty!(RoomEventFilter<'a>);
can_be_empty!(RoomFilter<'a>);
can_be_empty!(IncomingFilter);
can_be_empty!(IncomingFilterDefinition);
can_be_empty!(IncomingRoomEventFilter);
can_be_empty!(IncomingRoomFilter);
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
use super::{
Filter, FilterDefinition, IncomingFilterDefinition, IncomingRoomEventFilter,
IncomingRoomFilter, LazyLoadOptions, RoomEventFilter, RoomFilter, UrlFilter,
};
#[test]
fn default_filters_are_empty() -> serde_json::Result<()> {
assert_eq!(to_json_value(Filter::default())?, json!({}));
assert_eq!(to_json_value(FilterDefinition::default())?, json!({}));
assert_eq!(to_json_value(RoomEventFilter::default())?, json!({}));
assert_eq!(to_json_value(RoomFilter::default())?, json!({}));
Ok(())
}
#[test]
fn filter_definition_roundtrip() -> serde_json::Result<()> {
let filter = FilterDefinition::default();
let filter_str = to_json_value(&filter)?;
let incoming_filter = from_json_value::<IncomingFilterDefinition>(filter_str)?;
assert!(incoming_filter.is_empty());
Ok(())
}
#[test]
fn room_filter_definition_roundtrip() -> serde_json::Result<()> {
let filter = RoomFilter::default();
let room_filter = to_json_value(&filter)?;
let incoming_room_filter = from_json_value::<IncomingRoomFilter>(room_filter)?;
assert!(incoming_room_filter.is_empty());
Ok(())
}
#[test]
fn issue_366() {
let obj = json!({
"lazy_load_members": true,
"filter_json": { "contains_url": true, "types": ["m.room.message"] },
"types": ["m.room.message"],
"not_types": [],
"rooms": null,
"not_rooms": [],
"senders": null,
"not_senders": [],
"contains_url": true,
});
let filter: IncomingRoomEventFilter = assert_matches!(from_json_value(obj), Ok(f) => f);
assert_eq!(filter.types, Some(vec!["m.room.message".to_owned()]));
assert_eq!(filter.not_types, vec![""; 0]);
assert_eq!(filter.rooms, None);
assert_eq!(filter.not_rooms, vec![""; 0]);
assert_eq!(filter.senders, None);
assert_eq!(filter.not_senders, vec![""; 0]);
assert_eq!(filter.limit, None);
assert_eq!(filter.url_filter, Some(UrlFilter::EventsWithUrl));
assert_eq!(
filter.lazy_load_options,
LazyLoadOptions::Enabled { include_redundant_members: false }
);
#[cfg(feature = "unstable-msc3440")]
assert_eq!(filter.related_by_rel_types, vec![]);
#[cfg(feature = "unstable-msc3440")]
assert_eq!(filter.related_by_senders, vec![""; 0]);
}
}