push_messaging/fcm/error.rs
1use std::{error::Error, fmt, str::FromStr};
2
3use chrono::{Duration, DateTime, FixedOffset};
4use serde::{Serialize, Deserialize, Serializer, Deserializer};
5use serde_with::skip_serializing_none;
6
7use crate::client::error::ClientError;
8
9
10/// Fatal errors. Referred from [Firebase
11/// documentation](https://firebase.google.com/docs/cloud-messaging/http-server-ref#table9)
12#[derive(PartialEq, Debug, Clone, Eq, Serialize, Deserialize)]
13pub enum FcmError {
14 /// The sender account used to send a message couldn't be authenticated. Possible causes are:
15 ///
16 /// Authorization header missing or with invalid syntax in HTTP request.
17 ///
18 /// * The Firebase project that the specified server key belongs to is
19 /// incorrect.
20 /// * Legacy server keys only—the request originated from a server not
21 /// whitelisted in the Server key IPs.
22 ///
23 /// Check that the token you're sending inside the Authentication header is
24 /// the correct Server key associated with your project. See Checking the
25 /// validity of a Server key for details. If you are using a legacy server
26 /// key, you're recommended to upgrade to a new key that has no IP
27 /// restrictions.
28 Unauthorized,
29
30 /// Check that the JSON message is properly formatted and contains valid
31 /// fields (for instance, making sure the right data type is passed in).
32 InvalidMessage(String),
33
34 /// The server couldn't process the request. Retry the same request, but you must:
35 ///
36 /// * Honor the [RetryAfter](enum.RetryAfter.html) value if included.
37 /// * Implement exponential back-off in your retry mechanism. (e.g. if you
38 /// waited one second before the first retry, wait at least two second
39 /// before the next one, then 4 seconds and so on). If you're sending
40 /// multiple messages, delay each one independently by an additional random
41 /// amount to avoid issuing a new request for all messages at the same time.
42 ///
43 /// Senders that cause problems risk being blacklisted.
44 ServerError(Option<RetryAfter>),
45}
46
47impl From<FcmError> for crate::client::error::ClientError{
48 fn from(e: FcmError) -> Self {
49 match e {
50 FcmError::Unauthorized => Self::Unauthorized,
51 FcmError::InvalidMessage(s) => Self::InvalidMessage(s),
52 FcmError::ServerError(s) => Self::ServerError(format!("{:?}", s)),
53 }
54 }
55}
56
57impl Error for FcmError {}
58
59impl fmt::Display for FcmError {
60 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
61 match self {
62 FcmError::Unauthorized => write!(
63 f,
64 "authorization header missing or with invalid syntax in HTTP request"
65 ),
66 FcmError::InvalidMessage(ref s) => write!(f, "invalid message {}", s),
67 FcmError::ServerError(_) => write!(f, "the server couldn't process the request"),
68 }
69 }
70}
71
72impl From<reqwest::Error> for FcmError {
73 fn from(_: reqwest::Error) -> Self {
74 Self::ServerError(None)
75 }
76}
77
78#[serde_with::serde_as]
79#[derive(PartialEq, Debug, Clone, Eq)]
80pub enum RetryAfter {
81 /// Amount of time to wait until retrying the message is allowed.
82 Delay(Duration),
83
84 /// A point in time until retrying the message is allowed.
85 DateTime(DateTime<FixedOffset>),
86}
87
88#[serde_with::serde_as]
89#[skip_serializing_none]
90#[derive(Debug, Clone, Deserialize, Serialize)]
91pub struct RetryAfterSerializer {
92 #[serde_as(as = "Option<serde_with::DurationSeconds<i64>>")]
93 pub delay: Option<Duration>,
94 #[serde_as(as = "Option<serde_with::DisplayFromStr>")]
95 pub date_time: Option<DateTime<FixedOffset>>,
96}
97
98impl From<RetryAfter> for RetryAfterSerializer {
99 fn from(r: RetryAfter) -> Self {
100 match r {
101 RetryAfter::Delay(d) => Self {
102 delay: Some(d),
103 date_time: None,
104 },
105 RetryAfter::DateTime(d) => Self {
106 delay: None,
107 date_time: Some(d),
108 },
109 }
110 }
111}
112
113impl Serialize for RetryAfter {
114 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
115 where
116 S: Serializer,
117 {
118 RetryAfterSerializer::from(self.clone()).serialize(serializer)
119 }
120}
121
122impl<'a> Deserialize<'a> for RetryAfter {
123 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
124 where
125 D: Deserializer<'a>,
126 {
127 let r = RetryAfterSerializer::deserialize(deserializer)?;
128 match (r.delay, r.date_time) {
129 (Some(d), None) => Ok(RetryAfter::Delay(d)),
130 (None, Some(d)) => Ok(RetryAfter::DateTime(d)),
131 _ => Err(serde::de::Error::custom("invalid RetryAfter")),
132 }
133 }
134}
135
136impl FromStr for RetryAfter {
137 type Err = FcmError;
138
139 fn from_str(s: &str) -> Result<Self, Self::Err> {
140 s.parse::<i64>()
141 .map(Duration::seconds)
142 .map(RetryAfter::Delay)
143 .or_else(|_| DateTime::parse_from_rfc2822(s).map(RetryAfter::DateTime))
144 .map_err(|e| FcmError::InvalidMessage(format!("{}", e)))
145 }
146}
147
148
149
150#[derive(Debug, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)]
151pub enum ErrorReason {
152 /// Check that the request contains a registration token (in the `to` or
153 /// `registration_ids` field).
154 MissingRegistration,
155
156 /// Check the format of the registration token you pass to the server. Make
157 /// sure it matches the registration token the client app receives from
158 /// registering with Firebase Notifications. Do not truncate or add
159 /// additional characters.
160 InvalidRegistration,
161
162 /// An existing registration token may cease to be valid in a number of
163 /// scenarios, including:
164 ///
165 /// * If the client app unregisters with FCM.
166 /// * If the client app is automatically unregistered, which can happen if
167 /// the user uninstalls the application. For example, on iOS, if the APNS
168 /// Feedback Service reported the APNS token as invalid.
169 /// * If the registration token expires (for example, Google might decide to
170 /// refresh registration tokens, or the APNS token has expired for iOS
171 /// devices).
172 /// * If the client app is updated but the new version is not configured to
173 /// receive messages.
174 ///
175 /// For all these cases, remove this registration token from the app server
176 /// and stop using it to send messages.
177 NotRegistered,
178
179 /// Make sure the message was addressed to a registration token whose
180 /// package name matches the value passed in the request.
181 InvalidPackageName,
182
183 /// A registration token is tied to a certain group of senders. When a
184 /// client app registers for FCM, it must specify which senders are allowed
185 /// to send messages. You should use one of those sender IDs when sending
186 /// messages to the client app. If you switch to a different sender, the
187 /// existing registration tokens won't work.
188 MismatchSenderId,
189
190 /// Check that the provided parameters have the right name and type.
191 InvalidParameters,
192
193 /// Check that the total size of the payload data included in a message does
194 /// not exceed FCM limits: 4096 bytes for most messages, or 2048 bytes in
195 /// the case of messages to topics. This includes both the keys and the
196 /// values.
197 MessageTooBig,
198
199 /// Check that the custom payload data does not contain a key (such as
200 /// `from`, or `gcm`, or any value prefixed by google) that is used
201 /// internally by FCM. Note that some words (such as `collapse_key`) are
202 /// also used by FCM but are allowed in the payload, in which case the
203 /// payload value will be overridden by the FCM value.
204 InvalidDataKey,
205
206 /// Check that the value used in `time_to_live` is an integer representing a
207 /// duration in seconds between 0 and 2,419,200 (4 weeks).
208 InvalidTtl,
209
210 /// In internal use only. Check
211 /// [FcmError::ServerError](enum.FcmError.html#variant.ServerError).
212 Unavailable,
213
214 /// In internal use only. Check
215 /// [FcmError::ServerError](enum.FcmError.html#variant.ServerError).
216 InternalServerError,
217
218 /// The rate of messages to a particular device is too high. If an iOS app
219 /// sends messages at a rate exceeding APNs limits, it may receive this
220 /// error message
221 ///
222 /// Reduce the number of messages sent to this device and use exponential
223 /// backoff to retry sending.
224 DeviceMessageRateExceeded,
225
226 /// The rate of messages to subscribers to a particular topic is too high.
227 /// Reduce the number of messages sent for this topic and use exponential
228 /// backoff to retry sending.
229 TopicsMessageRateExceeded,
230
231 /// A message targeted to an iOS device could not be sent because the
232 /// required APNs authentication key was not uploaded or has expired. Check
233 /// the validity of your development and production credentials.
234 InvalidApnsCredential,
235}
236
237impl From<ErrorReason> for ClientError{
238 fn from(e: ErrorReason) -> Self {
239 let reason: String = match e {
240 ErrorReason::MissingRegistration => "missing registration".into(),
241 ErrorReason::InvalidRegistration => "invalid registration".into(),
242 ErrorReason::NotRegistered => "not registered".into(),
243 ErrorReason::InvalidPackageName => "invalid package name".into(),
244 ErrorReason::MismatchSenderId => "mismatch sender id".into(),
245 ErrorReason::InvalidParameters => "invalid parameters".into(),
246 ErrorReason::MessageTooBig => "message too big".into(),
247 ErrorReason::InvalidDataKey => "invalid data key".into(),
248 ErrorReason::InvalidTtl => "invalid ttl".into(),
249 ErrorReason::Unavailable => "unavailable".into(),
250 ErrorReason::InternalServerError => "internal server error".into(),
251 ErrorReason::DeviceMessageRateExceeded => "device message rate exceeded".into(),
252 ErrorReason::TopicsMessageRateExceeded => "topics message rate exceeded".into(),
253 ErrorReason::InvalidApnsCredential => "invalid apns credential".into(),
254 };
255 ClientError::InvalidMessage(reason)
256 }
257}