From c22510e9a20a274f46776553e465469ae7136ea5 Mon Sep 17 00:00:00 2001 From: Jacob Lee Date: Mon, 14 Nov 2022 16:39:47 -0800 Subject: [PATCH] Fix `serde_with` usage and update sound support (#2) --- Cargo.toml | 2 +- src/apns/mod.rs | 33 +++++++++++++++++++++++------ src/apns/request.rs | 50 +++++++++++++++++++++++++++++--------------- src/apns/response.rs | 6 +++--- src/result.rs | 3 +++ 5 files changed, 67 insertions(+), 27 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 8ded347e..0faeab0d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,7 +12,7 @@ reqwest-middleware = "0.1.6" serde = { version = "1.0.147", features = ["derive"] } serde_json = "1.0.87" serde_repr = "0.1.9" -serde_with = "2.0.1" +serde_with = { version = "2.0.1", features = ["time_0_3"] } thiserror = "1.0.37" time = { version = "0.3.16", features = ["serde"] } url = "2.3.1" diff --git a/src/apns/mod.rs b/src/apns/mod.rs index a0545c5c..652079fe 100644 --- a/src/apns/mod.rs +++ b/src/apns/mod.rs @@ -164,7 +164,7 @@ pub struct ApnsRequest { /// /// Specify 1 to prioritize the device’s power considerations over all other /// factors for delivery, and prevent awakening the device. - pub apns_priority: Option, + pub apns_priority: ApnsPriority, /// The topic for the notification. In general, the topic is your app’s /// bundle ID/app ID. It can have a suffix based on the type of push @@ -213,14 +213,14 @@ pub struct ApnsRequest { /// specify the value `1` and don’t include the `alert`, `badge`, or `sound` /// keys in your payload. See [Pushing Background Updates to Your /// App](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app). - pub content_available: Option, + pub content_available: bool, /// The notification service app extension flag. If the value is `1`, the /// system passes the notification to your notification service app /// extension before delivery. Use your extension to modify the /// notification’s content. See [Modifying Content in Newly Delivered /// Notifications](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications). - pub mutable_content: Option, + pub mutable_content: bool, /// The identifier of the window brought forward. The value of this key will /// be populated on the @@ -269,8 +269,8 @@ where let _ = headers.insert(APNS_EXPIRATION.clone(), apns_expiration); } - if let Some(apns_priority) = this.apns_priority { - let _ = headers.insert(APNS_PRIORITY.clone(), apns_priority.into()); + if this.apns_priority != ApnsPriority::default() { + let _ = headers.insert(APNS_PRIORITY.clone(), this.apns_priority.into()); } if let Some(apns_topic) = this.apns_topic { @@ -283,10 +283,31 @@ where let _ = headers.insert(APNS_COLLAPSE_ID.clone(), apns_collapse_id); } + let is_critical = this + .interruption_level + .as_ref() + .map(|il| *il == InterruptionLevel::Critical) + .unwrap_or_default(); + + let is_critical_sound = this + .sound + .as_ref() + .map(|sound| sound.critical) + .unwrap_or_default(); + + if is_critical != is_critical_sound { + return Err(Error::CriticalSound); + } + + let sound = this.sound.map(|mut sound| { + sound.critical = is_critical || is_critical_sound; + sound.into() + }); + let payload = ApnsPayload { alert: this.alert.map(Into::into), badge: this.badge, - sound: this.sound.map(Into::into), + sound, thread_id: this.thread_id, category: this.category, content_available: this.content_available, diff --git a/src/apns/request.rs b/src/apns/request.rs index c66c60f2..56fe6c7e 100644 --- a/src/apns/request.rs +++ b/src/apns/request.rs @@ -1,15 +1,19 @@ use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, skip_serializing_none}; +use serde_with::{serde_as, skip_serializing_none, BoolFromInt}; + +fn is_false(v: &bool) -> bool { + !v +} /// Put the JSON payload with the notification’s content into the body of your /// request. The JSON payload must not be compressed and is limited to a maximum /// size of 4 KB (4096 bytes). For a Voice over Internet Protocol (VoIP) /// notification, the maximum size is 5 KB (5120 bytes). -#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)] -#[serde(rename_all = "kebab-case")] #[serde_as] #[skip_serializing_none] -pub struct ApnsPayload +#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)] +#[serde(rename_all = "kebab-case")] +pub struct ApnsPayload where T: Serialize, { @@ -42,16 +46,18 @@ where /// specify the value `1` and don’t include the `alert`, `badge`, or `sound` /// keys in your payload. See [Pushing Background Updates to Your /// App](https://developer.apple.com/documentation/usernotifications/setting_up_a_remote_notification_server/pushing_background_updates_to_your_app). + #[serde(skip_serializing_if = "is_false")] #[serde_as(as = "BoolFromInt")] - pub content_available: Option, + pub content_available: bool, /// The notification service app extension flag. If the value is `1`, the /// system passes the notification to your notification service app /// extension before delivery. Use your extension to modify the /// notification’s content. See [Modifying Content in Newly Delivered /// Notifications](https://developer.apple.com/documentation/usernotifications/modifying_content_in_newly_delivered_notifications). + #[serde(skip_serializing_if = "is_false")] #[serde_as(as = "BoolFromInt")] - pub mutable_content: Option, + pub mutable_content: bool, /// The identifier of the window brought forward. The value of this key will /// be populated on the @@ -169,7 +175,7 @@ impl From for ApnsAlert { } } -#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] +#[derive(Debug, PartialEq, Deserialize, Serialize)] #[serde(untagged)] pub enum ApnsSound { /// The name of a sound file in your app’s main bundle or in the @@ -180,37 +186,47 @@ pub enum ApnsSound { /// [`UNNotificationSound`](https://developer.apple.com/documentation/usernotifications/unnotificationsound). Name(String), - Sound(Sound), + Critical(Sound), } -#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] #[serde_as] #[skip_serializing_none] +#[derive(Debug, PartialEq, Deserialize, Serialize)] pub struct Sound { /// The critical alert flag. Set to `1` to enable the critical alert. + #[serde(skip_serializing_if = "is_false")] #[serde_as(as = "BoolFromInt")] - critical: Option, + pub critical: bool, /// The name of a sound file in your app’s main bundle or in the /// `Library/Sounds` folder of your app’s container directory. Specify /// the string `default` to play the system sound. For information about /// how to prepare sounds, see /// [`UNNotificationSound`](https://developer.apple.com/documentation/usernotifications/unnotificationsound). - name: Option, + pub name: String, /// The volume for the critical alert’s sound. Set this to a value /// between `0` (silent) and `1` (full volume). - volume: Option, + pub volume: f64, +} + +impl Default for Sound { + fn default() -> Self { + Self { + critical: false, + name: "default".into(), + volume: 1f64, + } + } } impl From for ApnsSound { fn from(this: Sound) -> Self { - if this.critical.is_none() && this.volume.is_none() { - if let Some(name) = this.name { - return ApnsSound::Name(name); - } + if !this.critical { + ApnsSound::Name(this.name) + } else { + ApnsSound::Critical(this) } - ApnsSound::Sound(this) } } diff --git a/src/apns/response.rs b/src/apns/response.rs index edcff767..b0c3446d 100644 --- a/src/apns/response.rs +++ b/src/apns/response.rs @@ -1,13 +1,13 @@ use http::StatusCode; use serde::{Deserialize, Serialize}; -use serde_with::{serde_as, skip_serializing_none}; +use serde_with::{serde_as, skip_serializing_none, TimestampMilliSeconds}; use time::OffsetDateTime; use crate::result::Error; -#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)] #[serde_as] #[skip_serializing_none] +#[derive(Debug, Default, PartialEq, Eq, Deserialize, Serialize)] pub struct ApnsResponse { /// The error code indicating the reason for the failure. pub reason: Option, @@ -15,7 +15,7 @@ pub struct ApnsResponse { /// The time, in milliseconds since Epoch, at which APNs confirmed the token /// was no longer valid for the topic. This key is included only when the /// error in the `:status` field is 410. - #[serde_as(as = "TimestampMilliSeconds")] + #[serde_as(as = "Option")] pub timestamp: Option, } diff --git a/src/result.rs b/src/result.rs index f0a3de2b..c7948ed3 100644 --- a/src/result.rs +++ b/src/result.rs @@ -97,6 +97,9 @@ pub enum Error { #[error("{0}")] ApnsOther(String), + #[error("interruption level does not match sound critical flag")] + CriticalSound, + #[error(transparent)] InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),