1use failure::Fail;
2use reqwest::StatusCode;
3use serde::{Deserialize, Serialize};
4use serde_repr::{Deserialize_repr, Serialize_repr};
5use uuid::Uuid;
6
7pub static APN_URL_PRODUCTION: &'static str = "https://api.push.apple.com";
9
10pub static APN_URL_DEV: &'static str = "https://api.sandbox.push.apple.com";
12
13#[derive(Serialize_repr, Deserialize_repr, PartialEq, Eq, Clone, Copy, Debug)]
16#[repr(u8)]
17pub enum Priority {
18 Low = 1, Middle = 5,
20 High = 10, }
22
23impl Priority {
24 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#[derive(Serialize, Clone, Debug)]
37pub struct CollapseId(String);
38
39impl CollapseId {
40 pub fn new(value: String) -> Result<Self, CollapseIdTooLongError> {
43 if value.len() > 64 {
45 Err(CollapseIdTooLongError)
46 } else {
47 Ok(CollapseId(value))
48 }
49 }
50
51 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#[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#[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 #[serde(skip_serializing_if = "Option::is_none")]
137 pub badge: Option<u32>,
138 #[serde(skip_serializing_if = "Option::is_none")]
140 pub sound: Option<&'a str>,
141 #[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#[derive(Serialize, Clone, Debug)]
152pub(crate) struct ApnsRequest<'a> {
153 pub aps: Payload<'a>,
154}
155
156#[derive(Serialize, Clone, Debug)]
161pub struct Notification<'a> {
162 pub topic: &'a str,
164 pub device_token: &'a str,
165 pub payload: Payload<'a>,
166
167 pub id: Option<Uuid>,
169 pub expiration: Option<u64>,
171 pub priority: Option<Priority>,
173 pub collapse_id: Option<CollapseId>,
174 pub apns_push_type: Option<ApnsPushType>,
175}
176
177impl<'a> Notification<'a> {
178 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
193pub 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