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#[derive(Clone, Debug)]
14#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
15pub enum Action {
16 Notify,
18
19 #[cfg(feature = "unstable-msc3768")]
22 NotifyInApp,
23
24 SetTweak(Tweak),
26
27 #[doc(hidden)]
29 _Custom(CustomAction),
30}
31
32impl Action {
33 pub fn is_highlight(&self) -> bool {
35 matches!(self, Action::SetTweak(Tweak::Highlight(true)))
36 }
37
38 pub fn should_notify(&self) -> bool {
40 match self {
41 Action::Notify => true,
42 #[cfg(feature = "unstable-msc3768")]
43 Action::NotifyInApp => true,
44 _ => false,
45 }
46 }
47
48 #[cfg(feature = "unstable-msc3768")]
50 pub fn should_notify_remote(&self) -> bool {
51 matches!(self, Action::Notify)
52 }
53
54 pub fn sound(&self) -> Option<&str> {
56 as_variant!(self, Action::SetTweak(Tweak::Sound(sound)) => sound)
57 }
58}
59
60#[derive(Clone, Debug, Deserialize, Serialize)]
62#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
63#[serde(from = "tweak_serde::Tweak", into = "tweak_serde::Tweak")]
64pub enum Tweak {
65 Sound(String),
70
71 Highlight(#[serde(default = "crate::serde::default_true")] bool),
79
80 Custom {
82 name: String,
84
85 value: Box<RawJsonValue>,
87 },
88}
89
90impl<'de> Deserialize<'de> for Action {
91 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
92 where
93 D: Deserializer<'de>,
94 {
95 let json = Box::<RawJsonValue>::deserialize(deserializer)?;
96 let custom: CustomAction = from_raw_json_value(&json)?;
97
98 match &custom {
99 CustomAction::String(s) => match s.as_str() {
100 "notify" => Ok(Action::Notify),
101 #[cfg(feature = "unstable-msc3768")]
102 "org.matrix.msc3768.notify_in_app" => Ok(Action::NotifyInApp),
103 _ => Ok(Action::_Custom(custom)),
104 },
105 CustomAction::Object(o) => {
106 if o.get("set_tweak").is_some() {
107 Ok(Action::SetTweak(from_raw_json_value(&json)?))
108 } else {
109 Ok(Action::_Custom(custom))
110 }
111 }
112 }
113 }
114}
115
116impl Serialize for Action {
117 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
118 where
119 S: Serializer,
120 {
121 match self {
122 Action::Notify => serializer.serialize_unit_variant("Action", 0, "notify"),
123 #[cfg(feature = "unstable-msc3768")]
124 Action::NotifyInApp => {
125 serializer.serialize_unit_variant("Action", 0, "org.matrix.msc3768.notify_in_app")
126 }
127 Action::SetTweak(kind) => kind.serialize(serializer),
128 Action::_Custom(custom) => custom.serialize(serializer),
129 }
130 }
131}
132
133#[doc(hidden)]
135#[allow(unknown_lints, unnameable_types)]
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(untagged)]
138pub enum CustomAction {
139 String(String),
141
142 Object(BTreeMap<String, JsonValue>),
144}
145
146mod tweak_serde {
147 use serde::{Deserialize, Serialize};
148 use serde_json::value::RawValue as RawJsonValue;
149
150 #[derive(Clone, Deserialize, Serialize)]
152 #[serde(untagged)]
153 pub(crate) enum Tweak {
154 Sound(SoundTweak),
155 Highlight(HighlightTweak),
156 Custom {
157 #[serde(rename = "set_tweak")]
158 name: String,
159 value: Box<RawJsonValue>,
160 },
161 }
162
163 #[derive(Clone, PartialEq, Deserialize, Serialize)]
164 #[serde(tag = "set_tweak", rename = "sound")]
165 pub(crate) struct SoundTweak {
166 value: String,
167 }
168
169 #[derive(Clone, PartialEq, Deserialize, Serialize)]
170 #[serde(tag = "set_tweak", rename = "highlight")]
171 pub(crate) struct HighlightTweak {
172 #[serde(
173 default = "crate::serde::default_true",
174 skip_serializing_if = "crate::serde::is_true"
175 )]
176 value: bool,
177 }
178
179 impl From<super::Tweak> for Tweak {
180 fn from(tweak: super::Tweak) -> Self {
181 use super::Tweak::*;
182
183 match tweak {
184 Sound(value) => Self::Sound(SoundTweak { value }),
185 Highlight(value) => Self::Highlight(HighlightTweak { value }),
186 Custom { name, value } => Self::Custom { name, value },
187 }
188 }
189 }
190
191 impl From<Tweak> for super::Tweak {
192 fn from(tweak: Tweak) -> Self {
193 use Tweak::*;
194
195 match tweak {
196 Sound(SoundTweak { value }) => Self::Sound(value),
197 Highlight(HighlightTweak { value }) => Self::Highlight(value),
198 Custom { name, value } => Self::Custom { name, value },
199 }
200 }
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use assert_matches2::assert_matches;
207 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
208
209 use super::{Action, Tweak};
210
211 #[test]
212 fn serialize_notify() {
213 assert_eq!(to_json_value(Action::Notify).unwrap(), json!("notify"));
214 }
215
216 #[cfg(feature = "unstable-msc3768")]
217 #[test]
218 fn serialize_notify_in_app() {
219 assert_eq!(
220 to_json_value(Action::NotifyInApp).unwrap(),
221 json!("org.matrix.msc3768.notify_in_app")
222 );
223 }
224
225 #[test]
226 fn serialize_tweak_sound() {
227 assert_eq!(
228 to_json_value(Action::SetTweak(Tweak::Sound("default".into()))).unwrap(),
229 json!({ "set_tweak": "sound", "value": "default" })
230 );
231 }
232
233 #[test]
234 fn serialize_tweak_highlight() {
235 assert_eq!(
236 to_json_value(Action::SetTweak(Tweak::Highlight(true))).unwrap(),
237 json!({ "set_tweak": "highlight" })
238 );
239
240 assert_eq!(
241 to_json_value(Action::SetTweak(Tweak::Highlight(false))).unwrap(),
242 json!({ "set_tweak": "highlight", "value": false })
243 );
244 }
245
246 #[test]
247 fn deserialize_notify() {
248 assert_matches!(from_json_value::<Action>(json!("notify")), Ok(Action::Notify));
249 }
250
251 #[cfg(feature = "unstable-msc3768")]
252 #[test]
253 fn deserialize_notify_in_app() {
254 assert_matches!(
255 from_json_value::<Action>(json!("org.matrix.msc3768.notify_in_app")),
256 Ok(Action::NotifyInApp)
257 );
258 }
259
260 #[test]
261 fn deserialize_tweak_sound() {
262 let json_data = json!({
263 "set_tweak": "sound",
264 "value": "default"
265 });
266 assert_matches!(
267 from_json_value::<Action>(json_data),
268 Ok(Action::SetTweak(Tweak::Sound(value)))
269 );
270 assert_eq!(value, "default");
271 }
272
273 #[test]
274 fn deserialize_tweak_highlight() {
275 let json_data = json!({
276 "set_tweak": "highlight",
277 "value": true
278 });
279 assert_matches!(
280 from_json_value::<Action>(json_data),
281 Ok(Action::SetTweak(Tweak::Highlight(true)))
282 );
283 }
284
285 #[test]
286 fn deserialize_tweak_highlight_with_default_value() {
287 assert_matches!(
288 from_json_value::<Action>(json!({ "set_tweak": "highlight" })),
289 Ok(Action::SetTweak(Tweak::Highlight(true)))
290 );
291 }
292}