Skip to main content

hi_apns/
types.rs

1use failure::Fail;
2use reqwest::StatusCode;
3use serde::{Deserialize, Serialize};
4use serde_repr::{Deserialize_repr, Serialize_repr};
5use uuid::Uuid;
6
7/// APNS production endpoint.
8pub static APN_URL_PRODUCTION: &'static str = "https://api.push.apple.com";
9
10/// APNS development endpoint.
11pub static APN_URL_DEV: &'static str = "https://api.sandbox.push.apple.com";
12
13/// Notification priority.
14/// See APNS documentation for the effects.
15#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Clone, Copy, Debug)]
16#[repr(u8)]
17pub enum Priority {
18    Low = 1, // 5
19    Middle = 5,
20    High = 10, // 10
21}
22
23impl Priority {
24    /// Convert Priority to it's numeric value.
25    pub fn to_uint(self) -> u8 {
26        self as u8
27    }
28}
29
30#[derive(Fail, Debug)]
31#[fail(display = "CollapseId too long (must be at most 64 bytes)")]
32pub struct CollapseIdTooLongError;
33
34/// Wrapper type for collapse ids.
35/// It may be an arbitrary string, but is limited in length to at most 63 bytes.
36#[derive(Serialize, Clone, Debug)]
37pub struct CollapseId(String);
38
39impl CollapseId {
40    /// Construct a new collapse id.
41    /// Returns an error if id exceeds the maximum length of 64 bytes.
42    pub fn new(value: String) -> Result<Self, CollapseIdTooLongError> {
43        // CollapseID must be at most 64 bytes long.
44        if value.len() > 64 {
45            Err(CollapseIdTooLongError)
46        } else {
47            Ok(CollapseId(value))
48        }
49    }
50
51    /// Get id as a raw str.
52    pub fn as_str(&self) -> &str {
53        &self.0
54    }
55}
56
57#[derive(Serialize, Debug, Clone)]
58#[serde(rename_all = "lowercase")]
59pub enum ApnsPushType {
60    Alert,
61    Background,
62    Voip,
63    Location,
64    Complication,
65    Fileprovider,
66    Mdm,
67}
68
69impl ApnsPushType {
70    pub fn as_str(&self) -> &'static str {
71        match self {
72            ApnsPushType::Alert => "alert",
73            ApnsPushType::Background => "background",
74            ApnsPushType::Voip => "voip",
75            ApnsPushType::Location => "location",
76            ApnsPushType::Complication => "complication",
77            ApnsPushType::Fileprovider => "fileprovider",
78            ApnsPushType::Mdm => "mdm",
79        }
80    }
81}
82/// Alert content for a notification.
83///
84/// See the official documentation for details:
85/// https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/PayloadKeyReference.html
86#[derive(Serialize, Default, Clone, Debug)]
87pub struct AlertPayload<'a> {
88    #[serde(skip_serializing_if = "Option::is_none")]
89    pub title: Option<&'a str>,
90    #[serde(skip_serializing_if = "Option::is_none")]
91    pub body: Option<&'a str>,
92    #[serde(rename = "title-loc-key", skip_serializing_if = "Option::is_none")]
93    pub title_loc_key: Option<&'a str>,
94    #[serde(rename = "title-loc-args", skip_serializing_if = "Option::is_none")]
95    pub title_loc_args: Option<Vec<String>>,
96    #[serde(rename = "action-loc-key", skip_serializing_if = "Option::is_none")]
97    pub action_loc_key: Option<&'a str>,
98    #[serde(rename = "loc-key", skip_serializing_if = "Option::is_none")]
99    pub loc_key: Option<&'a str>,
100    #[serde(rename = "loc-args", skip_serializing_if = "Option::is_none")]
101    pub loc_args: Option<Vec<String>>,
102    #[serde(skip_serializing_if = "Option::is_none")]
103    pub loc_image: Option<&'a str>,
104}
105
106impl<'a> AlertPayload<'a> {
107    fn new(title: Option<&'a str>, body: Option<&'a str>) -> Self {
108        AlertPayload {
109            title: title,
110            body: body,
111            title_loc_key: None,
112            title_loc_args: None,
113            action_loc_key: None,
114            loc_key: None,
115            loc_args: None,
116            loc_image: None,
117        }
118    }
119}
120
121/// The alert content.
122/// This can either be a plain message string, or an AlertPayload with more
123/// configuration.
124#[derive(Serialize, Clone, Debug)]
125#[serde(untagged)]
126pub enum Alert<'a> {
127    Simple(&'a str),
128    Payload(AlertPayload<'a>),
129}
130
131#[derive(Serialize, Default, Clone, Debug)]
132pub struct Payload<'a> {
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub alert: Option<Alert<'a>>,
135    /// Updates the numeric badge for the app. Set to 0 to remove.
136    #[serde(skip_serializing_if = "Option::is_none")]
137    pub badge: Option<u32>,
138    /// Sound to play. Use 'default' for the default sound.
139    #[serde(skip_serializing_if = "Option::is_none")]
140    pub sound: Option<&'a str>,
141    /// Set to true to mark the app as having content available.
142    #[serde(rename = "content-available", skip_serializing_if = "Option::is_none")]
143    pub content_available: Option<bool>,
144    #[serde(skip_serializing_if = "Option::is_none")]
145    pub category: Option<&'a str>,
146    #[serde(rename = "thread-id", skip_serializing_if = "Option::is_none")]
147    pub thread_id: Option<&'a str>,
148}
149
150/// A full json request object for sending a notification to the API.
151#[derive(Serialize, Clone, Debug)]
152pub(crate) struct ApnsRequest<'a> {
153    pub aps: Payload<'a>,
154}
155
156/// A notification struct contains all relevant data for a notification request
157/// sent to the APNS API.
158/// This includes other options not contained in the payload.
159/// These options are transferred with HTTP request headers.
160#[derive(Serialize, Clone, Debug)]
161pub struct Notification<'a> {
162    /// The topic to use. Usually the app bundle id.
163    pub topic: &'a str,
164    pub device_token: &'a str,
165    pub payload: Payload<'a>,
166
167    /// Optional id identifying the message.
168    pub id: Option<Uuid>,
169    /// Optional expiration time as UNIX timestamp.
170    pub expiration: Option<u64>,
171    /// Priority for the notification.
172    pub priority: Option<Priority>,
173    pub collapse_id: Option<CollapseId>,
174    pub apns_push_type: Option<ApnsPushType>,
175}
176
177impl<'a> Notification<'a> {
178    /// Create a new notification.
179    pub fn new(topic: &'a str, device_token: &'a str, payload: Payload<'a>) -> Self {
180        Notification {
181            topic,
182            device_token,
183            payload,
184            id: None,
185            expiration: None,
186            priority: None,
187            collapse_id: None,
188            apns_push_type: None,
189        }
190    }
191}
192
193/// A builder for convenient construction of notifications.
194pub struct NotificationBuilder<'a> {
195    notification: Notification<'a>,
196}
197
198impl<'a> NotificationBuilder<'a> {
199    pub fn new(topic: &'a str, device_id: &'a str) -> Self {
200        NotificationBuilder {
201            notification: Notification::new(topic, device_id, Payload::default()),
202        }
203    }
204
205    pub fn push_type(mut self, push_type: ApnsPushType) -> Self {
206        self.notification.apns_push_type = push_type.into();
207        self
208    }
209
210    pub fn payload(mut self, payload: Payload<'a>) -> Self {
211        self.notification.payload = payload;
212        self
213    }
214
215    pub fn alert(mut self, alert: &'a str) -> Self {
216        self.notification.payload.alert = Some(Alert::Simple(alert.into()));
217        self
218    }
219
220    pub fn title(mut self, title: &'a str) -> Self {
221        let payload = match self.notification.payload.alert.take() {
222            None => AlertPayload::new(Some(title), None),
223            Some(Alert::Simple(_)) => AlertPayload::new(Some(title), None),
224            Some(Alert::Payload(mut payload)) => {
225                payload.title = Some(title);
226                payload
227            }
228        };
229        self.notification.payload.alert = Some(Alert::Payload(payload));
230        self
231    }
232
233    pub fn body(mut self, body: &'a str) -> Self {
234        let payload = match self.notification.payload.alert.take() {
235            None => AlertPayload::new(None, Some(body)),
236            Some(Alert::Simple(title)) => AlertPayload::new(Some(title), Some(body)),
237            Some(Alert::Payload(mut payload)) => {
238                payload.body = Some(body);
239                payload
240            }
241        };
242        self.notification.payload.alert = Some(Alert::Payload(payload));
243        self
244    }
245
246    pub fn badge(mut self, number: u32) -> Self {
247        self.notification.payload.badge = Some(number);
248        self
249    }
250
251    pub fn sound(mut self, sound: &'a str) -> Self {
252        self.notification.payload.sound = Some(sound.into());
253        self
254    }
255
256    pub fn content_available(mut self) -> Self {
257        self.notification.payload.content_available = Some(true);
258        self
259    }
260
261    pub fn category(mut self, category: &'a str) -> Self {
262        self.notification.payload.category = Some(category);
263        self
264    }
265
266    pub fn thread_id(mut self, thread_id: &'a str) -> Self {
267        self.notification.payload.thread_id = Some(thread_id);
268        self
269    }
270
271    pub fn id(mut self, id: Uuid) -> Self {
272        self.notification.id = Some(id);
273        self
274    }
275
276    pub fn expiration(mut self, expiration: u64) -> Self {
277        self.notification.expiration = Some(expiration);
278        self
279    }
280
281    pub fn priority(mut self, priority: Priority) -> Self {
282        self.notification.priority = Some(priority);
283        self
284    }
285
286    pub fn collapse_id(mut self, id: CollapseId) -> Self {
287        self.notification.collapse_id = Some(id);
288        self
289    }
290
291    pub fn build(self) -> Notification<'a> {
292        self.notification
293    }
294}
295
296#[derive(Debug, Deserialize)]
297pub struct Response {
298    pub reason: String,
299    pub timestamp: Option<i64>,
300
301    pub apns_id: String,
302    #[serde(skip)]
303    pub(crate) status_code: StatusCode,
304}
305
306// impl Response {
307//     fn is_ok(&self) -> bool {
308//         self.status_code.is_success()
309//     }
310// }