pub mod create_filter;
pub mod get_filter;
mod lazy_load;
mod url;
pub use lazy_load::LazyLoadOptions;
pub use url::UrlFilter;
use js_int::UInt;
use ruma_identifiers::{RoomId, UserId};
use ruma_serde::{Outgoing, StringEnum};
use serde::Serialize;
#[derive(Clone, Debug, PartialEq, Eq, StringEnum)]
#[ruma_enum(rename_all = "snake_case")]
pub enum EventFormat {
Client,
Federation,
#[doc(hidden)]
_Custom(String),
}
impl Default for EventFormat {
fn default() -> Self {
Self::Client
}
}
#[derive(Clone, Debug, Default, Outgoing, 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 [String],
#[serde(skip_serializing_if = "Option::is_none")]
pub limit: Option<UInt>,
#[serde(skip_serializing_if = "Option::is_none")]
pub rooms: Option<&'a [RoomId]>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_senders: &'a [UserId],
#[serde(skip_serializing_if = "Option::is_none")]
pub senders: Option<&'a [UserId]>,
#[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,
}
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 {
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()
}
}
impl IncomingRoomEventFilter {
pub fn is_empty(&self) -> bool {
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()
}
}
#[derive(Clone, Debug, Default, Outgoing, 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_serde::is_default")]
pub include_leave: bool,
#[serde(default, skip_serializing_if = "ruma_serde::is_empty")]
pub account_data: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "ruma_serde::is_empty")]
pub timeline: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "ruma_serde::is_empty")]
pub ephemeral: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "ruma_serde::is_empty")]
pub state: RoomEventFilter<'a>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_rooms: &'a [RoomId],
#[serde(skip_serializing_if = "Option::is_none")]
pub rooms: Option<&'a [RoomId]>,
}
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, Outgoing, 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 [UserId]>,
#[serde(skip_serializing_if = "Option::is_none")]
pub types: Option<&'a [String]>,
#[serde(default, skip_serializing_if = "<[_]>::is_empty")]
pub not_senders: &'a [UserId],
}
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, Outgoing, 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_serde::is_default")]
pub event_format: EventFormat,
#[serde(default, skip_serializing_if = "ruma_serde::is_empty")]
pub presence: Filter<'a>,
#[serde(default, skip_serializing_if = "ruma_serde::is_empty")]
pub account_data: Filter<'a>,
#[serde(default, skip_serializing_if = "ruma_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_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 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() -> Result<(), serde_json::Error> {
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() -> Result<(), serde_json::Error> {
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() -> Result<(), serde_json::Error> {
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() -> Result<(), serde_json::Error> {
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,
});
assert_matches!(
from_json_value(obj)?,
IncomingRoomEventFilter {
types: Some(types),
not_types,
rooms: None,
not_rooms,
senders: None,
not_senders,
limit: None,
url_filter: Some(UrlFilter::EventsWithUrl),
lazy_load_options: LazyLoadOptions::Enabled { include_redundant_members: false },
} if types == vec!["m.room.message".to_owned()]
&& not_types.is_empty()
&& not_rooms.is_empty()
&& not_senders.is_empty()
);
Ok(())
}
}