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