autoendpoint/routers/fcm/
error.rs1use crate::error::ApiErrorKind;
2use crate::routers::RouterError;
3
4use autopush_common::errors::ReportableError;
5use reqwest::StatusCode;
6
7#[derive(thiserror::Error, Debug)]
9pub enum FcmError {
10 #[error("Failed to decode the credential settings")]
11 CredentialDecode(#[from] serde_json::Error),
12
13 #[error("Error while building the OAuth client")]
14 OAuthClientBuild(#[source] std::io::Error),
15
16 #[error("Error while retrieving an OAuth token")]
17 OAuthToken(#[from] yup_oauth2::Error),
18
19 #[error("Unable to deserialize FCM response")]
20 DeserializeResponse(#[source] reqwest::Error),
21
22 #[error("Invalid JSON response from FCM")]
23 InvalidResponse(#[source] serde_json::Error, String, StatusCode),
24
25 #[error("Empty response from FCM")]
26 EmptyResponse(StatusCode),
27
28 #[error("No OAuth token was present")]
29 NoOAuthToken,
30
31 #[error("No registration token found for user")]
32 NoRegistrationToken,
33
34 #[error("No app ID found for user")]
35 NoAppId,
36
37 #[error("User has invalid app ID {0}")]
38 InvalidAppId(String),
39
40 #[error("Upstream error, {error_code}: {message}")]
41 Upstream {
42 error_code: String,
43 message: String,
44 retry_after: Option<u64>,
46 },
47}
48
49impl FcmError {
50 pub fn status(&self) -> StatusCode {
52 match self {
53 FcmError::InvalidAppId(_) => StatusCode::BAD_REQUEST,
54
55 FcmError::NoRegistrationToken | FcmError::NoAppId => StatusCode::GONE,
56
57 FcmError::CredentialDecode(_)
58 | FcmError::OAuthClientBuild(_)
59 | FcmError::OAuthToken(_)
60 | FcmError::NoOAuthToken => StatusCode::INTERNAL_SERVER_ERROR,
61
62 FcmError::Upstream { error_code, .. } if error_code == "RESOURCE_EXHAUSTED" => {
64 StatusCode::TOO_MANY_REQUESTS
65 }
66 FcmError::Upstream { error_code, .. } if error_code == "UNAVAILABLE" => {
68 StatusCode::SERVICE_UNAVAILABLE
69 }
70
71 FcmError::DeserializeResponse(_)
72 | FcmError::EmptyResponse(_)
73 | FcmError::InvalidResponse(_, _, _)
74 | FcmError::Upstream { .. } => StatusCode::BAD_GATEWAY,
75 }
76 }
77
78 pub fn errno(&self) -> Option<usize> {
80 match self {
81 FcmError::NoRegistrationToken | FcmError::NoAppId | FcmError::InvalidAppId(_) => {
82 Some(106)
83 }
84
85 FcmError::Upstream { error_code, .. }
86 if error_code == "RESOURCE_EXHAUSTED" || error_code == "UNAVAILABLE" =>
87 {
88 Some(201)
89 }
90
91 _ => None,
92 }
93 }
94
95 pub fn retry_after(&self) -> Option<u64> {
97 match self {
98 FcmError::Upstream { retry_after, .. } => *retry_after,
99 _ => None,
100 }
101 }
102}
103
104impl From<FcmError> for ApiErrorKind {
105 fn from(e: FcmError) -> Self {
106 ApiErrorKind::Router(RouterError::Fcm(e))
107 }
108}
109
110impl ReportableError for FcmError {
111 fn is_sentry_event(&self) -> bool {
112 matches!(&self, FcmError::InvalidAppId(_) | FcmError::NoAppId)
113 }
114
115 fn metric_label(&self) -> Option<&'static str> {
116 match &self {
117 FcmError::InvalidAppId(_) | FcmError::NoAppId => Some("notification.bridge.error"),
118 _ => None,
119 }
120 }
121
122 fn extras(&self) -> Vec<(&str, String)> {
123 match self {
124 FcmError::InvalidAppId(appid) => {
125 vec![
126 ("status", "bad_appid".to_owned()),
127 ("app_id", appid.to_string()),
128 ]
129 }
130 FcmError::EmptyResponse(status) => {
131 vec![("status", status.to_string())]
132 }
133 FcmError::InvalidResponse(_, body, status) => {
134 vec![("status", status.to_string()), ("body", body.to_owned())]
135 }
136 FcmError::Upstream { error_code, .. } => {
137 vec![("status", error_code.clone())]
138 }
139 _ => vec![],
140 }
141 }
142}