autopush_common/db/redis/
mod.rs

1/// This uses redis as a storage and management
2/// system for Autopush Notifications and Routing information.
3///
4/// Keys for the data are
5/// `autopush/user/{uaid}` String to store the user data
6/// `autopush/co/{uaid}` u64 to store the last time the user has interacted with the server
7/// `autopush/timestamp/{uaid}` u64 to store the last storage timestamp incremented by the server, once messages are delivered
8/// `autopush/channels/{uaid}` List to store the list of the channels of the user
9/// `autopush/msgs/{uaid}` SortedSet to store the list of the pending message ids for the user
10/// `autopush/msgs_exp/{uaid}` SortedSet to store the list of the pending message ids, ordered by expiry date, this is because SortedSet elements can't have independent expiry date
11/// `autopush/msg/{uaid}/{chidmessageid}`, with `{chidmessageid} == {chid}:{version}` String to store
12/// the content of the messages
13///
14mod redis_client;
15
16pub use redis_client::RedisClientImpl;
17
18use std::collections::HashMap;
19use std::time::Duration;
20
21use crate::db::error::DbError;
22use crate::notification::{default_ttl, Notification};
23use crate::util::deserialize_opt_u32_to_duration;
24
25use serde_derive::{Deserialize, Serialize};
26use uuid::Uuid;
27
28/// The settings for accessing the redis contents.
29#[derive(Clone, Debug, Deserialize)]
30#[serde(default)]
31pub struct RedisDbSettings {
32    #[serde(deserialize_with = "deserialize_opt_u32_to_duration")]
33    pub create_timeout: Option<Duration>,
34    #[serde(deserialize_with = "deserialize_opt_u32_to_duration")]
35    pub router_ttl: Option<Duration>,
36    #[serde(deserialize_with = "deserialize_opt_u32_to_duration")]
37    pub notification_ttl: Option<Duration>,
38}
39
40#[allow(clippy::derivable_impls)]
41impl Default for RedisDbSettings {
42    fn default() -> Self {
43        Self {
44            create_timeout: Default::default(),
45            router_ttl: Some(Duration::from_secs(crate::MAX_ROUTER_TTL_SECS)),
46            notification_ttl: Some(Duration::from_secs(crate::MAX_NOTIFICATION_TTL_SECS)),
47        }
48    }
49}
50
51impl TryFrom<&str> for RedisDbSettings {
52    type Error = DbError;
53    fn try_from(setting_string: &str) -> Result<Self, Self::Error> {
54        let me: Self = match serde_json::from_str(setting_string) {
55            Ok(me) => me,
56            Err(e) if e.is_eof() => Self::default(),
57            Err(e) => Err(DbError::General(format!(
58                "Could not parse RedisDbSettings: {:?}",
59                e
60            )))?,
61        };
62        if let Some(router_ttl) = me.router_ttl {
63            if router_ttl.as_secs() == 0 {
64                return Err(DbError::General(
65                    "router_ttl must be greater than 0".to_string(),
66                ));
67            }
68        }
69        if let Some(notification_ttl) = me.notification_ttl {
70            if notification_ttl.as_secs() == 0 {
71                return Err(DbError::General(
72                    "notification_ttl must be greater than 0".to_string(),
73                ));
74            }
75        }
76        Ok(me)
77    }
78}
79
80#[derive(Serialize, Default, Deserialize, Clone, Debug)]
81/// A Publishable Notification record. This is a notification that is either
82/// received from a third party or is outbound to a UserAgent.
83///
84pub struct StorableNotification {
85    // Required values
86    #[serde(rename = "channelID")]
87    pub channel_id: Uuid,
88    pub version: String,
89    pub timestamp: u64,
90    // Possibly stored values, provided with a default.
91    #[serde(default = "default_ttl", skip_serializing)]
92    pub ttl: u64,
93    // Optional values, which imply a "None" default.
94    #[serde(skip_serializing)]
95    pub topic: Option<String>,
96    #[serde(skip_serializing_if = "Option::is_none")]
97    pub data: Option<String>,
98    #[serde(skip_serializing)]
99    pub sortkey_timestamp: Option<u64>,
100    #[serde(skip_serializing_if = "Option::is_none")]
101    pub headers: Option<HashMap<String, String>>,
102    #[cfg(feature = "reliable_report")]
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub reliability_id: Option<String>,
105    #[cfg(feature = "reliable_report")]
106    #[serde(skip_serializing_if = "Option::is_none")]
107    pub reliable_state: Option<crate::reliability::ReliabilityState>,
108}
109
110impl From<Notification> for StorableNotification {
111    fn from(notification: Notification) -> Self {
112        Self {
113            channel_id: notification.channel_id,
114            version: notification.version,
115            timestamp: notification.timestamp,
116            ttl: notification.ttl,
117            topic: notification.topic,
118            data: notification.data,
119            sortkey_timestamp: notification.sortkey_timestamp,
120            headers: notification.headers,
121            #[cfg(feature = "reliable_report")]
122            reliability_id: notification.reliability_id,
123            #[cfg(feature = "reliable_report")]
124            reliable_state: notification.reliable_state,
125        }
126    }
127}
128
129impl From<StorableNotification> for Notification {
130    fn from(storable: StorableNotification) -> Self {
131        Self {
132            channel_id: storable.channel_id,
133            version: storable.version,
134            timestamp: storable.timestamp,
135            ttl: storable.ttl,
136            topic: storable.topic,
137            data: storable.data,
138            sortkey_timestamp: storable.sortkey_timestamp,
139            headers: storable.headers,
140            #[cfg(feature = "reliable_report")]
141            reliability_id: storable.reliability_id,
142            #[cfg(feature = "reliable_report")]
143            reliable_state: storable.reliable_state,
144        }
145    }
146}
147
148#[cfg(test)]
149mod tests {
150
151    use std::time::Duration;
152
153    #[test]
154    fn test_settings_parse() -> Result<(), crate::db::error::DbError> {
155        let settings = super::RedisDbSettings::try_from("{\"create_timeout\": 123}")?;
156        assert_eq!(
157            settings.create_timeout,
158            Some(std::time::Duration::from_secs(123))
159        );
160        let settings = super::RedisDbSettings::try_from("{}")?;
161        assert_ne!(settings.router_ttl, Some(Duration::from_secs(0)));
162        assert_ne!(settings.notification_ttl, Some(Duration::from_secs(0)));
163        let settings = super::RedisDbSettings::try_from("{\"router_ttl\":0}");
164        assert!(settings.is_err());
165        Ok(())
166    }
167}