Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Merge branch 'clokep/exact-event-search' into clokep/intentional-ment…
Browse files Browse the repository at this point in the history
…ions-exact
  • Loading branch information
clokep committed Feb 10, 2023
2 parents 56bbcd4 + f3f9279 commit 9786450
Show file tree
Hide file tree
Showing 9 changed files with 178 additions and 42 deletions.
1 change: 1 addition & 0 deletions changelog.d/15045.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Experimental support for [MSC3966](https://github.com/matrix-org/matrix-spec-proposals/pull/3966): the `exact_event_property_contains` push rule condition.
32 changes: 18 additions & 14 deletions rust/benches/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@
#![feature(test)]
use std::collections::BTreeSet;
use synapse::push::{
evaluator::PushRuleEvaluator, Condition, EventMatchCondition, FilteredPushRules, PushRules,
SimpleJsonValue,
evaluator::PushRuleEvaluator, Condition, EventMatchCondition, FilteredPushRules, JsonValue,
PushRules, SimpleJsonValue,
};
use test::Bencher;

Expand All @@ -27,15 +27,15 @@ fn bench_match_exact(b: &mut Bencher) {
let flattened_keys = [
(
"type".to_string(),
SimpleJsonValue::Str("m.text".to_string()),
JsonValue::Value(SimpleJsonValue::Str("m.text".to_string())),
),
(
"room_id".to_string(),
SimpleJsonValue::Str("!room:server".to_string()),
JsonValue::Value(SimpleJsonValue::Str("!room:server".to_string())),
),
(
"content.body".to_string(),
SimpleJsonValue::Str("test message".to_string()),
JsonValue::Value(SimpleJsonValue::Str("test message".to_string())),
),
]
.into_iter()
Expand All @@ -53,6 +53,7 @@ fn bench_match_exact(b: &mut Bencher) {
vec![],
false,
false,
false,
)
.unwrap();

Expand All @@ -75,15 +76,15 @@ fn bench_match_word(b: &mut Bencher) {
let flattened_keys = [
(
"type".to_string(),
SimpleJsonValue::Str("m.text".to_string()),
JsonValue::Value(SimpleJsonValue::Str("m.text".to_string())),
),
(
"room_id".to_string(),
SimpleJsonValue::Str("!room:server".to_string()),
JsonValue::Value(SimpleJsonValue::Str("!room:server".to_string())),
),
(
"content.body".to_string(),
SimpleJsonValue::Str("test message".to_string()),
JsonValue::Value(SimpleJsonValue::Str("test message".to_string())),
),
]
.into_iter()
Expand All @@ -101,6 +102,7 @@ fn bench_match_word(b: &mut Bencher) {
vec![],
false,
false,
false,
)
.unwrap();

Expand All @@ -123,15 +125,15 @@ fn bench_match_word_miss(b: &mut Bencher) {
let flattened_keys = [
(
"type".to_string(),
SimpleJsonValue::Str("m.text".to_string()),
JsonValue::Value(SimpleJsonValue::Str("m.text".to_string())),
),
(
"room_id".to_string(),
SimpleJsonValue::Str("!room:server".to_string()),
JsonValue::Value(SimpleJsonValue::Str("!room:server".to_string())),
),
(
"content.body".to_string(),
SimpleJsonValue::Str("test message".to_string()),
JsonValue::Value(SimpleJsonValue::Str("test message".to_string())),
),
]
.into_iter()
Expand All @@ -149,6 +151,7 @@ fn bench_match_word_miss(b: &mut Bencher) {
vec![],
false,
false,
false,
)
.unwrap();

Expand All @@ -171,15 +174,15 @@ fn bench_eval_message(b: &mut Bencher) {
let flattened_keys = [
(
"type".to_string(),
SimpleJsonValue::Str("m.text".to_string()),
JsonValue::Value(SimpleJsonValue::Str("m.text".to_string())),
),
(
"room_id".to_string(),
SimpleJsonValue::Str("!room:server".to_string()),
JsonValue::Value(SimpleJsonValue::Str("!room:server".to_string())),
),
(
"content.body".to_string(),
SimpleJsonValue::Str("test message".to_string()),
JsonValue::Value(SimpleJsonValue::Str("test message".to_string())),
),
]
.into_iter()
Expand All @@ -197,6 +200,7 @@ fn bench_eval_message(b: &mut Bencher) {
vec![],
false,
false,
false,
)
.unwrap();

Expand Down
65 changes: 51 additions & 14 deletions rust/src/push/evaluator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@

use std::collections::{BTreeMap, BTreeSet};

use crate::push::JsonValue;
use anyhow::{Context, Error};
use lazy_static::lazy_static;
use log::warn;
Expand Down Expand Up @@ -63,7 +64,7 @@ impl RoomVersionFeatures {
pub struct PushRuleEvaluator {
/// A mapping of "flattened" keys to simple JSON values in the event, e.g.
/// includes things like "type" and "content.msgtype".
flattened_keys: BTreeMap<String, SimpleJsonValue>,
flattened_keys: BTreeMap<String, JsonValue>,

/// The "content.body", if any.
body: String,
Expand All @@ -85,7 +86,7 @@ pub struct PushRuleEvaluator {

/// The related events, indexed by relation type. Flattened in the same manner as
/// `flattened_keys`.
related_events_flattened: BTreeMap<String, BTreeMap<String, SimpleJsonValue>>,
related_events_flattened: BTreeMap<String, BTreeMap<String, JsonValue>>,

/// If msc3664, push rules for related events, is enabled.
related_event_match_enabled: bool,
Expand All @@ -99,6 +100,9 @@ pub struct PushRuleEvaluator {

/// If MSC3758 (exact_event_match push rule condition) is enabled.
msc3758_exact_event_match: bool,

/// If MSC3966 (exact_event_property_contains push rule condition) is enabled.
msc3966_exact_event_property_contains: bool,
}

#[pymethods]
Expand All @@ -107,20 +111,21 @@ impl PushRuleEvaluator {
#[allow(clippy::too_many_arguments)]
#[new]
pub fn py_new(
flattened_keys: BTreeMap<String, SimpleJsonValue>,
flattened_keys: BTreeMap<String, JsonValue>,
has_mentions: bool,
user_mentions: BTreeSet<String>,
room_member_count: u64,
sender_power_level: Option<i64>,
notification_power_levels: BTreeMap<String, i64>,
related_events_flattened: BTreeMap<String, BTreeMap<String, SimpleJsonValue>>,
related_events_flattened: BTreeMap<String, BTreeMap<String, JsonValue>>,
related_event_match_enabled: bool,
room_version_feature_flags: Vec<String>,
msc3931_enabled: bool,
msc3758_exact_event_match: bool,
msc3966_exact_event_property_contains: bool,
) -> Result<Self, Error> {
let body = match flattened_keys.get("content.body") {
Some(SimpleJsonValue::Str(s)) => s.clone(),
Some(JsonValue::Value(SimpleJsonValue::Str(s))) => s.clone(),
_ => String::new(),
};

Expand All @@ -137,6 +142,7 @@ impl PushRuleEvaluator {
room_version_feature_flags,
msc3931_enabled,
msc3758_exact_event_match,
msc3966_exact_event_property_contains,
})
}

Expand Down Expand Up @@ -259,6 +265,9 @@ impl PushRuleEvaluator {
KnownCondition::RelatedEventMatch(event_match) => {
self.match_related_event_match(event_match, user_id)?
}
KnownCondition::ExactEventPropertyContains(exact_event_match) => {
self.match_exact_event_property_contains(exact_event_match)?
}
KnownCondition::IsUserMention => {
if let Some(uid) = user_id {
self.user_mentions.contains(uid)
Expand Down Expand Up @@ -340,7 +349,7 @@ impl PushRuleEvaluator {
return Ok(false);
};

let haystack = if let Some(SimpleJsonValue::Str(haystack)) =
let haystack = if let Some(JsonValue::Value(SimpleJsonValue::Str(haystack))) =
self.flattened_keys.get(&*event_match.key)
{
haystack
Expand Down Expand Up @@ -372,7 +381,9 @@ impl PushRuleEvaluator {

let value = &exact_event_match.value;

let haystack = if let Some(haystack) = self.flattened_keys.get(&*exact_event_match.key) {
let haystack = if let Some(JsonValue::Value(haystack)) =
self.flattened_keys.get(&*exact_event_match.key)
{
haystack
} else {
return Ok(false);
Expand Down Expand Up @@ -436,11 +447,12 @@ impl PushRuleEvaluator {
return Ok(false);
};

let haystack = if let Some(SimpleJsonValue::Str(haystack)) = event.get(&**key) {
haystack
} else {
return Ok(false);
};
let haystack =
if let Some(JsonValue::Value(SimpleJsonValue::Str(haystack))) = event.get(&**key) {
haystack
} else {
return Ok(false);
};

// For the content.body we match against "words", but for everything
// else we match against the entire value.
Expand All @@ -454,6 +466,29 @@ impl PushRuleEvaluator {
compiled_pattern.is_match(haystack)
}

/// Evaluates a `exact_event_property_contains` condition. (MSC3758)
fn match_exact_event_property_contains(
&self,
exact_event_match: &ExactEventMatchCondition,
) -> Result<bool, Error> {
// First check if the feature is enabled.
if !self.msc3966_exact_event_property_contains {
return Ok(false);
}

let value = &exact_event_match.value;

let haystack = if let Some(JsonValue::Array(haystack)) =
self.flattened_keys.get(&*exact_event_match.key)
{
haystack
} else {
return Ok(false);
};

Ok(haystack.contains(&**value))
}

/// Match the member count against an 'is' condition
/// The `is` condition can be things like '>2', '==3' or even just '4'.
fn match_member_count(&self, is: &str) -> Result<bool, Error> {
Expand Down Expand Up @@ -483,7 +518,7 @@ fn push_rule_evaluator() {
let mut flattened_keys = BTreeMap::new();
flattened_keys.insert(
"content.body".to_string(),
SimpleJsonValue::Str("foo bar bob hello".to_string()),
JsonValue::Value(SimpleJsonValue::Str("foo bar bob hello".to_string())),
);
let evaluator = PushRuleEvaluator::py_new(
flattened_keys,
Expand All @@ -497,6 +532,7 @@ fn push_rule_evaluator() {
vec![],
true,
true,
true,
)
.unwrap();

Expand All @@ -513,7 +549,7 @@ fn test_requires_room_version_supports_condition() {
let mut flattened_keys = BTreeMap::new();
flattened_keys.insert(
"content.body".to_string(),
SimpleJsonValue::Str("foo bar bob hello".to_string()),
JsonValue::Value(SimpleJsonValue::Str("foo bar bob hello".to_string())),
);
let flags = vec![RoomVersionFeatures::ExtensibleEvents.as_str().to_string()];
let evaluator = PushRuleEvaluator::py_new(
Expand All @@ -528,6 +564,7 @@ fn test_requires_room_version_supports_condition() {
flags,
true,
true,
true,
)
.unwrap();

Expand Down
35 changes: 34 additions & 1 deletion rust/src/push/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ use anyhow::{Context, Error};
use log::warn;
use pyo3::exceptions::PyTypeError;
use pyo3::prelude::*;
use pyo3::types::{PyBool, PyLong, PyString};
use pyo3::types::{PyBool, PyList, PyLong, PyString};
use pythonize::{depythonize, pythonize};
use serde::de::Error as _;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -280,6 +280,37 @@ impl<'source> FromPyObject<'source> for SimpleJsonValue {
}
}

/// A JSON values (list, string, int, boolean, or null).
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
#[serde(untagged)]
pub enum JsonValue {
Array(Vec<SimpleJsonValue>),
Value(SimpleJsonValue),
}

impl<'source> FromPyObject<'source> for JsonValue {
fn extract(ob: &'source PyAny) -> PyResult<Self> {
if let Ok(l) = <PyList as pyo3::PyTryFrom>::try_from(ob) {
if let Ok(a) = l.iter().map(SimpleJsonValue::extract).collect() {
Ok(JsonValue::Array(a))
} else {
// TOOD How to get a good error?
Err(PyTypeError::new_err(format!(
"Can't convert from {} to JsonValue::Array",
ob.get_type().name()?
)))
}
} else if let Ok(v) = SimpleJsonValue::extract(ob) {
Ok(JsonValue::Value(v))
} else {
Err(PyTypeError::new_err(format!(
"Can't convert from {} to JsonValue",
ob.get_type().name()?
)))
}
}
}

/// A condition used in push rules to match against an event.
///
/// We need this split as `serde` doesn't give us the ability to have a
Expand All @@ -303,6 +334,8 @@ pub enum KnownCondition {
ExactEventMatch(ExactEventMatchCondition),
#[serde(rename = "im.nheko.msc3664.related_event_match")]
RelatedEventMatch(RelatedEventMatchCondition),
#[serde(rename = "org.matrix.msc3966.exact_event_property_contains")]
ExactEventPropertyContains(ExactEventMatchCondition),
#[serde(rename = "org.matrix.msc3952.is_user_mention")]
IsUserMention,
ContainsDisplayName,
Expand Down
7 changes: 4 additions & 3 deletions stubs/synapse/synapse_rust/push.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

from typing import Any, Collection, Dict, Mapping, Optional, Sequence, Set, Tuple, Union

from synapse.types import JsonDict, SimpleJsonValue
from synapse.types import JsonDict, JsonValue

class PushRule:
@property
Expand Down Expand Up @@ -56,17 +56,18 @@ def get_base_rule_ids() -> Collection[str]: ...
class PushRuleEvaluator:
def __init__(
self,
flattened_keys: Mapping[str, SimpleJsonValue],
flattened_keys: Mapping[str, JsonValue],
has_mentions: bool,
user_mentions: Set[str],
room_member_count: int,
sender_power_level: Optional[int],
notification_power_levels: Mapping[str, int],
related_events_flattened: Mapping[str, Mapping[str, SimpleJsonValue]],
related_events_flattened: Mapping[str, Mapping[str, JsonValue]],
related_event_match_enabled: bool,
room_version_feature_flags: Tuple[str, ...],
msc3931_enabled: bool,
msc3758_exact_event_match: bool,
msc3966_exact_event_property_contains: bool,
): ...
def run(
self,
Expand Down
5 changes: 5 additions & 0 deletions synapse/config/experimental.py
Original file line number Diff line number Diff line change
Expand Up @@ -189,3 +189,8 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
self.msc3958_supress_edit_notifs = experimental.get(
"msc3958_supress_edit_notifs", False
)

# MSC3966: exact_event_property_contains push rule condition.
self.msc3966_exact_event_property_contains = experimental.get(
"msc3966_exact_event_property_contains", False
)
Loading

0 comments on commit 9786450

Please sign in to comment.