use std::collections::HashMap;
use std::sync::Mutex;
use veil_engine::{EntityKind, MemoryVault, ScopeId, Vault, VaultError};
use crate::{random_key, Error};
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct VaultInfo {
pub entries: u64,
pub oldest_created_at: Option<i64>,
pub persistent: bool,
}
pub trait ManagedVault: Vault {
fn conversation_scope(&self, hint: &str) -> ScopeId;
fn bind_scope(&self, id: &str, scope: &ScopeId) -> Result<(), Error>;
fn bound_scope(&self, id: &str) -> Result<Option<ScopeId>, Error>;
fn info(&self) -> Result<VaultInfo, Error>;
fn clear(&self) -> Result<(), Error>;
fn purge_expired(&self) -> Result<u64, Error>;
fn adopt(&self, from: &ScopeId, into: &ScopeId) -> Result<bool, Error> {
let _ = (from, into);
Ok(false)
}
}
pub struct RamVault {
vault: MemoryVault,
key: [u8; 32],
bound: Mutex<HashMap<String, ScopeId>>,
}
impl Default for RamVault {
fn default() -> Self {
RamVault { vault: MemoryVault::default(), key: random_key(), bound: Mutex::default() }
}
}
impl Vault for RamVault {
fn get_or_create(&self, scope: &ScopeId, kind: &EntityKind, value: &str) -> Result<u64, VaultError> {
self.vault.get_or_create(scope, kind, value)
}
fn lookup(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<Option<String>, VaultError> {
self.vault.lookup(scope, kind, n)
}
fn reserve(&self, scope: &ScopeId, kind: &EntityKind, n: u64) -> Result<(), VaultError> {
self.vault.reserve(scope, kind, n)
}
}
impl ManagedVault for RamVault {
fn conversation_scope(&self, hint: &str) -> ScopeId {
ScopeId::keyed(&self.key, hint)
}
fn bind_scope(&self, id: &str, scope: &ScopeId) -> Result<(), Error> {
self.bound.lock().map_err(|_| Error::Poisoned)?.insert(id.to_owned(), scope.clone());
Ok(())
}
fn bound_scope(&self, id: &str) -> Result<Option<ScopeId>, Error> {
Ok(self.bound.lock().map_err(|_| Error::Poisoned)?.get(id).cloned())
}
fn info(&self) -> Result<VaultInfo, Error> {
Ok(VaultInfo {
entries: self.vault.len() as u64,
oldest_created_at: self.vault.oldest_created_at(),
persistent: false,
})
}
fn clear(&self) -> Result<(), Error> {
self.vault.clear();
self.bound.lock().map_err(|_| Error::Poisoned)?.clear();
Ok(())
}
fn purge_expired(&self) -> Result<u64, Error> {
Ok(0)
}
fn adopt(&self, from: &ScopeId, into: &ScopeId) -> Result<bool, Error> {
Ok(self.vault.adopt(from, into))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn ram_vault() {
let v = RamVault::default();
let conv = v.conversation_scope("first message");
assert!(conv.as_str().starts_with("conv:") && !conv.as_str().contains("first"));
assert_eq!(conv, v.conversation_scope("first message"));
assert_ne!(conv, RamVault::default().conversation_scope("first message"), "each has its own key");
let kind = EntityKind::new("EMAIL").unwrap();
v.get_or_create(&conv, &kind, "a@example.com").unwrap();
v.bind_scope("resp_1", &conv).unwrap();
assert_eq!(v.bound_scope("resp_1").unwrap(), Some(conv.clone()));
let info = v.info().unwrap();
assert_eq!((info.entries, info.persistent), (1, false));
v.clear().unwrap();
assert_eq!(v.info().unwrap().entries, 0);
assert_eq!(v.bound_scope("resp_1").unwrap(), None);
assert_eq!(v.get_or_create(&conv, &kind, "a@example.com").unwrap(), 2, "numbers are not reused");
}
#[test]
fn ram_vault_adopt() {
let v = RamVault::default();
let (draft, chat) = (v.conversation_scope("draft"), v.conversation_scope("chat"));
let kind = EntityKind::new("EMAIL").unwrap();
v.get_or_create(&draft, &kind, "ana@example.org").unwrap();
assert!(v.adopt(&draft, &chat).unwrap());
assert_eq!(v.lookup(&chat, &kind, 1).unwrap().as_deref(), Some("ana@example.org"));
assert!(!v.adopt(&draft, &chat).unwrap());
}
}