From 2bd5c131f49b2239750c39ed63b623cd5a01c965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C3=A9vin=20Commaille?= Date: Fri, 4 Nov 2022 12:12:31 +0100 Subject: [PATCH] client-api: Make PusherKind contain the pusher data Since it depends on the kind --- crates/ruma-client-api/CHANGELOG.md | 1 + crates/ruma-client-api/src/push.rs | 63 ++++--- .../ruma-client-api/src/push/pusher_serde.rs | 173 ++++++++++++++++++ crates/ruma-client-api/src/push/set_pusher.rs | 8 +- ...er_action_serde.rs => set_pusher_serde.rs} | 35 +++- crates/ruma-common/CHANGELOG.md | 1 + crates/ruma-common/src/push.rs | 35 ++-- .../src/send_event_notification.rs | 9 +- 8 files changed, 258 insertions(+), 67 deletions(-) create mode 100644 crates/ruma-client-api/src/push/pusher_serde.rs rename crates/ruma-client-api/src/push/set_pusher/{pusher_action_serde.rs => set_pusher_serde.rs} (83%) diff --git a/crates/ruma-client-api/CHANGELOG.md b/crates/ruma-client-api/CHANGELOG.md index e8368da0..d4f7f356 100644 --- a/crates/ruma-client-api/CHANGELOG.md +++ b/crates/ruma-client-api/CHANGELOG.md @@ -10,6 +10,7 @@ Breaking changes: * Make `push::set_pusher::v3::Request` use an enum to differentiate when deleting a pusher * Move `push::get_pushers::v3::Pusher` to `push` and make it use the new `PusherIds` type * Remove `push::set_pusher::v3::Pusher` and use the common type instead +* Make `push::PusherKind` contain the pusher's `data` Improvements: diff --git a/crates/ruma-client-api/src/push.rs b/crates/ruma-client-api/src/push.rs index fe7831dc..de17b615 100644 --- a/crates/ruma-client-api/src/push.rs +++ b/crates/ruma-client-api/src/push.rs @@ -3,10 +3,10 @@ use std::{error::Error, fmt}; use ruma_common::{ push::{ - Action, ConditionalPushRule, ConditionalPushRuleInit, PatternedPushRule, - PatternedPushRuleInit, PushCondition, PusherData, SimplePushRule, SimplePushRuleInit, + Action, ConditionalPushRule, ConditionalPushRuleInit, HttpPusherData, PatternedPushRule, + PatternedPushRuleInit, PushCondition, SimplePushRule, SimplePushRuleInit, }, - serde::StringEnum, + serde::{JsonObject, StringEnum}, }; use serde::{Deserialize, Serialize}; @@ -20,6 +20,7 @@ pub mod get_pushrule_actions; pub mod get_pushrule_enabled; pub mod get_pushrules_all; pub mod get_pushrules_global_scope; +mod pusher_serde; pub mod set_pusher; pub mod set_pushrule; pub mod set_pushrule_actions; @@ -189,34 +190,33 @@ pub enum RuleKind { _Custom(PrivOwnedStr), } -/// Which kind a pusher is. -#[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))] -#[derive(Clone, Debug, PartialEq, Eq, StringEnum)] -#[ruma_enum(rename_all = "snake_case")] +/// Which kind a pusher is, and the information for that kind. +#[derive(Clone, Debug)] #[non_exhaustive] pub enum PusherKind { /// A pusher that sends HTTP pokes. - Http, + Http(HttpPusherData), /// A pusher that emails the user with unread notifications. - Email, + Email(EmailPusherData), #[doc(hidden)] - _Custom(PrivOwnedStr), + _Custom(CustomPusherData), } /// Defines a pusher. /// /// To create an instance of this type, first create a `PusherInit` and convert it via /// `Pusher::from` / `.into()`. -#[derive(Clone, Debug, Serialize, Deserialize)] +#[derive(Clone, Debug, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct Pusher { /// Identifiers for this pusher. #[serde(flatten)] pub ids: PusherIds, - /// The kind of the pusher. + /// The kind of the pusher and the information for that kind. + #[serde(flatten)] pub kind: PusherKind, /// A string that will allow the user to identify what application owns this pusher. @@ -231,9 +231,6 @@ pub struct Pusher { /// The preferred language for receiving notifications (e.g. 'en' or 'en-US') pub lang: String, - - /// Information for the pusher implementation itself. - pub data: PusherData, } /// Initial set of fields of `Pusher`. @@ -260,23 +257,13 @@ pub struct PusherInit { /// The preferred language for receiving notifications (e.g. 'en' or 'en-US'). pub lang: String, - - /// Information for the pusher implementation itself. - pub data: PusherData, } impl From for Pusher { fn from(init: PusherInit) -> Self { - let PusherInit { - ids, - kind, - app_display_name, - device_display_name, - profile_tag, - lang, - data, - } = init; - Self { ids, kind, app_display_name, device_display_name, profile_tag, lang, data } + let PusherInit { ids, kind, app_display_name, device_display_name, profile_tag, lang } = + init; + Self { ids, kind, app_display_name, device_display_name, profile_tag, lang } } } @@ -301,3 +288,23 @@ impl PusherIds { Self { pushkey, app_id } } } + +/// Information for an email pusher. +#[derive(Clone, Debug, Default)] +#[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] +pub struct EmailPusherData; + +impl EmailPusherData { + /// Creates a new empty `EmailPusherData`. + pub fn new() -> Self { + Self::default() + } +} + +#[doc(hidden)] +#[derive(Clone, Debug, Deserialize)] +#[non_exhaustive] +pub struct CustomPusherData { + kind: String, + data: JsonObject, +} diff --git a/crates/ruma-client-api/src/push/pusher_serde.rs b/crates/ruma-client-api/src/push/pusher_serde.rs new file mode 100644 index 00000000..507224ca --- /dev/null +++ b/crates/ruma-client-api/src/push/pusher_serde.rs @@ -0,0 +1,173 @@ +use ruma_common::serde::{from_raw_json_value, JsonObject}; +use serde::{de, ser::SerializeStruct, Deserialize, Serialize}; +use serde_json::value::RawValue as RawJsonValue; + +use super::{EmailPusherData, Pusher, PusherIds, PusherKind}; + +#[derive(Debug, Deserialize)] +struct PusherDeHelper { + #[serde(flatten)] + ids: PusherIds, + app_display_name: String, + device_display_name: String, + profile_tag: Option, + lang: String, +} + +impl<'de> Deserialize<'de> for Pusher { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + + let PusherDeHelper { ids, app_display_name, device_display_name, profile_tag, lang } = + from_raw_json_value(&json)?; + let kind = from_raw_json_value(&json)?; + + Ok(Self { ids, kind, app_display_name, device_display_name, profile_tag, lang }) + } +} + +impl Serialize for PusherKind { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut st = serializer.serialize_struct("PusherAction", 3)?; + + match self { + PusherKind::Http(data) => { + st.serialize_field("kind", &"http")?; + st.serialize_field("data", data)?; + } + PusherKind::Email(_) => { + st.serialize_field("kind", &"email")?; + st.serialize_field("data", &JsonObject::new())?; + } + PusherKind::_Custom(custom) => { + st.serialize_field("kind", &custom.kind)?; + st.serialize_field("data", &custom.data)?; + } + } + + st.end() + } +} + +#[derive(Debug, Deserialize)] +struct PusherKindDeHelper { + kind: String, + data: Box, +} + +impl<'de> Deserialize<'de> for PusherKind { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + let PusherKindDeHelper { kind, data } = from_raw_json_value(&json)?; + + match kind.as_ref() { + "http" => from_raw_json_value(&data).map(Self::Http), + "email" => Ok(Self::Email(EmailPusherData)), + _ => from_raw_json_value(&json).map(Self::_Custom), + } + } +} + +#[cfg(test)] +mod tests { + use assert_matches::assert_matches; + use ruma_common::{push::HttpPusherData, serde::JsonObject}; + use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; + + use crate::push::{CustomPusherData, EmailPusherData, PusherKind}; + + #[test] + fn serialize_email() { + let action = PusherKind::Email(EmailPusherData::new()); + + assert_eq!( + to_json_value(action).unwrap(), + json!({ + "kind": "email", + "data": {}, + }) + ); + } + + #[test] + fn serialize_http() { + let action = PusherKind::Http(HttpPusherData::new("http://localhost".to_owned())); + + assert_eq!( + to_json_value(action).unwrap(), + json!({ + "kind": "http", + "data": { + "url": "http://localhost", + }, + }) + ); + } + + #[test] + fn serialize_custom() { + let action = PusherKind::_Custom(CustomPusherData { + kind: "my.custom.kind".to_owned(), + data: JsonObject::new(), + }); + + assert_eq!( + to_json_value(action).unwrap(), + json!({ + "kind": "my.custom.kind", + "data": {} + }) + ); + } + + #[test] + fn deserialize_email() { + let json = json!({ + "kind": "email", + "data": {}, + }); + + assert_matches!(from_json_value(json).unwrap(), PusherKind::Email(_)); + } + + #[test] + fn deserialize_http() { + let json = json!({ + "kind": "http", + "data": { + "url": "http://localhost", + }, + }); + + let data = assert_matches!( + from_json_value(json).unwrap(), + PusherKind::Http(data) => data + ); + + assert_eq!(data.url, "http://localhost"); + assert_eq!(data.format, None); + } + + #[test] + fn deserialize_custom() { + let json = json!({ + "kind": "my.custom.kind", + "data": {} + }); + + let custom = + assert_matches!(from_json_value(json).unwrap(), PusherKind::_Custom(custom) => custom); + + assert_eq!(custom.kind, "my.custom.kind"); + assert!(custom.data.is_empty()); + } +} diff --git a/crates/ruma-client-api/src/push/set_pusher.rs b/crates/ruma-client-api/src/push/set_pusher.rs index 4f157664..fe567db4 100644 --- a/crates/ruma-client-api/src/push/set_pusher.rs +++ b/crates/ruma-client-api/src/push/set_pusher.rs @@ -1,6 +1,6 @@ //! `POST /_matrix/client/*/pushers/set` -mod pusher_action_serde; +mod set_pusher_serde; pub mod v3 { //! `/v3/` ([spec]) @@ -8,7 +8,7 @@ pub mod v3 { //! [spec]: https://spec.matrix.org/v1.4/client-server-api/#post_matrixclientv3pushersset use ruma_common::api::ruma_api; - use serde::{Deserialize, Serialize}; + use serde::Serialize; use crate::push::{Pusher, PusherIds}; @@ -72,7 +72,7 @@ pub mod v3 { } /// Data necessary to create or update a pusher. - #[derive(Clone, Debug, Serialize, Deserialize)] + #[derive(Clone, Debug, Serialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct PusherPostData { /// The pusher to configure. @@ -83,7 +83,7 @@ pub mod v3 { /// are already others for other users. /// /// Defaults to `false`. See the spec for more details. - #[serde(default, skip_serializing_if = "ruma_common::serde::is_default")] + #[serde(skip_serializing_if = "ruma_common::serde::is_default")] pub append: bool, } } diff --git a/crates/ruma-client-api/src/push/set_pusher/pusher_action_serde.rs b/crates/ruma-client-api/src/push/set_pusher/set_pusher_serde.rs similarity index 83% rename from crates/ruma-client-api/src/push/set_pusher/pusher_action_serde.rs rename to crates/ruma-client-api/src/push/set_pusher/set_pusher_serde.rs index ccc0c26c..289cbef7 100644 --- a/crates/ruma-client-api/src/push/set_pusher/pusher_action_serde.rs +++ b/crates/ruma-client-api/src/push/set_pusher/set_pusher_serde.rs @@ -3,9 +3,27 @@ use ruma_common::serde::from_raw_json_value; use serde::{de, ser::SerializeStruct, Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; -use crate::push::PusherKind; +use super::v3::{PusherAction, PusherPostData}; -use super::v3::PusherAction; +#[derive(Debug, Deserialize)] +struct PusherPostDataDeHelper { + #[serde(default)] + append: bool, +} + +impl<'de> Deserialize<'de> for PusherPostData { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + let json = Box::::deserialize(deserializer)?; + + let PusherPostDataDeHelper { append } = from_raw_json_value(&json)?; + let pusher = from_raw_json_value(&json)?; + + Ok(Self { pusher, append }) + } +} impl Serialize for PusherAction { fn serialize(&self, serializer: S) -> Result @@ -27,7 +45,7 @@ impl Serialize for PusherAction { #[derive(Debug, Deserialize)] struct PusherActionDeHelper { - kind: JsOption, + kind: JsOption, } impl<'de> Deserialize<'de> for PusherAction { @@ -50,23 +68,23 @@ impl<'de> Deserialize<'de> for PusherAction { #[cfg(test)] mod tests { use assert_matches::assert_matches; - use ruma_common::push::PusherData; use serde_json::{from_value as from_json_value, json, to_value as to_json_value}; use super::PusherAction; - use crate::push::{set_pusher::v3::PusherPostData, Pusher, PusherIds, PusherKind}; + use crate::push::{ + set_pusher::v3::PusherPostData, EmailPusherData, Pusher, PusherIds, PusherKind, + }; #[test] fn serialize_post() { let action = PusherAction::Post(PusherPostData { pusher: Pusher { ids: PusherIds::new("abcdef".to_owned(), "my.matrix.app".to_owned()), - kind: PusherKind::Email, + kind: PusherKind::Email(EmailPusherData::new()), app_display_name: "My Matrix App".to_owned(), device_display_name: "My Phone".to_owned(), profile_tag: None, lang: "en".to_owned(), - data: PusherData::new(), }, append: false, }); @@ -122,12 +140,11 @@ mod tests { let pusher = post_data.pusher; assert_eq!(pusher.ids.pushkey, "abcdef"); assert_eq!(pusher.ids.app_id, "my.matrix.app"); - assert_eq!(pusher.kind, PusherKind::Email); + assert_matches!(pusher.kind, PusherKind::Email(_)); assert_eq!(pusher.app_display_name, "My Matrix App"); assert_eq!(pusher.device_display_name, "My Phone"); assert_eq!(pusher.profile_tag, None); assert_eq!(pusher.lang, "en"); - assert!(pusher.data.is_empty()); } #[test] diff --git a/crates/ruma-common/CHANGELOG.md b/crates/ruma-common/CHANGELOG.md index ff9edb11..239fc87c 100644 --- a/crates/ruma-common/CHANGELOG.md +++ b/crates/ruma-common/CHANGELOG.md @@ -19,6 +19,7 @@ Breaking changes: the module calling the macro. * Make `name` optional on `SecretStorageKeyEventContent`. Default constructor has been adjusted as well to not require this field. +* Rename `push::PusherData` to `HttpPusherData` and make the `url` field required Improvements: diff --git a/crates/ruma-common/src/push.rs b/crates/ruma-common/src/push.rs index 639f0f9b..04d8e52f 100644 --- a/crates/ruma-common/src/push.rs +++ b/crates/ruma-common/src/push.rs @@ -401,15 +401,14 @@ impl Equivalent for str { } } -/// Information for the pusher implementation itself. -#[derive(Clone, Debug, Default, Serialize, Deserialize)] +/// Information for a pusher using the Push Gateway API. +#[derive(Clone, Debug, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] -pub struct PusherData { +pub struct HttpPusherData { /// The URL to use to send notifications to. /// /// Required if the pusher's kind is http. - #[serde(skip_serializing_if = "Option::is_none")] - pub url: Option, + pub url: String, /// The format to use when sending notifications to the Push Gateway. #[serde(skip_serializing_if = "Option::is_none")] @@ -427,28 +426,20 @@ pub struct PusherData { pub default_payload: JsonValue, } -impl PusherData { - /// Creates an empty `PusherData`. - pub fn new() -> Self { - Default::default() - } - - /// Returns `true` if all fields are `None`. - pub fn is_empty(&self) -> bool { - #[cfg(not(feature = "unstable-unspecified"))] - { - self.url.is_none() && self.format.is_none() - } - - #[cfg(feature = "unstable-unspecified")] - { - self.url.is_none() && self.format.is_none() && self.default_payload.is_null() +impl HttpPusherData { + /// Creates a new `HttpPusherData` with the given URL. + pub fn new(url: String) -> Self { + Self { + url, + format: None, + #[cfg(feature = "unstable-unspecified")] + default_payload: JsonValue::default(), } } } /// A special format that the homeserver should use when sending notifications to a Push Gateway. -/// Currently, only "event_id_only" is supported as of [Push Gateway API r0.1.1][spec]. +/// Currently, only `event_id_only` is supported, see the [Push Gateway API][spec]. /// /// [spec]: https://spec.matrix.org/v1.4/push-gateway-api/#homeserver-behaviour #[doc = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/doc/string_enum.md"))] 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 a7f3d803..6bf74374 100644 --- a/crates/ruma-push-gateway-api/src/send_event_notification.rs +++ b/crates/ruma-push-gateway-api/src/send_event_notification.rs @@ -236,7 +236,8 @@ pub mod v1 { /// /// This is the data dictionary passed in at pusher creation minus the `url` key. /// - /// It can be constructed from [`ruma_common::push::PusherData`] with `::from()` / `.into()`. + /// It can be constructed from [`ruma_common::push::HttpPusherData`] with `::from()` / + /// `.into()`. #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct PusherData { @@ -276,9 +277,9 @@ pub mod v1 { } } - impl From for PusherData { - fn from(data: ruma_common::push::PusherData) -> Self { - let ruma_common::push::PusherData { + impl From for PusherData { + fn from(data: ruma_common::push::HttpPusherData) -> Self { + let ruma_common::push::HttpPusherData { format, #[cfg(feature = "unstable-unspecified")] default_payload,