1use dashmap::{DashMap, mapref::entry::Entry};
8use parking_lot::RwLock;
9use rustack_ses_model::{
10 error::{SesError, SesErrorCode},
11 types::{ReceiptRule, ReceiptRuleSetMetadata},
12};
13
14#[derive(Debug, Clone)]
16pub struct ReceiptRuleSetRecord {
17 pub name: String,
19 pub rules: Vec<ReceiptRule>,
21 pub created_timestamp: chrono::DateTime<chrono::Utc>,
23}
24
25#[derive(Debug)]
30pub struct ReceiptRuleSetStore {
31 rule_sets: DashMap<String, ReceiptRuleSetRecord>,
32 active_rule_set: RwLock<Option<String>>,
33}
34
35impl Default for ReceiptRuleSetStore {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl ReceiptRuleSetStore {
42 #[must_use]
44 pub fn new() -> Self {
45 Self {
46 rule_sets: DashMap::new(),
47 active_rule_set: RwLock::new(None),
48 }
49 }
50
51 pub fn create_rule_set(&self, name: &str) -> Result<(), SesError> {
57 match self.rule_sets.entry(name.to_owned()) {
58 Entry::Occupied(_) => Err(SesError::with_message(
59 SesErrorCode::AlreadyExistsException,
60 format!("Receipt rule set <{name}> already exists."),
61 )),
62 Entry::Vacant(e) => {
63 e.insert(ReceiptRuleSetRecord {
64 name: name.to_owned(),
65 rules: Vec::new(),
66 created_timestamp: chrono::Utc::now(),
67 });
68 Ok(())
69 }
70 }
71 }
72
73 pub fn delete_rule_set(&self, name: &str) -> Result<(), SesError> {
79 self.rule_sets.remove(name).ok_or_else(|| {
80 SesError::with_message(
81 SesErrorCode::RuleSetDoesNotExistException,
82 format!("Receipt rule set <{name}> does not exist."),
83 )
84 })?;
85 let mut active = self.active_rule_set.write();
87 if active.as_deref() == Some(name) {
88 *active = None;
89 }
90 Ok(())
91 }
92
93 pub fn describe_rule_set(&self, name: &str) -> Result<ReceiptRuleSetRecord, SesError> {
99 self.rule_sets
100 .get(name)
101 .map(|entry| entry.value().clone())
102 .ok_or_else(|| {
103 SesError::with_message(
104 SesErrorCode::RuleSetDoesNotExistException,
105 format!("Receipt rule set <{name}> does not exist."),
106 )
107 })
108 }
109
110 pub fn create_rule(
116 &self,
117 rule_set_name: &str,
118 rule: ReceiptRule,
119 after: Option<&str>,
120 ) -> Result<(), SesError> {
121 let mut entry = self.rule_sets.get_mut(rule_set_name).ok_or_else(|| {
122 SesError::with_message(
123 SesErrorCode::RuleSetDoesNotExistException,
124 format!("Receipt rule set <{rule_set_name}> does not exist."),
125 )
126 })?;
127
128 if let Some(after_name) = after {
129 let pos = entry
130 .rules
131 .iter()
132 .position(|r| r.name == after_name)
133 .map_or(entry.rules.len(), |p| p + 1);
134 entry.rules.insert(pos, rule);
135 } else {
136 entry.rules.push(rule);
137 }
138 Ok(())
139 }
140
141 pub fn delete_rule(&self, rule_set_name: &str, rule_name: &str) -> Result<(), SesError> {
147 let mut entry = self.rule_sets.get_mut(rule_set_name).ok_or_else(|| {
148 SesError::with_message(
149 SesErrorCode::RuleSetDoesNotExistException,
150 format!("Receipt rule set <{rule_set_name}> does not exist."),
151 )
152 })?;
153 entry.rules.retain(|r| r.name != rule_name);
154 Ok(())
155 }
156
157 pub fn clone_rule_set(&self, source_name: &str, dest_name: &str) -> Result<(), SesError> {
164 let source = self.describe_rule_set(source_name)?;
165 match self.rule_sets.entry(dest_name.to_owned()) {
166 Entry::Occupied(_) => Err(SesError::with_message(
167 SesErrorCode::AlreadyExistsException,
168 format!("Receipt rule set <{dest_name}> already exists."),
169 )),
170 Entry::Vacant(e) => {
171 e.insert(ReceiptRuleSetRecord {
172 name: dest_name.to_owned(),
173 rules: source.rules.clone(),
174 created_timestamp: chrono::Utc::now(),
175 });
176 Ok(())
177 }
178 }
179 }
180
181 #[must_use]
183 pub fn get_active_rule_set(&self) -> Option<(ReceiptRuleSetMetadata, Vec<ReceiptRule>)> {
184 let active = self.active_rule_set.read();
185 let name = active.as_ref()?;
186 let record = self.rule_sets.get(name)?;
187 Some((
188 ReceiptRuleSetMetadata {
189 name: Some(record.name.clone()),
190 created_timestamp: Some(record.created_timestamp),
191 },
192 record.rules.clone(),
193 ))
194 }
195
196 pub fn set_active_rule_set(&self, name: Option<&str>) -> Result<(), SesError> {
203 if let Some(name) = name {
204 if !self.rule_sets.contains_key(name) {
205 return Err(SesError::with_message(
206 SesErrorCode::RuleSetDoesNotExistException,
207 format!("Receipt rule set <{name}> does not exist."),
208 ));
209 }
210 *self.active_rule_set.write() = Some(name.to_owned());
211 } else {
212 *self.active_rule_set.write() = None;
213 }
214 Ok(())
215 }
216}
217
218#[cfg(test)]
219mod tests {
220 use super::*;
221
222 #[test]
223 fn test_should_create_rule_set() {
224 let store = ReceiptRuleSetStore::new();
225 assert!(store.create_rule_set("my-rules").is_ok());
226 }
227
228 #[test]
229 fn test_should_reject_duplicate_rule_set() {
230 let store = ReceiptRuleSetStore::new();
231 store.create_rule_set("dup").unwrap_or_default();
232 assert!(store.create_rule_set("dup").is_err());
233 }
234
235 #[test]
236 fn test_should_delete_rule_set() {
237 let store = ReceiptRuleSetStore::new();
238 store.create_rule_set("del").unwrap_or_default();
239 assert!(store.delete_rule_set("del").is_ok());
240 assert!(store.describe_rule_set("del").is_err());
241 }
242
243 #[test]
244 fn test_should_describe_rule_set() {
245 let store = ReceiptRuleSetStore::new();
246 store.create_rule_set("desc").unwrap_or_default();
247 let record = store.describe_rule_set("desc");
248 assert!(record.is_ok());
249 assert_eq!(
250 record
251 .unwrap_or_else(|_| ReceiptRuleSetRecord {
252 name: String::new(),
253 rules: Vec::new(),
254 created_timestamp: chrono::Utc::now(),
255 })
256 .name,
257 "desc"
258 );
259 }
260
261 #[test]
262 fn test_should_create_rule_in_set() {
263 let store = ReceiptRuleSetStore::new();
264 store.create_rule_set("set1").unwrap_or_default();
265 let rule = ReceiptRule {
266 name: "rule1".to_owned(),
267 enabled: Some(true),
268 ..ReceiptRule::default()
269 };
270 assert!(store.create_rule("set1", rule, None).is_ok());
271 let record = store
272 .describe_rule_set("set1")
273 .unwrap_or_else(|_| ReceiptRuleSetRecord {
274 name: String::new(),
275 rules: Vec::new(),
276 created_timestamp: chrono::Utc::now(),
277 });
278 assert_eq!(record.rules.len(), 1);
279 }
280
281 #[test]
282 fn test_should_create_rule_after_existing() {
283 let store = ReceiptRuleSetStore::new();
284 store.create_rule_set("set2").unwrap_or_default();
285 let rule1 = ReceiptRule {
286 name: "rule1".to_owned(),
287 ..ReceiptRule::default()
288 };
289 let rule2 = ReceiptRule {
290 name: "rule2".to_owned(),
291 ..ReceiptRule::default()
292 };
293 let rule3 = ReceiptRule {
294 name: "rule3".to_owned(),
295 ..ReceiptRule::default()
296 };
297 store.create_rule("set2", rule1, None).unwrap_or_default();
298 store.create_rule("set2", rule2, None).unwrap_or_default();
299 store
300 .create_rule("set2", rule3, Some("rule1"))
301 .unwrap_or_default();
302 let record = store
303 .describe_rule_set("set2")
304 .unwrap_or_else(|_| ReceiptRuleSetRecord {
305 name: String::new(),
306 rules: Vec::new(),
307 created_timestamp: chrono::Utc::now(),
308 });
309 assert_eq!(record.rules[0].name, "rule1");
310 assert_eq!(record.rules[1].name, "rule3");
311 assert_eq!(record.rules[2].name, "rule2");
312 }
313
314 #[test]
315 fn test_should_delete_rule() {
316 let store = ReceiptRuleSetStore::new();
317 store.create_rule_set("set3").unwrap_or_default();
318 let rule = ReceiptRule {
319 name: "rule1".to_owned(),
320 ..ReceiptRule::default()
321 };
322 store.create_rule("set3", rule, None).unwrap_or_default();
323 assert!(store.delete_rule("set3", "rule1").is_ok());
324 let record = store
325 .describe_rule_set("set3")
326 .unwrap_or_else(|_| ReceiptRuleSetRecord {
327 name: String::new(),
328 rules: Vec::new(),
329 created_timestamp: chrono::Utc::now(),
330 });
331 assert!(record.rules.is_empty());
332 }
333
334 #[test]
335 fn test_should_clone_rule_set() {
336 let store = ReceiptRuleSetStore::new();
337 store.create_rule_set("source").unwrap_or_default();
338 let rule = ReceiptRule {
339 name: "rule1".to_owned(),
340 ..ReceiptRule::default()
341 };
342 store.create_rule("source", rule, None).unwrap_or_default();
343 assert!(store.clone_rule_set("source", "dest").is_ok());
344 let record = store
345 .describe_rule_set("dest")
346 .unwrap_or_else(|_| ReceiptRuleSetRecord {
347 name: String::new(),
348 rules: Vec::new(),
349 created_timestamp: chrono::Utc::now(),
350 });
351 assert_eq!(record.rules.len(), 1);
352 assert_eq!(record.rules[0].name, "rule1");
353 }
354
355 #[test]
356 fn test_should_set_and_get_active_rule_set() {
357 let store = ReceiptRuleSetStore::new();
358 store.create_rule_set("active-set").unwrap_or_default();
359 assert!(store.get_active_rule_set().is_none());
360 assert!(store.set_active_rule_set(Some("active-set")).is_ok());
361 let active = store.get_active_rule_set();
362 assert!(active.is_some());
363 let (metadata, _) = active.unwrap_or_default();
364 assert_eq!(metadata.name, Some("active-set".to_owned()));
365 }
366
367 #[test]
368 fn test_should_clear_active_on_delete() {
369 let store = ReceiptRuleSetStore::new();
370 store.create_rule_set("doomed").unwrap_or_default();
371 store
372 .set_active_rule_set(Some("doomed"))
373 .unwrap_or_default();
374 store.delete_rule_set("doomed").unwrap_or_default();
375 assert!(store.get_active_rule_set().is_none());
376 }
377}