ruma-push-gateway-api-0.6.0/.cargo_vcs_info.json0000644000000001720000000000100151660ustar { "git": { "sha1": "5cfc4874e28ac7650bb197f724b357506d95f9df" }, "path_in_vcs": "crates/ruma-push-gateway-api" }ruma-push-gateway-api-0.6.0/CHANGELOG.md000064400000000000000000000011261046102023000155670ustar 00000000000000# [unreleased] # 0.6.0 Breaking changes: * Remove `PartialEq` implementation for `NotificationCounts` # 0.5.0 Breaking changes: * Upgrade dependencies # 0.4.0 Breaking changes: * Upgrade dependencies # 0.3.0 Breaking changes: * Upgrade dependencies # 0.2.0 Breaking changes: * Upgrade ruma-events to 0.23.0 # 0.1.0 Breaking changes: * Remove `Copy` implementation for `NotificationCounts` to avoid simple changes being breaking * Change `Box` to `&RawJsonValue` in request types * Upgrade public dependencies # 0.0.1 * Add endpoint `send_event_notification::v1` ruma-push-gateway-api-0.6.0/Cargo.toml0000644000000023030000000000100131620ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.60" name = "ruma-push-gateway-api" version = "0.6.0" description = "Types for the endpoints in the Matrix push gateway API." homepage = "https://www.ruma.io/" readme = "README.md" keywords = [ "matrix", "chat", "messaging", "ruma", ] license = "MIT" repository = "https://github.com/ruma/ruma" [package.metadata.docs.rs] all-features = true [dependencies.js_int] version = "0.2.0" features = ["serde"] [dependencies.ruma-common] version = "0.10.0" features = [ "api", "events", ] [dependencies.serde] version = "1.0.118" features = ["derive"] [dependencies.serde_json] version = "1.0.61" [features] client = [] server = [] unstable-exhaustive-types = [] unstable-pre-spec = [] ruma-push-gateway-api-0.6.0/Cargo.toml.orig000064400000000000000000000013101046102023000166400ustar 00000000000000[package] name = "ruma-push-gateway-api" version = "0.6.0" description = "Types for the endpoints in the Matrix push gateway API." homepage = "https://www.ruma.io/" keywords = ["matrix", "chat", "messaging", "ruma"] license = "MIT" readme = "README.md" repository = "https://github.com/ruma/ruma" edition = "2021" rust-version = "1.60" [package.metadata.docs.rs] all-features = true [features] unstable-exhaustive-types = [] unstable-pre-spec = [] client = [] server = [] [dependencies] js_int = { version = "0.2.0", features = ["serde"] } ruma-common = { version = "0.10.0", path = "../ruma-common", features = ["api", "events"] } serde = { version = "1.0.118", features = ["derive"] } serde_json = "1.0.61" ruma-push-gateway-api-0.6.0/README.md000064400000000000000000000007731046102023000152440ustar 00000000000000# ruma-push-gateway-api [![crates.io page](https://img.shields.io/crates/v/ruma-push-gateway-api.svg)](https://crates.io/crates/ruma-push-gateway-api) [![docs.rs page](https://docs.rs/ruma-push-gateway-api/badge.svg)](https://docs.rs/ruma-push-gateway-api/) ![license: MIT](https://img.shields.io/crates/l/ruma-push-gateway-api.svg) **ruma-push-gateway-api** contains serializable types for the requests and responses for each endpoint in the [Matrix](https://matrix.org/) push gateway API specification. ruma-push-gateway-api-0.6.0/src/lib.rs000064400000000000000000000015031046102023000156600ustar 00000000000000#![doc(html_favicon_url = "https://www.ruma.io/favicon.ico")] #![doc(html_logo_url = "https://www.ruma.io/images/logo.png")] //! (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://spec.matrix.org/v1.2/push-gateway-api/ #![warn(missing_docs)] use std::fmt; pub mod send_event_notification; // Wrapper around `Box` that cannot be used in a meaningful way outside of // this crate. Used for string enums because their `_Custom` variant can't be // truly private (only `#[doc(hidden)]`). #[doc(hidden)] #[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct PrivOwnedStr(Box); impl fmt::Debug for PrivOwnedStr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.0.fmt(f) } } ruma-push-gateway-api-0.6.0/src/send_event_notification.rs000064400000000000000000000416051046102023000220210ustar 00000000000000//! `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 { //! `/v1/` ([spec]) //! //! [spec]: https://spec.matrix.org/v1.2/push-gateway-api/#post_matrixpushv1notify use js_int::{uint, UInt}; use ruma_common::{ api::ruma_api, events::RoomEventType, push::{PushFormat, Tweak}, serde::{Incoming, StringEnum}, EventId, RoomAliasId, RoomId, SecondsSinceUnixEpoch, UserId, }; use serde::{Deserialize, Serialize}; use serde_json::value::RawValue as RawJsonValue; #[cfg(feature = "unstable-pre-spec")] use serde_json::value::Value as JsonValue; 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, Incoming, 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 RoomEventType>, /// 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 str>, /// 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_common::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_common::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 = "NotificationCounts::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 build this with a custom value, convert it from a /// string with `::from() / .into()`. To check for values 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 Default for NotificationPriority { fn default() -> Self { Self::High } } /// Type for passing information about notification counts. #[derive(Clone, Debug, Default, Deserialize, Serialize)] #[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_common::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_common::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 } } fn is_default(&self) -> bool { self.unread == uint!(0) && self.missed_calls == uint!(0) } } /// Type for passing information about devices. #[derive(Clone, Debug, Deserialize, Incoming, 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. #[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(), } } } /// Information for the pusher implementation itself. /// /// 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()`. #[derive(Clone, Debug, Default, Serialize, Deserialize)] #[cfg_attr(not(feature = "unstable-exhaustive-types"), non_exhaustive)] pub struct PusherData { /// The format to use when sending notifications to the Push Gateway. #[serde(skip_serializing_if = "Option::is_none")] pub format: Option, /// iOS (+ macOS?) specific default payload that will be sent to apple push notification /// service. /// /// For more information, see [Sygnal docs][sygnal]. /// /// [sygnal]: https://github.com/matrix-org/sygnal/blob/main/docs/applications.md#ios-applications-beware // Not specified, issue: https://github.com/matrix-org/matrix-spec/issues/921 #[cfg(feature = "unstable-pre-spec")] #[serde(default, skip_serializing_if = "JsonValue::is_null")] 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-pre-spec"))] { self.format.is_none() } #[cfg(feature = "unstable-pre-spec")] { self.format.is_none() && self.default_payload.is_null() } } } impl From for PusherData { fn from(data: ruma_common::push::PusherData) -> Self { let ruma_common::push::PusherData { format, #[cfg(feature = "unstable-pre-spec")] default_payload, .. } = data; Self { format, #[cfg(feature = "unstable-pre-spec")] default_payload, } } } 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::{ event_id, events::RoomEventType, room_alias_id, room_id, user_id, SecondsSinceUnixEpoch, }; 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(&RoomEventType::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()); } } }