Skip to main content

autopush_common/db/
models.rs

1#[cfg(any(test, feature = "bigtable"))]
2use lazy_static::lazy_static;
3#[cfg(any(test, feature = "bigtable"))]
4use regex::RegexSet;
5
6#[cfg(any(test, feature = "bigtable"))]
7use crate::errors::{ApcErrorKind, Result};
8#[cfg(any(test, feature = "bigtable"))]
9use crate::notification::{STANDARD_NOTIFICATION_PREFIX, TOPIC_NOTIFICATION_PREFIX};
10#[cfg(any(test, feature = "bigtable"))]
11use uuid::Uuid;
12
13/// Contains some meta info regarding the message we're handling.
14#[cfg(any(test, feature = "bigtable"))]
15#[derive(Debug)]
16pub(crate) struct RangeKey {
17    /// The channel_identifier
18    pub(crate) channel_id: Uuid,
19    /// The optional topic identifier
20    pub(crate) topic: Option<String>,
21    /// The encoded sortkey and timestamp
22    pub(crate) sortkey_timestamp: Option<u64>,
23    /// Which version of this message are we handling
24    #[allow(unused)]
25    pub(crate) legacy_version: Option<String>,
26}
27
28#[cfg(any(test, feature = "bigtable"))]
29impl RangeKey {
30    /// read the custom sort_key and convert it into something the database can use.
31    pub(crate) fn parse_chidmessageid(key: &str) -> Result<RangeKey> {
32        lazy_static! {
33            static ref RE: RegexSet = RegexSet::new([
34                format!("^{TOPIC_NOTIFICATION_PREFIX}:\\S+:\\S+$").as_str(),
35                format!("^{STANDARD_NOTIFICATION_PREFIX}:\\d+:\\S+$").as_str(),
36                "^\\S{3,}:\\S+$"
37            ])
38            .unwrap();
39        }
40        if !RE.is_match(key) {
41            return Err(ApcErrorKind::GeneralError("Invalid chidmessageid".into()).into());
42        }
43
44        let v: Vec<&str> = key.split(':').collect();
45        match v[0] {
46            // This is a topic message (There Can Only Be One. <guitar riff>)
47            "01" => {
48                if v.len() != 3 {
49                    return Err(ApcErrorKind::GeneralError("Invalid topic key".into()).into());
50                }
51                let (channel_id, topic) = (v[1], v[2]);
52                let channel_id = Uuid::parse_str(channel_id)?;
53                Ok(RangeKey {
54                    channel_id,
55                    topic: Some(topic.to_string()),
56                    sortkey_timestamp: None,
57                    legacy_version: None,
58                })
59            }
60            // A "normal" pending message.
61            "02" => {
62                if v.len() != 3 {
63                    return Err(ApcErrorKind::GeneralError("Invalid topic key".into()).into());
64                }
65                let (sortkey, channel_id) = (v[1], v[2]);
66                let channel_id = Uuid::parse_str(channel_id)?;
67                Ok(RangeKey {
68                    channel_id,
69                    topic: None,
70                    sortkey_timestamp: Some(sortkey.parse()?),
71                    legacy_version: None,
72                })
73            }
74            // Ok, that's odd, but try to make some sense of it.
75            // (This is a bit of legacy code that we should be
76            // able to drop.)
77            _ => {
78                if v.len() != 2 {
79                    return Err(ApcErrorKind::GeneralError("Invalid topic key".into()).into());
80                }
81                let (channel_id, legacy_version) = (v[0], v[1]);
82                let channel_id = Uuid::parse_str(channel_id)?;
83                Ok(RangeKey {
84                    channel_id,
85                    topic: None,
86                    sortkey_timestamp: None,
87                    legacy_version: Some(legacy_version.to_string()),
88                })
89            }
90        }
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::RangeKey;
97    use crate::util::us_since_epoch;
98    use uuid::Uuid;
99
100    #[test]
101    fn test_parse_sort_key_ver1() {
102        let chid = Uuid::new_v4();
103        let chidmessageid = format!("01:{}:mytopic", chid.hyphenated());
104        let key = RangeKey::parse_chidmessageid(&chidmessageid).unwrap();
105        assert_eq!(key.topic, Some("mytopic".to_string()));
106        assert_eq!(key.channel_id, chid);
107        assert_eq!(key.sortkey_timestamp, None);
108    }
109
110    #[test]
111    fn test_parse_sort_key_ver2() {
112        let chid = Uuid::new_v4();
113        let sortkey_timestamp = us_since_epoch();
114        let chidmessageid = format!("02:{}:{}", sortkey_timestamp, chid.hyphenated());
115        let key = RangeKey::parse_chidmessageid(&chidmessageid).unwrap();
116        assert_eq!(key.topic, None);
117        assert_eq!(key.channel_id, chid);
118        assert_eq!(key.sortkey_timestamp, Some(sortkey_timestamp));
119    }
120
121    #[test]
122    fn test_parse_sort_key_bad_values() {
123        for val in &["02j3i2o", "03:ffas:wef", "01::mytopic", "02:oops:ohnoes"] {
124            let key = RangeKey::parse_chidmessageid(val);
125            assert!(key.is_err());
126        }
127    }
128}