Skip to content

Commit

Permalink
push: Add methods to get and insert user push rules in a Ruleset
Browse files Browse the repository at this point in the history
  • Loading branch information
zecakeh committed Nov 3, 2022
1 parent 6964d87 commit 7ee7777
Show file tree
Hide file tree
Showing 3 changed files with 142 additions and 4 deletions.
1 change: 1 addition & 0 deletions crates/ruma-common/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ Improvements:
* Stabilize support for event replacements (edits)
* 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 `Ruleset::insert` to insert new user push rules and `Ruleset::get` to access push rules.

# 0.10.5

Expand Down
8 changes: 4 additions & 4 deletions crates/ruma-common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ default = ["client", "server"]
client = []
server = []

api = ["dep:http", "dep:thiserror"]
api = ["dep:http"]
canonical-json = []
compat = ["ruma-macros/compat", "ruma-identifiers-validation/compat"]
events = ["dep:thiserror"]
events = []
js = ["dep:js-sys", "getrandom?/js", "uuid?/js"]
markdown = ["pulldown-cmark"]
rand = ["dep:rand", "dep:uuid"]
Expand All @@ -35,7 +35,7 @@ unstable-msc2677 = []
unstable-msc2746 = []
unstable-msc2870 = []
unstable-msc3245 = ["unstable-msc3246"]
unstable-msc3246 = ["unstable-msc3551", "dep:thiserror"]
unstable-msc3246 = ["unstable-msc3551"]
unstable-msc3381 = ["unstable-msc1767"]
unstable-msc3488 = ["unstable-msc1767"]
unstable-msc3551 = ["unstable-msc1767"]
Expand Down Expand Up @@ -67,7 +67,7 @@ ruma-identifiers-validation = { version = "0.9.0", path = "../ruma-identifiers-v
ruma-macros = { version = "0.10.5", path = "../ruma-macros" }
serde = { version = "1.0.118", features = ["derive"] }
serde_json = { version = "1.0.64", features = ["raw_value"] }
thiserror = { version = "1.0.26", optional = true }
thiserror = { version = "1.0.26" }
tracing = "0.1.25"
url = "2.2.2"
uuid = { version = "1.0.0", optional = true, features = ["v4"] }
Expand Down
137 changes: 137 additions & 0 deletions crates/ruma-common/src/push.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use indexmap::{Equivalent, IndexSet};
use serde::{Deserialize, Serialize};
#[cfg(feature = "unstable-unspecified")]
use serde_json::Value as JsonValue;
use thiserror::Error;
use tracing::instrument;

use crate::{
Expand Down Expand Up @@ -85,6 +86,79 @@ impl Ruleset {
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 be moved as the next-most important rule with respect to
/// `before`. If neither are set, and the rule is newly inserted, it will be moved as the most
/// rule with the higher priority of its kind.
///
/// Returns an error if the parameters are invalid, otherwise returns the replaced push rule, if
/// any.
pub fn insert(
&mut self,
rule: AnyPushRule,
after: Option<&str>,
before: Option<&str>,
) -> Result<Option<AnyPushRule>, 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.filter(|s| s.starts_with('.')).is_some() {
return Err(InsertPushRuleError::RelativeToServerDefaultRule);
}
if before.filter(|s| s.starts_with('.')).is_some() {
return Err(InsertPushRuleError::RelativeToServerDefaultRule);
}

match rule {
AnyPushRule::Override(r) => {
// `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;
indexset_replace_and_move(&mut self.override_, r, default_position, after, before)
.map(|r| r.map(AnyPushRule::Override))
}
AnyPushRule::Underride(r) => {
indexset_replace_and_move(&mut self.underride, r, 0, after, before)
.map(|r| r.map(AnyPushRule::Underride))
}
AnyPushRule::Content(r) => {
indexset_replace_and_move(&mut self.content, r, 0, after, before)
.map(|r| r.map(AnyPushRule::Content))
}
AnyPushRule::Room(r) => indexset_replace_and_move(&mut self.room, r, 0, after, before)
.map(|r| r.map(AnyPushRule::Room)),
AnyPushRule::Sender(r) => {
indexset_replace_and_move(&mut self.sender, r, 0, after, before)
.map(|r| r.map(AnyPushRule::Sender))
}
}
}

/// 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<'_>> {
let rule_id = rule_id.as_ref();

match kind {
RuleKind::Override => self.override_.get(rule_id).map(AnyPushRuleRef::Override),
RuleKind::Underride => self.underride.get(rule_id).map(AnyPushRuleRef::Underride),
RuleKind::Sender => self.sender.get(rule_id).map(AnyPushRuleRef::Sender),
RuleKind::Room => self.room.get(rule_id).map(AnyPushRuleRef::Room),
RuleKind::Content => self.content.get(rule_id).map(AnyPushRuleRef::Content),
RuleKind::_Custom(_) => None,
}
}

/// Get the first push rule that applies to this event, if any.
///
/// # Arguments
Expand Down Expand Up @@ -477,6 +551,69 @@ pub enum RuleKind {
_Custom(PrivOwnedStr),
}

/// 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,
}

/// Replace the rule and move it to the given position.
pub fn indexset_replace_and_move<T>(
set: &mut IndexSet<T>,
rule: T,
default_position: usize,
after: Option<&str>,
before: Option<&str>,
) -> Result<Option<T>, 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(replaced)
}

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
Expand Down

0 comments on commit 7ee7777

Please sign in to comment.