autoendpoint/routers/
mod.rs

1//! Routers route notifications to user agents
2
3use crate::error::ApiResult;
4use crate::extractors::notification::Notification;
5use crate::extractors::router_data_input::RouterDataInput;
6use crate::routers::apns::error::ApnsError;
7use crate::routers::fcm::error::FcmError;
8
9use autopush_common::db::error::DbError;
10
11use actix_web::http::StatusCode;
12use actix_web::HttpResponse;
13use async_trait::async_trait;
14use autopush_common::errors::ReportableError;
15use std::collections::HashMap;
16use thiserror::Error;
17
18#[cfg(feature = "stub")]
19use self::stub::error::StubError;
20pub mod apns;
21mod common;
22pub mod fcm;
23#[cfg(feature = "stub")]
24pub mod stub;
25pub mod webpush;
26
27#[async_trait(?Send)]
28pub trait Router {
29    /// Validate that the user can use this router, and return data to be stored in
30    /// the user's `router_data` field.
31    fn register(
32        &self,
33        router_input: &RouterDataInput,
34        app_id: &str,
35    ) -> Result<HashMap<String, serde_json::Value>, RouterError>;
36
37    /// Route a notification to the user
38    async fn route_notification(&self, notification: Notification) -> ApiResult<RouterResponse>;
39}
40
41/// The response returned when a router routes a notification
42#[derive(Debug, Eq, PartialEq)]
43pub struct RouterResponse {
44    pub status: StatusCode,
45    pub headers: HashMap<&'static str, String>,
46    pub body: Option<String>,
47}
48
49impl RouterResponse {
50    /// Build a successful (201 CREATED) router response
51    /// (Note, we return a 201 here for [RFC](https://datatracker.ietf.org/doc/html/rfc8030#section-5) compliance)
52    pub fn success(location: String, ttl: usize) -> Self {
53        RouterResponse {
54            status: StatusCode::CREATED,
55            headers: {
56                let mut map = HashMap::new();
57                map.insert("Location", location);
58                map.insert("TTL", ttl.to_string());
59                map
60            },
61            body: None,
62        }
63    }
64}
65
66impl From<RouterResponse> for HttpResponse {
67    fn from(router_response: RouterResponse) -> Self {
68        let mut builder = HttpResponse::build(router_response.status);
69
70        for (key, value) in router_response.headers {
71            builder.insert_header((key, value));
72        }
73
74        builder.body(router_response.body.unwrap_or_default())
75    }
76}
77
78#[derive(Debug, Error)]
79pub enum RouterError {
80    #[error(transparent)]
81    Apns(#[from] ApnsError),
82
83    #[error(transparent)]
84    Fcm(#[from] FcmError),
85
86    #[cfg(feature = "stub")]
87    #[error(transparent)]
88    Stub(#[from] StubError),
89
90    #[error("Database error while saving notification")]
91    SaveDb(#[source] DbError, Option<String>),
92
93    #[error("User was deleted during routing")]
94    UserWasDeleted,
95
96    #[error(
97        "This message is intended for a constrained device and is limited in \
98         size. Converted buffer is too long by {0} bytes"
99    )]
100    TooMuchData(usize),
101
102    #[error("Bridge authentication error")]
103    Authentication,
104
105    #[error("Bridge request timeout")]
106    RequestTimeout,
107
108    #[error("Error while connecting to bridge service")]
109    Connect(#[source] reqwest::Error),
110
111    #[error("Bridge reports user was not found")]
112    NotFound,
113}
114
115impl RouterError {
116    /// Get the associated HTTP status code
117    pub fn status(&self) -> StatusCode {
118        match self {
119            RouterError::Apns(e) => e.status(),
120            RouterError::Fcm(e) => StatusCode::from_u16(e.status().as_u16()).unwrap_or_default(),
121
122            RouterError::SaveDb(e, _) => e.status(),
123            #[cfg(feature = "stub")]
124            RouterError::Stub(e) => e.status(),
125
126            RouterError::UserWasDeleted | RouterError::NotFound => StatusCode::GONE,
127
128            RouterError::TooMuchData(_) => StatusCode::PAYLOAD_TOO_LARGE,
129
130            RouterError::Authentication | RouterError::RequestTimeout | RouterError::Connect(_) => {
131                StatusCode::BAD_GATEWAY
132            }
133        }
134    }
135
136    /// Get the associated error number
137    pub fn errno(&self) -> Option<usize> {
138        match self {
139            RouterError::Apns(e) => e.errno(),
140            RouterError::Fcm(e) => e.errno(),
141
142            #[cfg(feature = "stub")]
143            RouterError::Stub(e) => e.errno(),
144
145            RouterError::TooMuchData(_) => Some(104),
146
147            RouterError::UserWasDeleted => Some(105),
148
149            RouterError::NotFound => Some(106),
150
151            RouterError::SaveDb(_, _) => Some(201),
152
153            RouterError::Authentication => Some(901),
154
155            RouterError::Connect(_) => Some(902),
156
157            RouterError::RequestTimeout => Some(903),
158        }
159    }
160}
161
162impl ReportableError for RouterError {
163    fn reportable_source(&self) -> Option<&(dyn ReportableError + 'static)> {
164        match &self {
165            RouterError::Apns(e) => Some(e),
166            RouterError::Fcm(e) => Some(e),
167            RouterError::SaveDb(e, _) => Some(e),
168            _ => None,
169        }
170    }
171
172    fn is_sentry_event(&self) -> bool {
173        match self {
174            // apns handle_error emits a metric for ApnsError::Unregistered
175            RouterError::Apns(e) => e.is_sentry_event(),
176            RouterError::Fcm(e) => e.is_sentry_event(),
177            // common handle_error emits metrics for these
178            RouterError::Authentication
179            | RouterError::Connect(_)
180            | RouterError::NotFound
181            | RouterError::RequestTimeout
182            | RouterError::TooMuchData(_) => false,
183            RouterError::SaveDb(e, _) => e.is_sentry_event(),
184            _ => true,
185        }
186    }
187
188    fn metric_label(&self) -> Option<&'static str> {
189        // NOTE: Some metrics are emitted for other Errors via handle_error
190        // callbacks, whereas some are emitted via this method. These 2 should
191        // be consolidated: https://mozilla-hub.atlassian.net/browse/SYNC-3695
192        match self {
193            RouterError::Apns(e) => e.metric_label(),
194            RouterError::Fcm(e) => e.metric_label(),
195            RouterError::TooMuchData(_) => Some("notification.bridge.error.too_much_data"),
196            _ => None,
197        }
198    }
199
200    fn extras(&self) -> Vec<(&str, String)> {
201        match &self {
202            RouterError::Apns(e) => e.extras(),
203            RouterError::Fcm(e) => e.extras(),
204            RouterError::SaveDb(e, sub) => {
205                let mut extras = e.extras();
206                if let Some(sub) = sub {
207                    extras.append(&mut vec![("sub", sub.clone())]);
208                };
209                extras
210            }
211            _ => vec![],
212        }
213    }
214}