Split ruma_common::push into more submodules
This commit is contained in:
parent
4be63127f7
commit
e04ed242fc
@ -8,13 +8,15 @@ use std::{
|
|||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
};
|
};
|
||||||
|
|
||||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
use serde::{Deserialize, Serialize};
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
|
||||||
|
|
||||||
pub use room_member_count_is::{ComparisonOperator, RoomMemberCountIs};
|
pub use self::{
|
||||||
|
action::{Action, Tweak},
|
||||||
|
condition::{ComparisonOperator, PushCondition, RoomMemberCountIs},
|
||||||
|
};
|
||||||
|
|
||||||
mod room_member_count_is;
|
mod action;
|
||||||
mod tweak_serde;
|
mod condition;
|
||||||
|
|
||||||
/// A push ruleset scopes a set of rules according to some criteria.
|
/// A push ruleset scopes a set of rules according to some criteria.
|
||||||
///
|
///
|
||||||
@ -227,321 +229,3 @@ impl TryFrom<AnyPushRule> for ConditionalPushRule {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This represents the different actions that should be taken when a rule is matched, and
|
|
||||||
/// controls how notifications are delivered to the client.
|
|
||||||
///
|
|
||||||
/// See https://matrix.org/docs/spec/client_server/r0.6.0#actions for details.
|
|
||||||
#[derive(Clone, Debug)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
pub enum Action {
|
|
||||||
/// Causes matching events to generate a notification.
|
|
||||||
Notify,
|
|
||||||
|
|
||||||
/// Prevents matching events from generating a notification.
|
|
||||||
DontNotify,
|
|
||||||
|
|
||||||
/// Behaves like notify but homeservers may choose to coalesce multiple events
|
|
||||||
/// into a single notification.
|
|
||||||
Coalesce,
|
|
||||||
|
|
||||||
/// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
|
|
||||||
SetTweak(Tweak),
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The `set_tweak` action.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[serde(from = "tweak_serde::Tweak", into = "tweak_serde::Tweak")]
|
|
||||||
pub enum Tweak {
|
|
||||||
/// A string representing the sound to be played when this notification arrives.
|
|
||||||
///
|
|
||||||
/// A value of "default" means to play a default sound. A device may choose to alert the user by
|
|
||||||
/// some other means if appropriate, eg. vibration.
|
|
||||||
Sound(String),
|
|
||||||
|
|
||||||
/// A boolean representing whether or not this message should be highlighted in the UI.
|
|
||||||
///
|
|
||||||
/// This will normally take the form of presenting the message in a different color and/or
|
|
||||||
/// style. The UI might also be adjusted to draw particular attention to the room in which the
|
|
||||||
/// event occurred. If a `highlight` tweak is given with no value, its value is defined to be
|
|
||||||
/// `true`. If no highlight tweak is given at all then the value of `highlight` is defined to be
|
|
||||||
/// `false`.
|
|
||||||
Highlight(#[serde(default = "ruma_serde::default_true")] bool),
|
|
||||||
|
|
||||||
/// A custom tweak
|
|
||||||
Custom {
|
|
||||||
/// The name of the custom tweak (`set_tweak` field)
|
|
||||||
name: String,
|
|
||||||
|
|
||||||
/// The value of the custom tweak
|
|
||||||
value: Box<RawJsonValue>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'de> Deserialize<'de> for Action {
|
|
||||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
|
||||||
where
|
|
||||||
D: Deserializer<'de>,
|
|
||||||
{
|
|
||||||
use serde::de::{MapAccess, Visitor};
|
|
||||||
|
|
||||||
struct ActionVisitor;
|
|
||||||
impl<'de> Visitor<'de> for ActionVisitor {
|
|
||||||
type Value = Action;
|
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
write!(formatter, "a valid action object")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Match a simple action type
|
|
||||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
|
||||||
where
|
|
||||||
E: serde::de::Error,
|
|
||||||
{
|
|
||||||
match v {
|
|
||||||
"notify" => Ok(Action::Notify),
|
|
||||||
"dont_notify" => Ok(Action::DontNotify),
|
|
||||||
"coalesce" => Ok(Action::Coalesce),
|
|
||||||
s => Err(E::unknown_variant(&s, &["notify", "dont_notify", "coalesce"])),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Match the more complex set_tweaks action object as a key-value map
|
|
||||||
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
|
|
||||||
where
|
|
||||||
A: MapAccess<'de>,
|
|
||||||
{
|
|
||||||
Tweak::deserialize(serde::de::value::MapAccessDeserializer::new(map))
|
|
||||||
.map(Action::SetTweak)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
deserializer.deserialize_any(ActionVisitor)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Serialize for Action {
|
|
||||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
|
||||||
where
|
|
||||||
S: Serializer,
|
|
||||||
{
|
|
||||||
match self {
|
|
||||||
Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
|
|
||||||
Action::DontNotify => serializer.serialize_unit_variant("Action", 1, "dont_notify"),
|
|
||||||
Action::Coalesce => serializer.serialize_unit_variant("Action", 2, "coalesce"),
|
|
||||||
Action::SetTweak(kind) => kind.serialize(serializer),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A condition that must apply for an associated push rule's action to be taken.
|
|
||||||
#[derive(Clone, Debug, Deserialize, Serialize)]
|
|
||||||
#[non_exhaustive]
|
|
||||||
#[serde(tag = "kind", rename_all = "snake_case")]
|
|
||||||
pub enum PushCondition {
|
|
||||||
/// This is a glob pattern match on a field of the event.
|
|
||||||
EventMatch {
|
|
||||||
/// The dot-separated field of the event to match.
|
|
||||||
key: String,
|
|
||||||
|
|
||||||
/// The glob-style pattern to match against.
|
|
||||||
///
|
|
||||||
/// Patterns with no special glob characters should be treated as having asterisks prepended
|
|
||||||
/// and appended when testing the condition.
|
|
||||||
pattern: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// This matches unencrypted messages where `content.body` contains the owner's display name in
|
|
||||||
/// that room.
|
|
||||||
ContainsDisplayName,
|
|
||||||
|
|
||||||
/// This matches the current number of members in the room.
|
|
||||||
RoomMemberCount {
|
|
||||||
/// The condition on the current number of members in the room.
|
|
||||||
is: RoomMemberCountIs,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// This takes into account the current power levels in the room, ensuring the sender of the
|
|
||||||
/// event has high enough power to trigger the notification.
|
|
||||||
SenderNotificationPermission {
|
|
||||||
/// The field in the power level event the user needs a minimum power level for.
|
|
||||||
///
|
|
||||||
/// Fields must be specified under the `notifications` property in the power level event's
|
|
||||||
/// `content`.
|
|
||||||
key: String,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use js_int::uint;
|
|
||||||
use matches::assert_matches;
|
|
||||||
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
|
||||||
|
|
||||||
use super::{Action, PushCondition, RoomMemberCountIs, Tweak};
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_string_action() {
|
|
||||||
assert_eq!(to_json_value(&Action::Notify).unwrap(), json!("notify"));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_tweak_sound_action() {
|
|
||||||
assert_eq!(
|
|
||||||
to_json_value(&Action::SetTweak(Tweak::Sound("default".into()))).unwrap(),
|
|
||||||
json!({ "set_tweak": "sound", "value": "default" })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_tweak_highlight_action() {
|
|
||||||
assert_eq!(
|
|
||||||
to_json_value(&Action::SetTweak(Tweak::Highlight(true))).unwrap(),
|
|
||||||
json!({ "set_tweak": "highlight" })
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
to_json_value(&Action::SetTweak(Tweak::Highlight(false))).unwrap(),
|
|
||||||
json!({ "set_tweak": "highlight", "value": false })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_string_action() {
|
|
||||||
assert_matches!(from_json_value::<Action>(json!("notify")).unwrap(), Action::Notify);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_tweak_sound_action() {
|
|
||||||
let json_data = json!({
|
|
||||||
"set_tweak": "sound",
|
|
||||||
"value": "default"
|
|
||||||
});
|
|
||||||
assert_matches!(
|
|
||||||
&from_json_value::<Action>(json_data).unwrap(),
|
|
||||||
Action::SetTweak(Tweak::Sound(value)) if value == "default"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_tweak_highlight_action() {
|
|
||||||
let json_data = json!({
|
|
||||||
"set_tweak": "highlight",
|
|
||||||
"value": true
|
|
||||||
});
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<Action>(json_data).unwrap(),
|
|
||||||
Action::SetTweak(Tweak::Highlight(true))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_tweak_highlight_action_with_default_value() {
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<Action>(json!({ "set_tweak": "highlight" })).unwrap(),
|
|
||||||
Action::SetTweak(Tweak::Highlight(true))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_event_match_condition() {
|
|
||||||
let json_data = json!({
|
|
||||||
"key": "content.msgtype",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.notice"
|
|
||||||
});
|
|
||||||
assert_eq!(
|
|
||||||
to_json_value(&PushCondition::EventMatch {
|
|
||||||
key: "content.msgtype".into(),
|
|
||||||
pattern: "m.notice".into(),
|
|
||||||
})
|
|
||||||
.unwrap(),
|
|
||||||
json_data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_contains_display_name_condition() {
|
|
||||||
assert_eq!(
|
|
||||||
to_json_value(&PushCondition::ContainsDisplayName).unwrap(),
|
|
||||||
json!({ "kind": "contains_display_name" })
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_room_member_count_condition() {
|
|
||||||
let json_data = json!({
|
|
||||||
"is": "2",
|
|
||||||
"kind": "room_member_count"
|
|
||||||
});
|
|
||||||
assert_eq!(
|
|
||||||
to_json_value(&PushCondition::RoomMemberCount {
|
|
||||||
is: RoomMemberCountIs::from(uint!(2))
|
|
||||||
})
|
|
||||||
.unwrap(),
|
|
||||||
json_data
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn serialize_sender_notification_permission_condition() {
|
|
||||||
let json_data = json!({
|
|
||||||
"key": "room",
|
|
||||||
"kind": "sender_notification_permission"
|
|
||||||
});
|
|
||||||
assert_eq!(
|
|
||||||
json_data,
|
|
||||||
to_json_value(&PushCondition::SenderNotificationPermission { key: "room".into() })
|
|
||||||
.unwrap()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_event_match_condition() {
|
|
||||||
let json_data = json!({
|
|
||||||
"key": "content.msgtype",
|
|
||||||
"kind": "event_match",
|
|
||||||
"pattern": "m.notice"
|
|
||||||
});
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<PushCondition>(json_data).unwrap(),
|
|
||||||
PushCondition::EventMatch { key, pattern }
|
|
||||||
if key == "content.msgtype" && pattern == "m.notice"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_contains_display_name_condition() {
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<PushCondition>(json!({ "kind": "contains_display_name" })).unwrap(),
|
|
||||||
PushCondition::ContainsDisplayName
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_room_member_count_condition() {
|
|
||||||
let json_data = json!({
|
|
||||||
"is": "2",
|
|
||||||
"kind": "room_member_count"
|
|
||||||
});
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<PushCondition>(json_data).unwrap(),
|
|
||||||
PushCondition::RoomMemberCount { is }
|
|
||||||
if is == RoomMemberCountIs::from(uint!(2))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn deserialize_sender_notification_permission_condition() {
|
|
||||||
let json_data = json!({
|
|
||||||
"key": "room",
|
|
||||||
"kind": "sender_notification_permission"
|
|
||||||
});
|
|
||||||
assert_matches!(
|
|
||||||
from_json_value::<PushCondition>(json_data).unwrap(),
|
|
||||||
PushCondition::SenderNotificationPermission {
|
|
||||||
key
|
|
||||||
} if key == "room"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
236
ruma-common/src/push/action.rs
Normal file
236
ruma-common/src/push/action.rs
Normal file
@ -0,0 +1,236 @@
|
|||||||
|
use std::fmt::{self, Formatter};
|
||||||
|
|
||||||
|
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||||
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
|
/// This represents the different actions that should be taken when a rule is matched, and
|
||||||
|
/// controls how notifications are delivered to the client.
|
||||||
|
///
|
||||||
|
/// See https://matrix.org/docs/spec/client_server/r0.6.0#actions for details.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
pub enum Action {
|
||||||
|
/// Causes matching events to generate a notification.
|
||||||
|
Notify,
|
||||||
|
|
||||||
|
/// Prevents matching events from generating a notification.
|
||||||
|
DontNotify,
|
||||||
|
|
||||||
|
/// Behaves like notify but homeservers may choose to coalesce multiple events
|
||||||
|
/// into a single notification.
|
||||||
|
Coalesce,
|
||||||
|
|
||||||
|
/// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
|
||||||
|
SetTweak(Tweak),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The `set_tweak` action.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[serde(from = "tweak_serde::Tweak", into = "tweak_serde::Tweak")]
|
||||||
|
pub enum Tweak {
|
||||||
|
/// A string representing the sound to be played when this notification arrives.
|
||||||
|
///
|
||||||
|
/// A value of "default" means to play a default sound. A device may choose to alert the user by
|
||||||
|
/// some other means if appropriate, eg. vibration.
|
||||||
|
Sound(String),
|
||||||
|
|
||||||
|
/// A boolean representing whether or not this message should be highlighted in the UI.
|
||||||
|
///
|
||||||
|
/// This will normally take the form of presenting the message in a different color and/or
|
||||||
|
/// style. The UI might also be adjusted to draw particular attention to the room in which the
|
||||||
|
/// event occurred. If a `highlight` tweak is given with no value, its value is defined to be
|
||||||
|
/// `true`. If no highlight tweak is given at all then the value of `highlight` is defined to be
|
||||||
|
/// `false`.
|
||||||
|
Highlight(#[serde(default = "ruma_serde::default_true")] bool),
|
||||||
|
|
||||||
|
/// A custom tweak
|
||||||
|
Custom {
|
||||||
|
/// The name of the custom tweak (`set_tweak` field)
|
||||||
|
name: String,
|
||||||
|
|
||||||
|
/// The value of the custom tweak
|
||||||
|
value: Box<RawJsonValue>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'de> Deserialize<'de> for Action {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where
|
||||||
|
D: Deserializer<'de>,
|
||||||
|
{
|
||||||
|
use serde::de::{MapAccess, Visitor};
|
||||||
|
|
||||||
|
struct ActionVisitor;
|
||||||
|
impl<'de> Visitor<'de> for ActionVisitor {
|
||||||
|
type Value = Action;
|
||||||
|
|
||||||
|
fn expecting(&self, formatter: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(formatter, "a valid action object")
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match a simple action type
|
||||||
|
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||||
|
where
|
||||||
|
E: serde::de::Error,
|
||||||
|
{
|
||||||
|
match v {
|
||||||
|
"notify" => Ok(Action::Notify),
|
||||||
|
"dont_notify" => Ok(Action::DontNotify),
|
||||||
|
"coalesce" => Ok(Action::Coalesce),
|
||||||
|
s => Err(E::unknown_variant(&s, &["notify", "dont_notify", "coalesce"])),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Match the more complex set_tweaks action object as a key-value map
|
||||||
|
fn visit_map<A>(self, map: A) -> Result<Self::Value, A::Error>
|
||||||
|
where
|
||||||
|
A: MapAccess<'de>,
|
||||||
|
{
|
||||||
|
Tweak::deserialize(serde::de::value::MapAccessDeserializer::new(map))
|
||||||
|
.map(Action::SetTweak)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
deserializer.deserialize_any(ActionVisitor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Serialize for Action {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where
|
||||||
|
S: Serializer,
|
||||||
|
{
|
||||||
|
match self {
|
||||||
|
Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
|
||||||
|
Action::DontNotify => serializer.serialize_unit_variant("Action", 1, "dont_notify"),
|
||||||
|
Action::Coalesce => serializer.serialize_unit_variant("Action", 2, "coalesce"),
|
||||||
|
Action::SetTweak(kind) => kind.serialize(serializer),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod tweak_serde {
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use serde_json::value::RawValue as RawJsonValue;
|
||||||
|
|
||||||
|
/// Values for the `set_tweak` action.
|
||||||
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
|
#[serde(untagged)]
|
||||||
|
pub enum Tweak {
|
||||||
|
Sound(SoundTweak),
|
||||||
|
Highlight(HighlightTweak),
|
||||||
|
Custom {
|
||||||
|
#[serde(rename = "set_tweak")]
|
||||||
|
name: String,
|
||||||
|
value: Box<RawJsonValue>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "set_tweak", rename = "sound")]
|
||||||
|
pub struct SoundTweak {
|
||||||
|
value: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
||||||
|
#[serde(tag = "set_tweak", rename = "highlight")]
|
||||||
|
pub struct HighlightTweak {
|
||||||
|
#[serde(default = "ruma_serde::default_true", skip_serializing_if = "ruma_serde::is_true")]
|
||||||
|
value: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<super::Tweak> for Tweak {
|
||||||
|
fn from(tweak: super::Tweak) -> Self {
|
||||||
|
use super::Tweak::*;
|
||||||
|
|
||||||
|
match tweak {
|
||||||
|
Sound(value) => Self::Sound(SoundTweak { value }),
|
||||||
|
Highlight(value) => Self::Highlight(HighlightTweak { value }),
|
||||||
|
Custom { name, value } => Self::Custom { name, value },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<Tweak> for super::Tweak {
|
||||||
|
fn from(tweak: Tweak) -> Self {
|
||||||
|
use Tweak::*;
|
||||||
|
|
||||||
|
match tweak {
|
||||||
|
Sound(SoundTweak { value }) => Self::Sound(value),
|
||||||
|
Highlight(HighlightTweak { value }) => Self::Highlight(value),
|
||||||
|
Custom { name, value } => Self::Custom { name, value },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::{Action, Tweak};
|
||||||
|
|
||||||
|
use matches::assert_matches;
|
||||||
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_string() {
|
||||||
|
assert_eq!(to_json_value(&Action::Notify).unwrap(), json!("notify"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_tweak_sound() {
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&Action::SetTweak(Tweak::Sound("default".into()))).unwrap(),
|
||||||
|
json!({ "set_tweak": "sound", "value": "default" })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_tweak_highlight() {
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&Action::SetTweak(Tweak::Highlight(true))).unwrap(),
|
||||||
|
json!({ "set_tweak": "highlight" })
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&Action::SetTweak(Tweak::Highlight(false))).unwrap(),
|
||||||
|
json!({ "set_tweak": "highlight", "value": false })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_string() {
|
||||||
|
assert_matches!(from_json_value::<Action>(json!("notify")).unwrap(), Action::Notify);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_tweak_sound() {
|
||||||
|
let json_data = json!({
|
||||||
|
"set_tweak": "sound",
|
||||||
|
"value": "default"
|
||||||
|
});
|
||||||
|
assert_matches!(
|
||||||
|
&from_json_value::<Action>(json_data).unwrap(),
|
||||||
|
Action::SetTweak(Tweak::Sound(value)) if value == "default"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_tweak_highlight() {
|
||||||
|
let json_data = json!({
|
||||||
|
"set_tweak": "highlight",
|
||||||
|
"value": true
|
||||||
|
});
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<Action>(json_data).unwrap(),
|
||||||
|
Action::SetTweak(Tweak::Highlight(true))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_tweak_highlight_with_default_value() {
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<Action>(json!({ "set_tweak": "highlight" })).unwrap(),
|
||||||
|
Action::SetTweak(Tweak::Highlight(true))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
154
ruma-common/src/push/condition.rs
Normal file
154
ruma-common/src/push/condition.rs
Normal file
@ -0,0 +1,154 @@
|
|||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
mod room_member_count_is;
|
||||||
|
|
||||||
|
pub use room_member_count_is::{ComparisonOperator, RoomMemberCountIs};
|
||||||
|
|
||||||
|
/// A condition that must apply for an associated push rule's action to be taken.
|
||||||
|
#[derive(Clone, Debug, Deserialize, Serialize)]
|
||||||
|
#[non_exhaustive]
|
||||||
|
#[serde(tag = "kind", rename_all = "snake_case")]
|
||||||
|
pub enum PushCondition {
|
||||||
|
/// This is a glob pattern match on a field of the event.
|
||||||
|
EventMatch {
|
||||||
|
/// The dot-separated field of the event to match.
|
||||||
|
key: String,
|
||||||
|
|
||||||
|
/// The glob-style pattern to match against.
|
||||||
|
///
|
||||||
|
/// Patterns with no special glob characters should be treated as having asterisks prepended
|
||||||
|
/// and appended when testing the condition.
|
||||||
|
pattern: String,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// This matches unencrypted messages where `content.body` contains the owner's display name in
|
||||||
|
/// that room.
|
||||||
|
ContainsDisplayName,
|
||||||
|
|
||||||
|
/// This matches the current number of members in the room.
|
||||||
|
RoomMemberCount {
|
||||||
|
/// The condition on the current number of members in the room.
|
||||||
|
is: RoomMemberCountIs,
|
||||||
|
},
|
||||||
|
|
||||||
|
/// This takes into account the current power levels in the room, ensuring the sender of the
|
||||||
|
/// event has high enough power to trigger the notification.
|
||||||
|
SenderNotificationPermission {
|
||||||
|
/// The field in the power level event the user needs a minimum power level for.
|
||||||
|
///
|
||||||
|
/// Fields must be specified under the `notifications` property in the power level event's
|
||||||
|
/// `content`.
|
||||||
|
key: String,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use js_int::uint;
|
||||||
|
use matches::assert_matches;
|
||||||
|
use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
|
||||||
|
|
||||||
|
use super::{PushCondition, RoomMemberCountIs};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_event_match_condition() {
|
||||||
|
let json_data = json!({
|
||||||
|
"key": "content.msgtype",
|
||||||
|
"kind": "event_match",
|
||||||
|
"pattern": "m.notice"
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&PushCondition::EventMatch {
|
||||||
|
key: "content.msgtype".into(),
|
||||||
|
pattern: "m.notice".into(),
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
json_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_contains_display_name_condition() {
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&PushCondition::ContainsDisplayName).unwrap(),
|
||||||
|
json!({ "kind": "contains_display_name" })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_room_member_count_condition() {
|
||||||
|
let json_data = json!({
|
||||||
|
"is": "2",
|
||||||
|
"kind": "room_member_count"
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
to_json_value(&PushCondition::RoomMemberCount {
|
||||||
|
is: RoomMemberCountIs::from(uint!(2))
|
||||||
|
})
|
||||||
|
.unwrap(),
|
||||||
|
json_data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn serialize_sender_notification_permission_condition() {
|
||||||
|
let json_data = json!({
|
||||||
|
"key": "room",
|
||||||
|
"kind": "sender_notification_permission"
|
||||||
|
});
|
||||||
|
assert_eq!(
|
||||||
|
json_data,
|
||||||
|
to_json_value(&PushCondition::SenderNotificationPermission { key: "room".into() })
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_event_match_condition() {
|
||||||
|
let json_data = json!({
|
||||||
|
"key": "content.msgtype",
|
||||||
|
"kind": "event_match",
|
||||||
|
"pattern": "m.notice"
|
||||||
|
});
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<PushCondition>(json_data).unwrap(),
|
||||||
|
PushCondition::EventMatch { key, pattern }
|
||||||
|
if key == "content.msgtype" && pattern == "m.notice"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_contains_display_name_condition() {
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<PushCondition>(json!({ "kind": "contains_display_name" })).unwrap(),
|
||||||
|
PushCondition::ContainsDisplayName
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_room_member_count_condition() {
|
||||||
|
let json_data = json!({
|
||||||
|
"is": "2",
|
||||||
|
"kind": "room_member_count"
|
||||||
|
});
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<PushCondition>(json_data).unwrap(),
|
||||||
|
PushCondition::RoomMemberCount { is }
|
||||||
|
if is == RoomMemberCountIs::from(uint!(2))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn deserialize_sender_notification_permission_condition() {
|
||||||
|
let json_data = json!({
|
||||||
|
"key": "room",
|
||||||
|
"kind": "sender_notification_permission"
|
||||||
|
});
|
||||||
|
assert_matches!(
|
||||||
|
from_json_value::<PushCondition>(json_data).unwrap(),
|
||||||
|
PushCondition::SenderNotificationPermission {
|
||||||
|
key
|
||||||
|
} if key == "room"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,52 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::value::RawValue as RawJsonValue;
|
|
||||||
|
|
||||||
/// Values for the `set_tweak` action.
|
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum Tweak {
|
|
||||||
Sound(SoundTweak),
|
|
||||||
Highlight(HighlightTweak),
|
|
||||||
Custom {
|
|
||||||
#[serde(rename = "set_tweak")]
|
|
||||||
name: String,
|
|
||||||
value: Box<RawJsonValue>,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "set_tweak", rename = "sound")]
|
|
||||||
pub struct SoundTweak {
|
|
||||||
value: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
|
|
||||||
#[serde(tag = "set_tweak", rename = "highlight")]
|
|
||||||
pub struct HighlightTweak {
|
|
||||||
#[serde(default = "ruma_serde::default_true", skip_serializing_if = "ruma_serde::is_true")]
|
|
||||||
value: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<super::Tweak> for Tweak {
|
|
||||||
fn from(tweak: super::Tweak) -> Self {
|
|
||||||
use super::Tweak::*;
|
|
||||||
|
|
||||||
match tweak {
|
|
||||||
Sound(value) => Self::Sound(SoundTweak { value }),
|
|
||||||
Highlight(value) => Self::Highlight(HighlightTweak { value }),
|
|
||||||
Custom { name, value } => Self::Custom { name, value },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<Tweak> for super::Tweak {
|
|
||||||
fn from(tweak: Tweak) -> Self {
|
|
||||||
use Tweak::*;
|
|
||||||
|
|
||||||
match tweak {
|
|
||||||
Sound(SoundTweak { value }) => Self::Sound(value),
|
|
||||||
Highlight(HighlightTweak { value }) => Self::Highlight(value),
|
|
||||||
Custom { name, value } => Self::Custom { name, value },
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user