diff --git a/crates/ruma-push-gateway-api/src/lib.rs b/crates/ruma-push-gateway-api/src/lib.rs index dc329f4e..b1b47b3c 100644 --- a/crates/ruma-push-gateway-api/src/lib.rs +++ b/crates/ruma-push-gateway-api/src/lib.rs @@ -3,7 +3,7 @@ //! (De)serializable types for the [Matrix Push Gateway API][push-api]. //! These types can be shared by push gateway and server code. //! -//! [push-api]: https://matrix.org/docs/spec/push_gateway/r0.1.1.html +//! [push-api]: https://spec.matrix.org/v1.2/push-gateway-api/ #![warn(missing_docs)] diff --git a/crates/ruma-push-gateway-api/src/send_event_notification.rs b/crates/ruma-push-gateway-api/src/send_event_notification.rs index 671eb224..df2a6f22 100644 --- a/crates/ruma-push-gateway-api/src/send_event_notification.rs +++ b/crates/ruma-push-gateway-api/src/send_event_notification.rs @@ -1,4 +1,402 @@ +//! `POST /_matrix/push/*/notify` +//! //! Endpoint to notify a push gateway about an event or update the number of unread notifications a //! user has. -pub mod v1; +pub mod v1 { + //! `/v1/` ([spec]) + //! + //! [spec]: https://spec.matrix.org/v1.2/push-gateway-api/#post_matrixpushv1notify + + use js_int::UInt; + use ruma_api::ruma_api; + use ruma_common::{ + push::{PusherData, Tweak}, + SecondsSinceUnixEpoch, + }; + use ruma_events::EventType; + use ruma_identifiers::{EventId, RoomAliasId, RoomId, RoomName, UserId}; + use ruma_serde::{Outgoing, StringEnum}; + use serde::{Deserialize, Serialize}; + use serde_json::value::RawValue as RawJsonValue; + + use crate::PrivOwnedStr; + + ruma_api! { + metadata: { + description: "Notify a push gateway about an event or update the number of unread notifications a user has", + name: "send_event_notification", + method: POST, + stable_path: "/_matrix/push/v1/notify", + rate_limited: false, + authentication: None, + added: 1.0, + } + + request: { + /// Information about the push notification + pub notification: Notification<'a>, + } + + #[derive(Default)] + response: { + /// A list of all pushkeys given in the notification request that are not valid. + /// + /// These could have been rejected by an upstream gateway because they have expired or have + /// never been valid. Homeservers must cease sending notification requests for these + /// pushkeys and remove the associated pushers. It may not necessarily be the notification + /// in the request that failed: it could be that a previous notification to the same pushkey + /// failed. May be empty. + pub rejected: Vec, + } + } + + impl<'a> Request<'a> { + /// Creates a new `Request` with the given notification. + pub fn new(notification: Notification<'a>) -> Self { + Self { notification } + } + } + + impl Response { + /// Creates a new `Response` with the given list of rejected pushkeys. + pub fn new(rejected: Vec) -> Self { + Self { rejected } + } + } + + /// Type for passing information about a push notification + #[derive(Clone, Debug, Default, Outgoing, Serialize)] + #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] + pub struct Notification<'a> { + /// The Matrix event ID of the event being notified about. + /// + /// Required if the notification is about a particular Matrix event. May be omitted for + /// notifications that only contain updated badge counts. This ID can and should be used to + /// detect duplicate notification requests. + #[serde(skip_serializing_if = "Option::is_none")] + pub event_id: Option<&'a EventId>, + + /// The ID of the room in which this event occurred. + /// + /// Required if the notification relates to a specific Matrix event. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_id: Option<&'a RoomId>, + + /// The type of the event as in the event's `type` field. + #[serde(rename = "type", skip_serializing_if = "Option::is_none")] + pub event_type: Option<&'a EventType>, + + /// The sender of the event as in the corresponding event field. + #[serde(skip_serializing_if = "Option::is_none")] + pub sender: Option<&'a UserId>, + + /// The current display name of the sender in the room in which the event occurred. + #[serde(skip_serializing_if = "Option::is_none")] + pub sender_display_name: Option<&'a str>, + + /// The name of the room in which the event occurred. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_name: Option<&'a RoomName>, + + /// An alias to display for the room in which the event occurred. + #[serde(skip_serializing_if = "Option::is_none")] + pub room_alias: Option<&'a RoomAliasId>, + + /// Whether the user receiving the notification is the subject of a member event (i.e. the + /// `state_key` of the member event is equal to the user's Matrix ID). + #[serde(default, skip_serializing_if = "ruma_serde::is_default")] + pub user_is_target: bool, + + /// The priority of the notification. + /// + /// If omitted, `high` is assumed. This may be used by push gateways to deliver less + /// time-sensitive notifications in a way that will preserve battery power on mobile + /// devices. + #[serde(default, skip_serializing_if = "ruma_serde::is_default")] + pub prio: NotificationPriority, + + /// The `content` field from the event, if present. + /// + /// The pusher may omit this if the event had no content or for any other reason. + #[serde(skip_serializing_if = "Option::is_none")] + pub content: Option<&'a RawJsonValue>, + + /// Current number of unacknowledged communications for the recipient user. + /// + /// Counts whose value is zero should be omitted. + #[serde(default, skip_serializing_if = "ruma_serde::is_default")] + pub counts: NotificationCounts, + + /// An array of devices that the notification should be sent to. + pub devices: &'a [Device], + } + + impl<'a> Notification<'a> { + /// Create a new notification for the given devices. + pub fn new(devices: &'a [Device]) -> Self { + Notification { devices, ..Default::default() } + } + } + + /// Type for passing information about notification priority. + /// + /// This may be used by push gateways to deliver less time-sensitive + /// notifications in a way that will preserve battery power on mobile devices. + /// + /// This type can hold an arbitrary string. To check for formats that are not available as a + /// documented variant here, use its string representation, obtained through `.as_str()`. + #[derive(Clone, Debug, PartialEq, Eq, StringEnum)] + #[ruma_enum(rename_all = "snake_case")] + #[non_exhaustive] + pub enum NotificationPriority { + /// A high priority notification + High, + + /// A low priority notification + Low, + + #[doc(hidden)] + _Custom(PrivOwnedStr), + } + + impl NotificationPriority { + /// Creates a string slice from this `NotificationPriority`. + pub fn as_str(&self) -> &str { + self.as_ref() + } + } + + impl Default for NotificationPriority { + fn default() -> Self { + Self::High + } + } + + /// Type for passing information about notification counts. + #[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)] + #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] + pub struct NotificationCounts { + /// The number of unread messages a user has across all of the rooms they + /// are a member of. + #[serde(default, skip_serializing_if = "ruma_serde::is_default")] + pub unread: UInt, + + /// The number of unacknowledged missed calls a user has across all rooms of + /// which they are a member. + #[serde(default, skip_serializing_if = "ruma_serde::is_default")] + pub missed_calls: UInt, + } + + impl NotificationCounts { + /// Create new notification counts from the given unread and missed call + /// counts. + pub fn new(unread: UInt, missed_calls: UInt) -> Self { + NotificationCounts { unread, missed_calls } + } + } + + /// Type for passing information about devices. + #[derive(Clone, Debug, Deserialize, Outgoing, Serialize)] + #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] + pub struct Device { + /// The `app_id` given when the pusher was created. + /// + /// Max length: 64 chars. + pub app_id: String, + + /// The `pushkey` given when the pusher was created. + /// + /// Max length: 512 bytes. + pub pushkey: String, + + /// The unix timestamp (in seconds) when the pushkey was last updated. + #[serde(skip_serializing_if = "Option::is_none")] + pub pushkey_ts: Option, + + /// A dictionary of additional pusher-specific data. + /// + /// For 'http' pushers, this is the data dictionary passed in at pusher creation minus the + /// `url` key. + #[serde(default, skip_serializing_if = "PusherData::is_empty")] + pub data: PusherData, + + /// A dictionary of customisations made to the way this notification is to be presented. + /// + /// These are added by push rules. + #[serde(with = "tweak_serde", skip_serializing_if = "Vec::is_empty")] + pub tweaks: Vec, + } + + impl Device { + /// Create a new device with the given app id and pushkey + pub fn new(app_id: String, pushkey: String) -> Self { + Device { + app_id, + pushkey, + pushkey_ts: None, + data: PusherData::new(), + tweaks: Vec::new(), + } + } + } + + mod tweak_serde { + use std::fmt; + + use ruma_common::push::Tweak; + use serde::{ + de::{MapAccess, Visitor}, + ser::SerializeMap, + Deserializer, Serializer, + }; + + pub fn serialize(tweak: &[Tweak], serializer: S) -> Result + where + S: Serializer, + { + let mut map = serializer.serialize_map(Some(tweak.len()))?; + for item in tweak { + #[allow(unreachable_patterns)] + match item { + Tweak::Highlight(b) => map.serialize_entry("highlight", b)?, + Tweak::Sound(value) => map.serialize_entry("sound", value)?, + Tweak::Custom { value, name } => map.serialize_entry(name, value)?, + _ => unreachable!("variant added to Tweak not covered by Custom"), + } + } + map.end() + } + + struct TweaksVisitor; + + impl<'de> Visitor<'de> for TweaksVisitor { + type Value = Vec; + + fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { + formatter.write_str("List of tweaks") + } + + fn visit_map(self, mut access: M) -> Result + where + M: MapAccess<'de>, + { + let mut tweaks = vec![]; + while let Some(key) = access.next_key::()? { + match &*key { + "sound" => tweaks.push(Tweak::Sound(access.next_value()?)), + // If a highlight tweak is given with no value, its value is defined to be + // true. + "highlight" => { + let highlight = if let Ok(highlight) = access.next_value() { + highlight + } else { + true + }; + + tweaks.push(Tweak::Highlight(highlight)); + } + _ => tweaks.push(Tweak::Custom { name: key, value: access.next_value()? }), + }; + } + + // If no highlight tweak is given at all then the value of highlight is defined to + // be false. + if !tweaks.iter().any(|tw| matches!(tw, Tweak::Highlight(_))) { + tweaks.push(Tweak::Highlight(false)); + } + + Ok(tweaks) + } + } + + pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + deserializer.deserialize_map(TweaksVisitor) + } + } + + #[cfg(test)] + mod tests { + use js_int::uint; + use ruma_common::SecondsSinceUnixEpoch; + use ruma_events::EventType; + use ruma_identifiers::{event_id, room_alias_id, room_id, user_id}; + use serde_json::{ + from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, + }; + + use super::{Device, Notification, NotificationCounts, NotificationPriority, Tweak}; + + #[test] + fn serialize_request() { + let expected = json!({ + "event_id": "$3957tyerfgewrf384", + "room_id": "!slw48wfj34rtnrf:example.com", + "type": "m.room.message", + "sender": "@exampleuser:matrix.org", + "sender_display_name": "Major Tom", + "room_alias": "#exampleroom:matrix.org", + "prio": "low", + "content": {}, + "counts": { + "unread": 2, + }, + "devices": [ + { + "app_id": "org.matrix.matrixConsole.ios", + "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/", + "pushkey_ts": 123, + "tweaks": { + "sound": "silence", + "highlight": true, + "custom": "go wild" + } + } + ] + }); + + let eid = event_id!("$3957tyerfgewrf384"); + let rid = room_id!("!slw48wfj34rtnrf:example.com"); + let uid = user_id!("@exampleuser:matrix.org"); + let alias = room_alias_id!("#exampleroom:matrix.org"); + + let count = NotificationCounts { unread: uint!(2), ..NotificationCounts::default() }; + + let device = Device { + pushkey_ts: Some(SecondsSinceUnixEpoch(uint!(123))), + tweaks: vec![ + Tweak::Highlight(true), + Tweak::Sound("silence".into()), + Tweak::Custom { + name: "custom".into(), + value: from_json_value(JsonValue::String("go wild".into())).unwrap(), + }, + ], + ..Device::new( + "org.matrix.matrixConsole.ios".into(), + "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/".into(), + ) + }; + let devices = &[device]; + + let notice = Notification { + event_id: Some(eid), + room_id: Some(rid), + event_type: Some(&EventType::RoomMessage), + sender: Some(uid), + sender_display_name: Some("Major Tom"), + room_alias: Some(alias), + content: Some(serde_json::from_str("{}").unwrap()), + counts: count, + prio: NotificationPriority::Low, + devices, + ..Notification::default() + }; + + assert_eq!(expected, to_json_value(notice).unwrap()) + } + } +} diff --git a/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs b/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs deleted file mode 100644 index b5495add..00000000 --- a/crates/ruma-push-gateway-api/src/send_event_notification/v1.rs +++ /dev/null @@ -1,382 +0,0 @@ -//! [POST /_matrix/push/v1/notify](https://matrix.org/docs/spec/push_gateway/r0.1.1#post-matrix-push-v1-notify) - -use js_int::UInt; -use ruma_api::ruma_api; -use ruma_common::{ - push::{PusherData, Tweak}, - SecondsSinceUnixEpoch, -}; -use ruma_events::EventType; -use ruma_identifiers::{EventId, RoomAliasId, RoomId, RoomName, UserId}; -use ruma_serde::{Outgoing, StringEnum}; -use serde::{Deserialize, Serialize}; -use serde_json::value::RawValue as RawJsonValue; - -use crate::PrivOwnedStr; - -ruma_api! { - metadata: { - description: "Notify a push gateway about an event or update the number of unread notifications a user has", - name: "send_event_notification", - method: POST, - stable_path: "/_matrix/push/v1/notify", - rate_limited: false, - authentication: None, - added: 1.0, - } - - request: { - /// Information about the push notification - pub notification: Notification<'a>, - } - - #[derive(Default)] - response: { - /// A list of all pushkeys given in the notification request that are not valid. - /// - /// These could have been rejected by an upstream gateway because they have expired or have - /// never been valid. Homeservers must cease sending notification requests for these - /// pushkeys and remove the associated pushers. It may not necessarily be the notification - /// in the request that failed: it could be that a previous notification to the same pushkey - /// failed. May be empty. - pub rejected: Vec, - } -} - -impl<'a> Request<'a> { - /// Creates a new `Request` with the given notification. - pub fn new(notification: Notification<'a>) -> Self { - Self { notification } - } -} - -impl Response { - /// Creates a new `Response` with the given list of rejected pushkeys. - pub fn new(rejected: Vec) -> Self { - Self { rejected } - } -} - -/// Type for passing information about a push notification -#[derive(Clone, Debug, Default, Outgoing, Serialize)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct Notification<'a> { - /// The Matrix event ID of the event being notified about. - /// - /// Required if the notification is about a particular Matrix event. May be omitted for - /// notifications that only contain updated badge counts. This ID can and should be used to - /// detect duplicate notification requests. - #[serde(skip_serializing_if = "Option::is_none")] - pub event_id: Option<&'a EventId>, - - /// The ID of the room in which this event occurred. - /// - /// Required if the notification relates to a specific Matrix event. - #[serde(skip_serializing_if = "Option::is_none")] - pub room_id: Option<&'a RoomId>, - - /// The type of the event as in the event's `type` field. - #[serde(rename = "type", skip_serializing_if = "Option::is_none")] - pub event_type: Option<&'a EventType>, - - /// The sender of the event as in the corresponding event field. - #[serde(skip_serializing_if = "Option::is_none")] - pub sender: Option<&'a UserId>, - - /// The current display name of the sender in the room in which the event occurred. - #[serde(skip_serializing_if = "Option::is_none")] - pub sender_display_name: Option<&'a str>, - - /// The name of the room in which the event occurred. - #[serde(skip_serializing_if = "Option::is_none")] - pub room_name: Option<&'a RoomName>, - - /// An alias to display for the room in which the event occurred. - #[serde(skip_serializing_if = "Option::is_none")] - pub room_alias: Option<&'a RoomAliasId>, - - /// Whether the user receiving the notification is the subject of a member event (i.e. the - /// `state_key` of the member event is equal to the user's Matrix ID). - #[serde(default, skip_serializing_if = "ruma_serde::is_default")] - pub user_is_target: bool, - - /// The priority of the notification. - /// - /// If omitted, `high` is assumed. This may be used by push gateways to deliver less - /// time-sensitive notifications in a way that will preserve battery power on mobile devices. - #[serde(default, skip_serializing_if = "ruma_serde::is_default")] - pub prio: NotificationPriority, - - /// The `content` field from the event, if present. - /// - /// The pusher may omit this if the event had no content or for any other reason. - #[serde(skip_serializing_if = "Option::is_none")] - pub content: Option<&'a RawJsonValue>, - - /// Current number of unacknowledged communications for the recipient user. - /// - /// Counts whose value is zero should be omitted. - #[serde(default, skip_serializing_if = "ruma_serde::is_default")] - pub counts: NotificationCounts, - - /// An array of devices that the notification should be sent to. - pub devices: &'a [Device], -} - -impl<'a> Notification<'a> { - /// Create a new notification for the given devices. - pub fn new(devices: &'a [Device]) -> Self { - Notification { devices, ..Default::default() } - } -} - -/// Type for passing information about notification priority. -/// -/// This may be used by push gateways to deliver less time-sensitive -/// notifications in a way that will preserve battery power on mobile devices. -/// -/// This type can hold an arbitrary string. To check for formats that are not available as a -/// documented variant here, use its string representation, obtained through `.as_str()`. -#[derive(Clone, Debug, PartialEq, Eq, StringEnum)] -#[ruma_enum(rename_all = "snake_case")] -#[non_exhaustive] -pub enum NotificationPriority { - /// A high priority notification - High, - - /// A low priority notification - Low, - - #[doc(hidden)] - _Custom(PrivOwnedStr), -} - -impl NotificationPriority { - /// Creates a string slice from this `NotificationPriority`. - pub fn as_str(&self) -> &str { - self.as_ref() - } -} - -impl Default for NotificationPriority { - fn default() -> Self { - Self::High - } -} - -/// Type for passing information about notification counts. -#[derive(Clone, Debug, Default, Deserialize, Serialize, PartialEq)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct NotificationCounts { - /// The number of unread messages a user has across all of the rooms they - /// are a member of. - #[serde(default, skip_serializing_if = "ruma_serde::is_default")] - pub unread: UInt, - - /// The number of unacknowledged missed calls a user has across all rooms of - /// which they are a member. - #[serde(default, skip_serializing_if = "ruma_serde::is_default")] - pub missed_calls: UInt, -} - -impl NotificationCounts { - /// Create new notification counts from the given unread and missed call - /// counts. - pub fn new(unread: UInt, missed_calls: UInt) -> Self { - NotificationCounts { unread, missed_calls } - } -} - -/// Type for passing information about devices. -#[derive(Clone, Debug, Deserialize, Outgoing, Serialize)] -#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct Device { - /// The `app_id` given when the pusher was created. - /// - /// Max length: 64 chars. - pub app_id: String, - - /// The `pushkey` given when the pusher was created. - /// - /// Max length: 512 bytes. - pub pushkey: String, - - /// The unix timestamp (in seconds) when the pushkey was last updated. - #[serde(skip_serializing_if = "Option::is_none")] - pub pushkey_ts: Option, - - /// A dictionary of additional pusher-specific data. - /// - /// For 'http' pushers, this is the data dictionary passed in at pusher creation minus the - /// `url` key. - #[serde(default, skip_serializing_if = "PusherData::is_empty")] - pub data: PusherData, - - /// A dictionary of customisations made to the way this notification is to be presented. - /// - /// These are added by push rules. - #[serde(with = "tweak_serde", skip_serializing_if = "Vec::is_empty")] - pub tweaks: Vec, -} - -impl Device { - /// Create a new device with the given app id and pushkey - pub fn new(app_id: String, pushkey: String) -> Self { - Device { app_id, pushkey, pushkey_ts: None, data: PusherData::new(), tweaks: Vec::new() } - } -} - -mod tweak_serde { - use std::fmt; - - use ruma_common::push::Tweak; - use serde::{ - de::{MapAccess, Visitor}, - ser::SerializeMap, - Deserializer, Serializer, - }; - - pub fn serialize(tweak: &[Tweak], serializer: S) -> Result - where - S: Serializer, - { - let mut map = serializer.serialize_map(Some(tweak.len()))?; - for item in tweak { - #[allow(unreachable_patterns)] - match item { - Tweak::Highlight(b) => map.serialize_entry("highlight", b)?, - Tweak::Sound(value) => map.serialize_entry("sound", value)?, - Tweak::Custom { value, name } => map.serialize_entry(name, value)?, - _ => unreachable!("variant added to Tweak not covered by Custom"), - } - } - map.end() - } - - struct TweaksVisitor; - - impl<'de> Visitor<'de> for TweaksVisitor { - type Value = Vec; - - fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result { - formatter.write_str("List of tweaks") - } - - fn visit_map(self, mut access: M) -> Result - where - M: MapAccess<'de>, - { - let mut tweaks = vec![]; - while let Some(key) = access.next_key::()? { - match &*key { - "sound" => tweaks.push(Tweak::Sound(access.next_value()?)), - // If a highlight tweak is given with no value, its value is defined to be true. - "highlight" => { - let highlight = - if let Ok(highlight) = access.next_value() { highlight } else { true }; - - tweaks.push(Tweak::Highlight(highlight)); - } - _ => tweaks.push(Tweak::Custom { name: key, value: access.next_value()? }), - }; - } - - // If no highlight tweak is given at all then the value of highlight is defined to be - // false. - if !tweaks.iter().any(|tw| matches!(tw, Tweak::Highlight(_))) { - tweaks.push(Tweak::Highlight(false)); - } - - Ok(tweaks) - } - } - - pub fn deserialize<'de, D>(deserializer: D) -> Result, D::Error> - where - D: Deserializer<'de>, - { - deserializer.deserialize_map(TweaksVisitor) - } -} - -#[cfg(test)] -mod tests { - use js_int::uint; - use ruma_common::SecondsSinceUnixEpoch; - use ruma_events::EventType; - use ruma_identifiers::{event_id, room_alias_id, room_id, user_id}; - use serde_json::{ - from_value as from_json_value, json, to_value as to_json_value, Value as JsonValue, - }; - - use super::{Device, Notification, NotificationCounts, NotificationPriority, Tweak}; - - #[test] - fn serialize_request() { - let expected = json!({ - "event_id": "$3957tyerfgewrf384", - "room_id": "!slw48wfj34rtnrf:example.com", - "type": "m.room.message", - "sender": "@exampleuser:matrix.org", - "sender_display_name": "Major Tom", - "room_alias": "#exampleroom:matrix.org", - "prio": "low", - "content": {}, - "counts": { - "unread": 2, - }, - "devices": [ - { - "app_id": "org.matrix.matrixConsole.ios", - "pushkey": "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/", - "pushkey_ts": 123, - "tweaks": { - "sound": "silence", - "highlight": true, - "custom": "go wild" - } - } - ] - }); - - let eid = event_id!("$3957tyerfgewrf384"); - let rid = room_id!("!slw48wfj34rtnrf:example.com"); - let uid = user_id!("@exampleuser:matrix.org"); - let alias = room_alias_id!("#exampleroom:matrix.org"); - - let count = NotificationCounts { unread: uint!(2), ..NotificationCounts::default() }; - - let device = Device { - pushkey_ts: Some(SecondsSinceUnixEpoch(uint!(123))), - tweaks: vec![ - Tweak::Highlight(true), - Tweak::Sound("silence".into()), - Tweak::Custom { - name: "custom".into(), - value: from_json_value(JsonValue::String("go wild".into())).unwrap(), - }, - ], - ..Device::new( - "org.matrix.matrixConsole.ios".into(), - "V2h5IG9uIGVhcnRoIGRpZCB5b3UgZGVjb2RlIHRoaXM/".into(), - ) - }; - let devices = &[device]; - - let notice = Notification { - event_id: Some(eid), - room_id: Some(rid), - event_type: Some(&EventType::RoomMessage), - sender: Some(uid), - sender_display_name: Some("Major Tom"), - room_alias: Some(alias), - content: Some(serde_json::from_str("{}").unwrap()), - counts: count, - prio: NotificationPriority::Low, - devices, - ..Notification::default() - }; - - assert_eq!(expected, to_json_value(notice).unwrap()) - } -}