use std::collections::HashMap;
use std::sync::{Mutex, MutexGuard};
use crate::{EntityKind, ScopeId, VaultError};
pub trait Vault: Send + Sync {
fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError>;
fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError>;
fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError>;
}
#[derive(Default)]
pub struct MemoryVault {
inner: Mutex<Inner>,
}
#[derive(Default)]
struct Inner {
by_value: HashMap<(ScopeId, EntityKind, String), u64>,
by_number: HashMap<(ScopeId, EntityKind, u64), String>,
counters: HashMap<(ScopeId, EntityKind), u64>,
created: Vec<i64>,
}
impl MemoryVault {
pub fn len(&self) -> usize {
self.lock().by_number.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn oldest_created_at(&self) -> Option<i64> {
self.lock().created.iter().min().copied()
}
pub fn clear(&self) {
let mut inner = self.lock();
inner.by_value.clear();
inner.by_number.clear();
inner.created.clear();
}
fn lock(&self) -> MutexGuard<'_, Inner> {
self.inner.lock().unwrap_or_else(|e| e.into_inner())
}
}
impl Vault for MemoryVault {
fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError> {
let mut inner = self.lock();
let key = (scope.clone(), kind.clone(), value.to_owned());
if let Some(&n) = inner.by_value.get(&key) {
return Ok(n);
}
let counter = inner.counters.entry((scope.clone(), kind.clone())).or_insert(0);
*counter += 1;
let n = *counter;
inner.by_number.insert((scope.clone(), kind.clone(), n), value.to_owned());
inner.by_value.insert(key, n);
inner.created.push(now());
Ok(n)
}
fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError> {
Ok(self.lock().by_number.get(&(scope.clone(), kind.clone(), n)).cloned())
}
fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError> {
let mut inner = self.lock();
let counter = inner.counters.entry((scope.clone(), kind.clone())).or_insert(0);
*counter = (*counter).max(n);
Ok(())
}
}
fn now() -> i64 {
std::time::SystemTime::now().duration_since(std::time::UNIX_EPOCH).map_or(0, |d| d.as_secs() as i64)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clear_keeps_numbers_used() {
let v = MemoryVault::default();
let (scope, kind) = (ScopeId::new("s"), EntityKind::new("EMAIL").unwrap());
assert_eq!(v.get_or_create(&scope, &kind, "a@example.com").unwrap(), 1);
assert_eq!((v.len(), v.oldest_created_at().is_some()), (1, true));
v.clear();
assert!(v.is_empty() && v.oldest_created_at().is_none());
assert_eq!(v.lookup(&scope, &kind, 1).unwrap(), None);
assert_eq!(v.get_or_create(&scope, &kind, "a@example.com").unwrap(), 2);
}
}