ruma_common/push/
action.rs

1use std::collections::BTreeMap;
2
3use as_variant::as_variant;
4use serde::{Deserialize, Deserializer, Serialize, Serializer};
5use serde_json::value::{RawValue as RawJsonValue, Value as JsonValue};
6
7use crate::serde::from_raw_json_value;
8
9/// This represents the different actions that should be taken when a rule is matched, and
10/// controls how notifications are delivered to the client.
11///
12/// See [the spec](https://spec.matrix.org/latest/client-server-api/#actions) for details.
13#[derive(Clone, Debug)]
14#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
15pub enum Action {
16    /// Causes matching events to generate a notification.
17    Notify,
18
19    /// Sets an entry in the 'tweaks' dictionary sent to the push gateway.
20    SetTweak(Tweak),
21
22    /// An unknown action.
23    #[doc(hidden)]
24    _Custom(CustomAction),
25}
26
27impl Action {
28    /// Whether this action is an `Action::SetTweak(Tweak::Highlight(true))`.
29    pub fn is_highlight(&self) -> bool {
30        matches!(self, Action::SetTweak(Tweak::Highlight(true)))
31    }
32
33    /// Whether this action should trigger a notification.
34    pub fn should_notify(&self) -> bool {
35        matches!(self, Action::Notify)
36    }
37
38    /// The sound that should be played with this action, if any.
39    pub fn sound(&self) -> Option<&str> {
40        as_variant!(self, Action::SetTweak(Tweak::Sound(sound)) => sound)
41    }
42}
43
44/// The `set_tweak` action.
45#[derive(Clone, Debug, Deserialize, Serialize)]
46#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
47#[serde(from = "tweak_serde::Tweak", into = "tweak_serde::Tweak")]
48pub enum Tweak {
49    /// A string representing the sound to be played when this notification arrives.
50    ///
51    /// A value of "default" means to play a default sound. A device may choose to alert the user
52    /// by some other means if appropriate, eg. vibration.
53    Sound(String),
54
55    /// A boolean representing whether or not this message should be highlighted in the UI.
56    ///
57    /// This will normally take the form of presenting the message in a different color and/or
58    /// style. The UI might also be adjusted to draw particular attention to the room in which the
59    /// event occurred. If a `highlight` tweak is given with no value, its value is defined to be
60    /// `true`. If no highlight tweak is given at all then the value of `highlight` is defined to
61    /// be `false`.
62    Highlight(#[serde(default = "crate::serde::default_true")] bool),
63
64    /// A custom tweak
65    Custom {
66        /// The name of the custom tweak (`set_tweak` field)
67        name: String,
68
69        /// The value of the custom tweak
70        value: Box<RawJsonValue>,
71    },
72}
73
74impl<'de> Deserialize<'de> for Action {
75    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
76    where
77        D: Deserializer<'de>,
78    {
79        let json = Box::<RawJsonValue>::deserialize(deserializer)?;
80        let custom: CustomAction = from_raw_json_value(&json)?;
81
82        match &custom {
83            CustomAction::String(s) => match s.as_str() {
84                "notify" => Ok(Action::Notify),
85                _ => Ok(Action::_Custom(custom)),
86            },
87            CustomAction::Object(o) => {
88                if o.get("set_tweak").is_some() {
89                    Ok(Action::SetTweak(from_raw_json_value(&json)?))
90                } else {
91                    Ok(Action::_Custom(custom))
92                }
93            }
94        }
95    }
96}
97
98impl Serialize for Action {
99    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
100    where
101        S: Serializer,
102    {
103        match self {
104            Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
105            Action::SetTweak(kind) => kind.serialize(serializer),
106            Action::_Custom(custom) => custom.serialize(serializer),
107        }
108    }
109}
110
111/// An unknown action.
112#[doc(hidden)]
113#[allow(unknown_lints, unnameable_types)]
114#[derive(Debug, Clone, Serialize, Deserialize)]
115#[serde(untagged)]
116pub enum CustomAction {
117    /// A string.
118    String(String),
119
120    /// An object.
121    Object(BTreeMap<String, JsonValue>),
122}
123
124mod tweak_serde {
125    use serde::{Deserialize, Serialize};
126    use serde_json::value::RawValue as RawJsonValue;
127
128    /// Values for the `set_tweak` action.
129    #[derive(Clone, Deserialize, Serialize)]
130    #[serde(untagged)]
131    pub(crate) enum Tweak {
132        Sound(SoundTweak),
133        Highlight(HighlightTweak),
134        Custom {
135            #[serde(rename = "set_tweak")]
136            name: String,
137            value: Box<RawJsonValue>,
138        },
139    }
140
141    #[derive(Clone, PartialEq, Deserialize, Serialize)]
142    #[serde(tag = "set_tweak", rename = "sound")]
143    pub(crate) struct SoundTweak {
144        value: String,
145    }
146
147    #[derive(Clone, PartialEq, Deserialize, Serialize)]
148    #[serde(tag = "set_tweak", rename = "highlight")]
149    pub(crate) struct HighlightTweak {
150        #[serde(
151            default = "crate::serde::default_true",
152            skip_serializing_if = "crate::serde::is_true"
153        )]
154        value: bool,
155    }
156
157    impl From<super::Tweak> for Tweak {
158        fn from(tweak: super::Tweak) -> Self {
159            use super::Tweak::*;
160
161            match tweak {
162                Sound(value) => Self::Sound(SoundTweak { value }),
163                Highlight(value) => Self::Highlight(HighlightTweak { value }),
164                Custom { name, value } => Self::Custom { name, value },
165            }
166        }
167    }
168
169    impl From<Tweak> for super::Tweak {
170        fn from(tweak: Tweak) -> Self {
171            use Tweak::*;
172
173            match tweak {
174                Sound(SoundTweak { value }) => Self::Sound(value),
175                Highlight(HighlightTweak { value }) => Self::Highlight(value),
176                Custom { name, value } => Self::Custom { name, value },
177            }
178        }
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use assert_matches2::assert_matches;
185    use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
186
187    use super::{Action, Tweak};
188
189    #[test]
190    fn serialize_string() {
191        assert_eq!(to_json_value(Action::Notify).unwrap(), json!("notify"));
192    }
193
194    #[test]
195    fn serialize_tweak_sound() {
196        assert_eq!(
197            to_json_value(Action::SetTweak(Tweak::Sound("default".into()))).unwrap(),
198            json!({ "set_tweak": "sound", "value": "default" })
199        );
200    }
201
202    #[test]
203    fn serialize_tweak_highlight() {
204        assert_eq!(
205            to_json_value(Action::SetTweak(Tweak::Highlight(true))).unwrap(),
206            json!({ "set_tweak": "highlight" })
207        );
208
209        assert_eq!(
210            to_json_value(Action::SetTweak(Tweak::Highlight(false))).unwrap(),
211            json!({ "set_tweak": "highlight", "value": false })
212        );
213    }
214
215    #[test]
216    fn deserialize_string() {
217        assert_matches!(from_json_value::<Action>(json!("notify")), Ok(Action::Notify));
218    }
219
220    #[test]
221    fn deserialize_tweak_sound() {
222        let json_data = json!({
223            "set_tweak": "sound",
224            "value": "default"
225        });
226        assert_matches!(
227            from_json_value::<Action>(json_data),
228            Ok(Action::SetTweak(Tweak::Sound(value)))
229        );
230        assert_eq!(value, "default");
231    }
232
233    #[test]
234    fn deserialize_tweak_highlight() {
235        let json_data = json!({
236            "set_tweak": "highlight",
237            "value": true
238        });
239        assert_matches!(
240            from_json_value::<Action>(json_data),
241            Ok(Action::SetTweak(Tweak::Highlight(true)))
242        );
243    }
244
245    #[test]
246    fn deserialize_tweak_highlight_with_default_value() {
247        assert_matches!(
248            from_json_value::<Action>(json!({ "set_tweak": "highlight" })),
249            Ok(Action::SetTweak(Tweak::Highlight(true)))
250        );
251    }
252}