1use std::collections::HashMap;
5use std::sync::{Mutex, MutexGuard};
6
7use crate::{EntityKind, ScopeId, VaultError};
8
9pub trait Vault: Send + Sync {
15 fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError>;
18 fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError>;
20 fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError>;
23}
24
25#[derive(Default)]
27pub struct MemoryVault {
28 inner: Mutex<Inner>,
29}
30
31#[derive(Default)]
32struct Inner {
33 by_value: HashMap<(ScopeId, EntityKind, String), u64>,
34 by_number: HashMap<(ScopeId, EntityKind, u64), String>,
35 counters: HashMap<(ScopeId, EntityKind), u64>,
36 created: Vec<i64>,
37}
38
39impl MemoryVault {
40 pub fn len(&self) -> usize {
42 self.lock().by_number.len()
43 }
44
45 pub fn is_empty(&self) -> bool {
47 self.len() == 0
48 }
49
50 pub fn oldest_created_at(&self) -> Option<i64> {
52 self.lock().created.iter().min().copied()
53 }
54
55 pub fn clear(&self) {
58 let mut inner = self.lock();
59 inner.by_value.clear();
60 inner.by_number.clear();
61 inner.created.clear();
62 }
63
64 pub fn adopt(&self, from: &ScopeId, into: &ScopeId) -> bool {
69 let mut inner = self.lock();
70 if from == into || inner.counters.keys().any(|(scope, _)| scope == into) {
71 return false;
72 }
73 let counters: Vec<_> =
74 inner.counters.iter().filter(|((s, _), _)| s == from).map(|((_, k), n)| (k.clone(), *n)).collect();
75 let values: Vec<_> = inner
76 .by_number
77 .iter()
78 .filter(|((s, _, _), _)| s == from)
79 .map(|((_, k, n), v)| (k.clone(), *n, v.clone()))
80 .collect();
81 for (kind, n) in counters {
82 inner.counters.insert((into.clone(), kind), n);
83 }
84 let now = now();
85 for (kind, n, value) in values {
86 inner.by_value.insert((into.clone(), kind.clone(), value.clone()), n);
87 inner.by_number.insert((into.clone(), kind, n), value);
88 inner.created.push(now);
89 }
90 true
91 }
92
93 fn lock(&self) -> MutexGuard<'_, Inner> {
96 self.inner.lock().unwrap_or_else(|e| e.into_inner())
97 }
98}
99
100impl Vault for MemoryVault {
101 fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError> {
102 let mut inner = self.lock();
103 let key = (scope.clone(), kind.clone(), value.to_owned());
104 if let Some(&n) = inner.by_value.get(&key) {
105 return Ok(n);
106 }
107 let counter = inner.counters.entry((scope.clone(), kind.clone())).or_insert(0);
108 *counter += 1;
109 let n = *counter;
110 inner.by_number.insert((scope.clone(), kind.clone(), n), value.to_owned());
111 inner.by_value.insert(key, n);
112 inner.created.push(now());
113 Ok(n)
114 }
115
116 fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError> {
117 Ok(self.lock().by_number.get(&(scope.clone(), kind.clone(), n)).cloned())
118 }
119
120 fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError> {
121 let mut inner = self.lock();
122 let counter = inner.counters.entry((scope.clone(), kind.clone())).or_insert(0);
123 *counter = (*counter).max(n);
124 Ok(())
125 }
126}
127
128fn now() -> i64 {
129 std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs() as i64)
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 #[test]
137 fn clear_keeps_numbers_used() {
138 let v = MemoryVault::default();
139 let (scope, kind) = (ScopeId::new("s"), EntityKind::new("EMAIL").unwrap());
140 assert_eq!(v.get_or_create(&scope, &kind, "a@example.com").unwrap(), 1);
141 assert_eq!((v.len(), v.oldest_created_at().is_some()), (1, true));
142 v.clear();
143 assert!(v.is_empty() && v.oldest_created_at().is_none());
144 assert_eq!(v.lookup(&scope, &kind, 1).unwrap(), None);
145 assert_eq!(v.get_or_create(&scope, &kind, "a@example.com").unwrap(), 2);
146 }
147
148 #[test]
149 fn adopt_copies_a_scope_into_an_unused_one() {
150 let v = MemoryVault::default();
151 let (draft, chat, other) = (ScopeId::new("draft"), ScopeId::new("chat"), ScopeId::new("other"));
152 let email = EntityKind::new("EMAIL").unwrap();
153 assert_eq!(v.get_or_create(&draft, &email, "ana@example.org").unwrap(), 1);
154 assert_eq!(v.get_or_create(&draft, &email, "bob@example.org").unwrap(), 2);
155 assert!(v.adopt(&draft, &chat));
156 assert_eq!(v.lookup(&chat, &email, 2).unwrap().as_deref(), Some("bob@example.org"));
157 assert_eq!(v.get_or_create(&chat, &email, "ana@example.org").unwrap(), 1);
159 assert_eq!(v.get_or_create(&chat, &email, "cy@example.org").unwrap(), 3);
160 v.get_or_create(&other, &email, "cy@example.org").unwrap();
162 assert!(!v.adopt(&draft, &other));
163 assert_eq!(v.lookup(&other, &email, 2).unwrap(), None);
164 assert!(!v.adopt(&draft, &chat));
165 assert!(!v.adopt(&draft, &draft));
166 }
167}