use std::collections::HashMap;
use std::sync::Mutex;
use crate::Error;
use super::model::DomainState;
pub trait StateStore: Send + Sync {
fn get(&self, host: &str) -> Result<Option<DomainState>, Error>;
fn put(&self, state: &DomainState) -> Result<(), Error>;
fn remove(&self, host: &str) -> Result<(), Error>;
fn update(
&self,
host: &str,
update: &mut dyn FnMut(DomainState) -> DomainState,
) -> Result<DomainState, Error> {
let current = self.get(host)?.unwrap_or_else(|| DomainState::new(host));
let next = update(current);
self.put(&next)?;
Ok(next)
}
}
#[derive(Debug, Default)]
pub struct InMemoryStateStore {
inner: Mutex<HashMap<String, DomainState>>,
}
impl InMemoryStateStore {
pub fn new() -> Self {
Self::default()
}
}
impl StateStore for InMemoryStateStore {
fn get(&self, host: &str) -> Result<Option<DomainState>, Error> {
Ok(self
.inner
.lock()
.expect("state store lock poisoned")
.get(host)
.cloned())
}
fn put(&self, state: &DomainState) -> Result<(), Error> {
self.inner
.lock()
.expect("state store lock poisoned")
.insert(state.host.clone(), state.clone());
Ok(())
}
fn remove(&self, host: &str) -> Result<(), Error> {
self.inner
.lock()
.expect("state store lock poisoned")
.remove(host);
Ok(())
}
fn update(
&self,
host: &str,
update: &mut dyn FnMut(DomainState) -> DomainState,
) -> Result<DomainState, Error> {
let mut guard = self.inner.lock().expect("state store lock poisoned");
let current = guard
.get(host)
.cloned()
.unwrap_or_else(|| DomainState::new(host));
let next = update(current);
guard.insert(next.host.clone(), next.clone());
Ok(next)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::state::Outcome;
use std::time::Duration;
#[test]
fn in_memory_round_trips_state() {
let store = InMemoryStateStore::new();
assert_eq!(store.get("example.com").unwrap(), None);
let state = DomainState::new("example.com").record(
Outcome::Success,
Some("http://p:1".into()),
10,
Duration::ZERO,
);
store.put(&state).unwrap();
let loaded = store.get("example.com").unwrap().unwrap();
assert_eq!(loaded, state);
}
#[test]
fn in_memory_update_creates_then_modifies() {
let store = InMemoryStateStore::new();
let s1 = store
.update("example.com", &mut |cur| {
cur.record(Outcome::Blocked, None, 1, Duration::ZERO)
})
.unwrap();
assert_eq!(s1.failures, 1);
assert_eq!(s1.host, "example.com");
let s2 = store
.update("example.com", &mut |cur| {
cur.record(Outcome::Success, None, 2, Duration::ZERO)
})
.unwrap();
assert_eq!(s2.failures, 1);
assert_eq!(s2.successes, 1);
assert_eq!(store.get("example.com").unwrap().unwrap(), s2);
}
#[test]
fn in_memory_remove_deletes() {
let store = InMemoryStateStore::new();
store.put(&DomainState::new("h")).unwrap();
store.remove("h").unwrap();
assert_eq!(store.get("h").unwrap(), None);
store.remove("h").unwrap();
}
}