push: Add method to insert a user push rule in a Ruleset
This commit is contained in:
parent
a50f5f5cb2
commit
f87714d73f
@ -35,7 +35,9 @@ Improvements:
|
|||||||
* Stabilize support for event replacements (edits)
|
* Stabilize support for event replacements (edits)
|
||||||
* Add support for read receipts for threads (MSC3771 / Matrix 1.4)
|
* Add support for read receipts for threads (MSC3771 / Matrix 1.4)
|
||||||
* Add `push::PredefinedRuleId` and associated types as a list of predefined push rule IDs
|
* Add `push::PredefinedRuleId` and associated types as a list of predefined push rule IDs
|
||||||
* Add `Ruleset::get` to access push rules.
|
* Add convenience methods to `Ruleset`
|
||||||
|
* `Ruleset::get` to access a push rule
|
||||||
|
* `Ruleset::insert` to add or update user push rules
|
||||||
|
|
||||||
# 0.10.5
|
# 0.10.5
|
||||||
|
|
||||||
|
@ -21,10 +21,10 @@ default = ["client", "server"]
|
|||||||
client = []
|
client = []
|
||||||
server = []
|
server = []
|
||||||
|
|
||||||
api = ["dep:http", "dep:thiserror"]
|
api = ["dep:http"]
|
||||||
canonical-json = []
|
canonical-json = []
|
||||||
compat = ["ruma-macros/compat", "ruma-identifiers-validation/compat"]
|
compat = ["ruma-macros/compat", "ruma-identifiers-validation/compat"]
|
||||||
events = ["dep:thiserror"]
|
events = []
|
||||||
js = ["dep:js-sys", "getrandom?/js", "uuid?/js"]
|
js = ["dep:js-sys", "getrandom?/js", "uuid?/js"]
|
||||||
markdown = ["pulldown-cmark"]
|
markdown = ["pulldown-cmark"]
|
||||||
rand = ["dep:rand", "dep:uuid"]
|
rand = ["dep:rand", "dep:uuid"]
|
||||||
@ -35,7 +35,7 @@ unstable-msc2677 = []
|
|||||||
unstable-msc2746 = []
|
unstable-msc2746 = []
|
||||||
unstable-msc2870 = []
|
unstable-msc2870 = []
|
||||||
unstable-msc3245 = ["unstable-msc3246"]
|
unstable-msc3245 = ["unstable-msc3246"]
|
||||||
unstable-msc3246 = ["unstable-msc3551", "dep:thiserror"]
|
unstable-msc3246 = ["unstable-msc3551"]
|
||||||
unstable-msc3381 = ["unstable-msc1767"]
|
unstable-msc3381 = ["unstable-msc1767"]
|
||||||
unstable-msc3488 = ["unstable-msc1767"]
|
unstable-msc3488 = ["unstable-msc1767"]
|
||||||
unstable-msc3551 = ["unstable-msc1767"]
|
unstable-msc3551 = ["unstable-msc1767"]
|
||||||
@ -67,7 +67,7 @@ ruma-identifiers-validation = { version = "0.9.0", path = "../ruma-identifiers-v
|
|||||||
ruma-macros = { version = "0.10.5", path = "../ruma-macros" }
|
ruma-macros = { version = "0.10.5", path = "../ruma-macros" }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
serde_json = { workspace = true, features = ["raw_value"] }
|
serde_json = { workspace = true, features = ["raw_value"] }
|
||||||
thiserror = { workspace = true, optional = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true, features = ["attributes"] }
|
tracing = { workspace = true, features = ["attributes"] }
|
||||||
url = "2.2.2"
|
url = "2.2.2"
|
||||||
uuid = { version = "1.0.0", optional = true, features = ["v4"] }
|
uuid = { version = "1.0.0", optional = true, features = ["v4"] }
|
||||||
|
@ -20,6 +20,7 @@ use indexmap::{Equivalent, IndexSet};
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
#[cfg(feature = "unstable-unspecified")]
|
#[cfg(feature = "unstable-unspecified")]
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
|
use thiserror::Error;
|
||||||
use tracing::instrument;
|
use tracing::instrument;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
@ -85,6 +86,92 @@ impl Ruleset {
|
|||||||
self.into_iter()
|
self.into_iter()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Inserts a user-defined rule in the rule set.
|
||||||
|
///
|
||||||
|
/// If a rule with the same kind and `rule_id` exists, it will be replaced.
|
||||||
|
///
|
||||||
|
/// If `after` or `before` is set, the rule will be moved relative to the rule with the given
|
||||||
|
/// ID. If both are set, the rule will become the next-most important rule with respect to
|
||||||
|
/// `before`. If neither are set, and the rule is newly inserted, it will become the rule with
|
||||||
|
/// the highest priority of its kind.
|
||||||
|
///
|
||||||
|
/// Returns an error if the parameters are invalid.
|
||||||
|
pub fn insert(
|
||||||
|
&mut self,
|
||||||
|
rule: NewPushRule,
|
||||||
|
after: Option<&str>,
|
||||||
|
before: Option<&str>,
|
||||||
|
) -> Result<(), InsertPushRuleError> {
|
||||||
|
let rule_id = rule.rule_id();
|
||||||
|
if rule_id.starts_with('.') {
|
||||||
|
return Err(InsertPushRuleError::ServerDefaultRuleId);
|
||||||
|
}
|
||||||
|
if rule_id.contains('/') {
|
||||||
|
return Err(InsertPushRuleError::InvalidRuleId);
|
||||||
|
}
|
||||||
|
if rule_id.contains('\\') {
|
||||||
|
return Err(InsertPushRuleError::InvalidRuleId);
|
||||||
|
}
|
||||||
|
if after.map_or(false, |s| s.starts_with('.')) {
|
||||||
|
return Err(InsertPushRuleError::RelativeToServerDefaultRule);
|
||||||
|
}
|
||||||
|
if before.map_or(false, |s| s.starts_with('.')) {
|
||||||
|
return Err(InsertPushRuleError::RelativeToServerDefaultRule);
|
||||||
|
}
|
||||||
|
|
||||||
|
match rule {
|
||||||
|
NewPushRule::Override(r) => {
|
||||||
|
let mut rule = ConditionalPushRule::from(r);
|
||||||
|
|
||||||
|
if let Some(prev_rule) = self.override_.get(rule.rule_id.as_str()) {
|
||||||
|
rule.enabled = prev_rule.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
// `m.rule.master` should always be the rule with the highest priority, so we insert
|
||||||
|
// this one at most at the second place.
|
||||||
|
let default_position = 1;
|
||||||
|
|
||||||
|
insert_and_move_rule(&mut self.override_, rule, default_position, after, before)
|
||||||
|
}
|
||||||
|
NewPushRule::Underride(r) => {
|
||||||
|
let mut rule = ConditionalPushRule::from(r);
|
||||||
|
|
||||||
|
if let Some(prev_rule) = self.underride.get(rule.rule_id.as_str()) {
|
||||||
|
rule.enabled = prev_rule.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_and_move_rule(&mut self.underride, rule, 0, after, before)
|
||||||
|
}
|
||||||
|
NewPushRule::Content(r) => {
|
||||||
|
let mut rule = PatternedPushRule::from(r);
|
||||||
|
|
||||||
|
if let Some(prev_rule) = self.content.get(rule.rule_id.as_str()) {
|
||||||
|
rule.enabled = prev_rule.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_and_move_rule(&mut self.content, rule, 0, after, before)
|
||||||
|
}
|
||||||
|
NewPushRule::Room(r) => {
|
||||||
|
let mut rule = SimplePushRule::from(r);
|
||||||
|
|
||||||
|
if let Some(prev_rule) = self.room.get(rule.rule_id.as_str()) {
|
||||||
|
rule.enabled = prev_rule.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_and_move_rule(&mut self.room, rule, 0, after, before)
|
||||||
|
}
|
||||||
|
NewPushRule::Sender(r) => {
|
||||||
|
let mut rule = SimplePushRule::from(r);
|
||||||
|
|
||||||
|
if let Some(prev_rule) = self.sender.get(rule.rule_id.as_str()) {
|
||||||
|
rule.enabled = prev_rule.enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
insert_and_move_rule(&mut self.sender, rule, 0, after, before)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the rule from the given kind and with the given `rule_id` in the rule set.
|
/// Get the rule from the given kind and with the given `rule_id` in the rule set.
|
||||||
pub fn get(&self, kind: RuleKind, rule_id: impl AsRef<str>) -> Option<AnyPushRuleRef<'_>> {
|
pub fn get(&self, kind: RuleKind, rule_id: impl AsRef<str>) -> Option<AnyPushRuleRef<'_>> {
|
||||||
let rule_id = rule_id.as_ref();
|
let rule_id = rule_id.as_ref();
|
||||||
@ -545,6 +632,13 @@ impl NewSimplePushRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NewSimplePushRule> for SimplePushRule {
|
||||||
|
fn from(new_rule: NewSimplePushRule) -> Self {
|
||||||
|
let NewSimplePushRule { rule_id, actions } = new_rule;
|
||||||
|
Self { actions, default: false, enabled: true, rule_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A patterned push rule to update or create.
|
/// A patterned push rule to update or create.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
@ -567,6 +661,13 @@ impl NewPatternedPushRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NewPatternedPushRule> for PatternedPushRule {
|
||||||
|
fn from(new_rule: NewPatternedPushRule) -> Self {
|
||||||
|
let NewPatternedPushRule { rule_id, pattern, actions } = new_rule;
|
||||||
|
Self { actions, default: false, enabled: true, rule_id, pattern }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A conditional push rule to update or create.
|
/// A conditional push rule to update or create.
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)]
|
||||||
@ -592,6 +693,76 @@ impl NewConditionalPushRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<NewConditionalPushRule> for ConditionalPushRule {
|
||||||
|
fn from(new_rule: NewConditionalPushRule) -> Self {
|
||||||
|
let NewConditionalPushRule { rule_id, conditions, actions } = new_rule;
|
||||||
|
Self { actions, default: false, enabled: true, rule_id, conditions }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The error type returned when trying to insert a user-defined push rule into a `Ruleset`.
|
||||||
|
#[derive(Debug, Error)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum InsertPushRuleError {
|
||||||
|
/// The rule ID starts with a dot (`.`), which is reserved for server-default rules.
|
||||||
|
#[error("rule IDs starting with a dot are reserved for server-default rules")]
|
||||||
|
ServerDefaultRuleId,
|
||||||
|
|
||||||
|
/// The rule ID contains an invalid character.
|
||||||
|
#[error("invalid rule ID")]
|
||||||
|
InvalidRuleId,
|
||||||
|
|
||||||
|
/// The rule is being placed relative to a server-default rule, which is forbidden.
|
||||||
|
#[error("can't place rule relative to server-default rule")]
|
||||||
|
RelativeToServerDefaultRule,
|
||||||
|
|
||||||
|
/// The `before` or `after` rule could not be found.
|
||||||
|
#[error("The before or after rule could not be found")]
|
||||||
|
UnknownRuleId,
|
||||||
|
|
||||||
|
/// `before` has a higher priority than `after`.
|
||||||
|
#[error("before has a higher priority than after")]
|
||||||
|
BeforeHigherThanAfter,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Insert the rule in the given indexset and move it to the given position.
|
||||||
|
pub fn insert_and_move_rule<T>(
|
||||||
|
set: &mut IndexSet<T>,
|
||||||
|
rule: T,
|
||||||
|
default_position: usize,
|
||||||
|
after: Option<&str>,
|
||||||
|
before: Option<&str>,
|
||||||
|
) -> Result<(), InsertPushRuleError>
|
||||||
|
where
|
||||||
|
T: Hash + Eq,
|
||||||
|
str: Equivalent<T>,
|
||||||
|
{
|
||||||
|
let (from, replaced) = set.replace_full(rule);
|
||||||
|
|
||||||
|
let mut to = default_position;
|
||||||
|
|
||||||
|
if let Some(rule_id) = after {
|
||||||
|
let idx = set.get_index_of(rule_id).ok_or(InsertPushRuleError::UnknownRuleId)?;
|
||||||
|
to = idx + 1;
|
||||||
|
}
|
||||||
|
if let Some(rule_id) = before {
|
||||||
|
let idx = set.get_index_of(rule_id).ok_or(InsertPushRuleError::UnknownRuleId)?;
|
||||||
|
|
||||||
|
if idx < to {
|
||||||
|
return Err(InsertPushRuleError::BeforeHigherThanAfter);
|
||||||
|
}
|
||||||
|
|
||||||
|
to = idx;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only move the item if it's new or if it was positioned.
|
||||||
|
if replaced.is_none() || after.is_some() || before.is_some() {
|
||||||
|
set.move_index(from, to);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user