autopush_common/db/
models.rs1use lazy_static::lazy_static;
2use regex::RegexSet;
3use std::collections::HashMap;
4
5use crate::errors::{ApcErrorKind, Result};
6use crate::notification::{STANDARD_NOTIFICATION_PREFIX, TOPIC_NOTIFICATION_PREFIX};
7use serde_derive::{Deserialize, Serialize};
8use uuid::Uuid;
9
10use crate::util::InsertOpt;
11
12#[derive(Default, Deserialize, PartialEq, Debug, Clone, Serialize)]
17pub(crate) struct NotificationHeaders {
18 #[serde(skip_serializing_if = "Option::is_none")]
19 crypto_key: Option<String>,
20 #[serde(skip_serializing_if = "Option::is_none")]
21 encryption: Option<String>,
22 #[serde(skip_serializing_if = "Option::is_none")]
23 encryption_key: Option<String>,
24 #[serde(skip_serializing_if = "Option::is_none")]
25 encoding: Option<String>,
26}
27
28#[allow(clippy::implicit_hasher)]
29impl From<NotificationHeaders> for HashMap<String, String> {
30 fn from(val: NotificationHeaders) -> Self {
31 let mut map = Self::new();
32 map.insert_opt("crypto_key", val.crypto_key);
33 map.insert_opt("encryption", val.encryption);
34 map.insert_opt("encryption_key", val.encryption_key);
35 map.insert_opt("encoding", val.encoding);
36 map
37 }
38}
39
40impl From<HashMap<String, String>> for NotificationHeaders {
41 fn from(val: HashMap<String, String>) -> Self {
42 Self {
43 crypto_key: val.get("crypto_key").map(|v| v.to_string()),
44 encryption: val.get("encryption").map(|v| v.to_string()),
45 encryption_key: val.get("encryption_key").map(|v| v.to_string()),
46 encoding: val.get("encoding").map(|v| v.to_string()),
47 }
48 }
49}
50
51#[derive(Debug)]
53pub(crate) struct RangeKey {
54 pub(crate) channel_id: Uuid,
56 pub(crate) topic: Option<String>,
58 pub(crate) sortkey_timestamp: Option<u64>,
60 #[allow(unused)]
62 pub(crate) legacy_version: Option<String>,
63}
64
65impl RangeKey {
66 pub(crate) fn parse_chidmessageid(key: &str) -> Result<RangeKey> {
68 lazy_static! {
69 static ref RE: RegexSet = RegexSet::new([
70 format!("^{TOPIC_NOTIFICATION_PREFIX}:\\S+:\\S+$").as_str(),
71 format!("^{STANDARD_NOTIFICATION_PREFIX}:\\d+:\\S+$").as_str(),
72 "^\\S{3,}:\\S+$"
73 ])
74 .unwrap();
75 }
76 if !RE.is_match(key) {
77 return Err(ApcErrorKind::GeneralError("Invalid chidmessageid".into()).into());
78 }
79
80 let v: Vec<&str> = key.split(':').collect();
81 match v[0] {
82 "01" => {
84 if v.len() != 3 {
85 return Err(ApcErrorKind::GeneralError("Invalid topic key".into()).into());
86 }
87 let (channel_id, topic) = (v[1], v[2]);
88 let channel_id = Uuid::parse_str(channel_id)?;
89 Ok(RangeKey {
90 channel_id,
91 topic: Some(topic.to_string()),
92 sortkey_timestamp: None,
93 legacy_version: None,
94 })
95 }
96 "02" => {
98 if v.len() != 3 {
99 return Err(ApcErrorKind::GeneralError("Invalid topic key".into()).into());
100 }
101 let (sortkey, channel_id) = (v[1], v[2]);
102 let channel_id = Uuid::parse_str(channel_id)?;
103 Ok(RangeKey {
104 channel_id,
105 topic: None,
106 sortkey_timestamp: Some(sortkey.parse()?),
107 legacy_version: None,
108 })
109 }
110 _ => {
114 if v.len() != 2 {
115 return Err(ApcErrorKind::GeneralError("Invalid topic key".into()).into());
116 }
117 let (channel_id, legacy_version) = (v[0], v[1]);
118 let channel_id = Uuid::parse_str(channel_id)?;
119 Ok(RangeKey {
120 channel_id,
121 topic: None,
122 sortkey_timestamp: None,
123 legacy_version: Some(legacy_version.to_string()),
124 })
125 }
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use crate::db::RangeKey;
133 use crate::util::us_since_epoch;
134 use uuid::Uuid;
135
136 #[test]
137 fn test_parse_sort_key_ver1() {
138 let chid = Uuid::new_v4();
139 let chidmessageid = format!("01:{}:mytopic", chid.hyphenated());
140 let key = RangeKey::parse_chidmessageid(&chidmessageid).unwrap();
141 assert_eq!(key.topic, Some("mytopic".to_string()));
142 assert_eq!(key.channel_id, chid);
143 assert_eq!(key.sortkey_timestamp, None);
144 }
145
146 #[test]
147 fn test_parse_sort_key_ver2() {
148 let chid = Uuid::new_v4();
149 let sortkey_timestamp = us_since_epoch();
150 let chidmessageid = format!("02:{}:{}", sortkey_timestamp, chid.hyphenated());
151 let key = RangeKey::parse_chidmessageid(&chidmessageid).unwrap();
152 assert_eq!(key.topic, None);
153 assert_eq!(key.channel_id, chid);
154 assert_eq!(key.sortkey_timestamp, Some(sortkey_timestamp));
155 }
156
157 #[test]
158 fn test_parse_sort_key_bad_values() {
159 for val in &["02j3i2o", "03:ffas:wef", "01::mytopic", "02:oops:ohnoes"] {
160 let key = RangeKey::parse_chidmessageid(val);
161 assert!(key.is_err());
162 }
163 }
164}