use std::collections::HashMap;
use std::hash::Hash;
use std::pin::Pin;
use std::sync::{Arc, RwLock};
use std::task::{Context, Poll};
use tokio_stream::Stream;
use super::resync::{ResyncMarker, ResyncedEvent};
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum StoreOp<K> {
Upsert(K),
Remove(K),
Ignore,
}
pub struct Store<K, V> {
inner: Arc<RwLock<HashMap<K, V>>>,
}
impl<K, V> Clone for Store<K, V> {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
impl<K, V> Default for Store<K, V> {
fn default() -> Self {
Self {
inner: Arc::new(RwLock::new(HashMap::new())),
}
}
}
impl<K, V> Store<K, V> {
pub fn new() -> Self {
Self::default()
}
}
impl<K: Eq + Hash, V> Store<K, V> {
pub fn len(&self) -> usize {
self.read(|m| m.len())
}
pub fn is_empty(&self) -> bool {
self.read(|m| m.is_empty())
}
pub fn contains_key(&self, key: &K) -> bool {
self.read(|m| m.contains_key(key))
}
pub fn get(&self, key: &K) -> Option<V>
where
V: Clone,
{
self.read(|m| m.get(key).cloned())
}
pub fn keys(&self) -> Vec<K>
where
K: Clone,
{
self.read(|m| m.keys().cloned().collect())
}
pub fn values(&self) -> Vec<V>
where
V: Clone,
{
self.read(|m| m.values().cloned().collect())
}
pub fn snapshot(&self) -> Vec<(K, V)>
where
K: Clone,
V: Clone,
{
self.read(|m| m.iter().map(|(k, v)| (k.clone(), v.clone())).collect())
}
pub fn with_read<R>(&self, f: impl FnOnce(&HashMap<K, V>) -> R) -> R {
self.read(f)
}
fn read<R>(&self, f: impl FnOnce(&HashMap<K, V>) -> R) -> R {
let guard = self.inner.read().unwrap_or_else(|e| e.into_inner());
f(&guard)
}
fn write<R>(&self, f: impl FnOnce(&mut HashMap<K, V>) -> R) -> R {
let mut guard = self.inner.write().unwrap_or_else(|e| e.into_inner());
f(&mut guard)
}
fn apply(&self, op: StoreOp<K>, value: V) {
match op {
StoreOp::Upsert(k) => self.write(|m| {
m.insert(k, value);
}),
StoreOp::Remove(k) => self.write(|m| {
m.remove(&k);
}),
StoreOp::Ignore => {}
}
}
fn replace_all(&self, next: HashMap<K, V>) {
self.write(|m| *m = next);
}
}
pub trait ReflectExt<V>:
Stream<Item = crate::Result<ResyncedEvent<V>>> + Sized + Unpin
{
fn reflect<K, F>(self, store: Store<K, V>, op: F) -> Reflect<Self, K, V, F>
where
K: Eq + Hash,
V: Clone,
F: FnMut(&V) -> StoreOp<K>,
{
Reflect::new(self, store, op)
}
}
impl<S, V> ReflectExt<V> for S where S: Stream<Item = crate::Result<ResyncedEvent<V>>> + Unpin {}
pub struct Reflect<S, K, V, F>
where
S: Stream<Item = crate::Result<ResyncedEvent<V>>> + Unpin,
K: Eq + Hash,
V: Clone,
F: FnMut(&V) -> StoreOp<K>,
{
inner: S,
store: Store<K, V>,
op: F,
staging: Option<HashMap<K, V>>,
}
impl<S, K, V, F> Reflect<S, K, V, F>
where
S: Stream<Item = crate::Result<ResyncedEvent<V>>> + Unpin,
K: Eq + Hash,
V: Clone,
F: FnMut(&V) -> StoreOp<K>,
{
fn new(inner: S, store: Store<K, V>, op: F) -> Self {
Self {
inner,
store,
op,
staging: None,
}
}
}
impl<S, K, V, F> Unpin for Reflect<S, K, V, F>
where
S: Stream<Item = crate::Result<ResyncedEvent<V>>> + Unpin,
K: Eq + Hash,
V: Clone,
F: FnMut(&V) -> StoreOp<K>,
{
}
impl<S, K, V, F> Stream for Reflect<S, K, V, F>
where
S: Stream<Item = crate::Result<ResyncedEvent<V>>> + Unpin,
K: Eq + Hash,
V: Clone,
F: FnMut(&V) -> StoreOp<K>,
{
type Item = crate::Result<ResyncedEvent<V>>;
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match Pin::new(&mut this.inner).poll_next(cx) {
Poll::Pending => Poll::Pending,
Poll::Ready(None) => Poll::Ready(None),
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
Poll::Ready(Some(Ok(item))) => {
match &item {
ResyncedEvent::Marker(ResyncMarker::ResyncStart) => {
this.staging = Some(HashMap::new());
}
ResyncedEvent::Marker(ResyncMarker::ResyncEnd) => {
if let Some(next) = this.staging.take() {
this.store.replace_all(next);
}
}
ResyncedEvent::Event(v) | ResyncedEvent::Resynced(v) => {
let op = (this.op)(v);
match &mut this.staging {
Some(stage) => {
if let StoreOp::Upsert(k) = op {
stage.insert(k, v.clone());
}
}
None => this.store.apply(op, v.clone()),
}
}
}
Poll::Ready(Some(Ok(item)))
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use tokio_stream::StreamExt;
fn synth(
items: Vec<crate::Result<ResyncedEvent<(u32, &'static str)>>>,
) -> impl Stream<Item = crate::Result<ResyncedEvent<(u32, &'static str)>>> + Unpin {
tokio_stream::iter(items)
}
fn op(ev: &(u32, &'static str)) -> StoreOp<u32> {
match ev.1 {
"del" => StoreOp::Remove(ev.0),
"skip" => StoreOp::Ignore,
_ => StoreOp::Upsert(ev.0),
}
}
#[tokio::test]
async fn upsert_remove_ignore_apply_to_store() {
let store: Store<u32, (u32, &'static str)> = Store::new();
let reader = store.clone();
let items = vec![
Ok(ResyncedEvent::Event((1, "a"))),
Ok(ResyncedEvent::Event((2, "b"))),
Ok(ResyncedEvent::Event((1, "a2"))), Ok(ResyncedEvent::Event((3, "skip"))), Ok(ResyncedEvent::Event((2, "del"))), ];
let drained: Vec<_> = synth(items).reflect(store, op).collect().await;
assert_eq!(drained.len(), 5);
assert_eq!(reader.len(), 1, "only key 1 remains");
assert_eq!(reader.get(&1), Some((1, "a2")), "upsert overwrote");
assert!(!reader.contains_key(&2), "key 2 removed");
assert!(!reader.contains_key(&3), "ignored event never inserted");
}
#[tokio::test]
async fn resync_window_replaces_snapshot_atomically() {
let store: Store<u32, (u32, &'static str)> = Store::new();
let reader = store.clone();
let items = vec![
Ok(ResyncedEvent::Event((1, "a"))),
Ok(ResyncedEvent::Event((2, "b"))),
Ok(ResyncedEvent::Marker(ResyncMarker::ResyncStart)),
Ok(ResyncedEvent::Resynced((2, "b2"))),
Ok(ResyncedEvent::Resynced((3, "c"))),
Ok(ResyncedEvent::Resynced((9, "del"))),
Ok(ResyncedEvent::Marker(ResyncMarker::ResyncEnd)),
];
let _ = synth(items).reflect(store, op).collect::<Vec<_>>().await;
assert_eq!(reader.len(), 2, "snapshot replaced, not merged");
assert!(!reader.contains_key(&1), "key 1 dropped by resync");
assert_eq!(reader.get(&2), Some((2, "b2")), "key 2 refreshed");
assert!(reader.contains_key(&3), "key 3 added by resync");
assert!(!reader.contains_key(&9), "Remove ignored during staging");
}
#[tokio::test]
async fn post_resync_deltas_apply_again() {
let store: Store<u32, (u32, &'static str)> = Store::new();
let reader = store.clone();
let items = vec![
Ok(ResyncedEvent::Marker(ResyncMarker::ResyncStart)),
Ok(ResyncedEvent::Resynced((1, "a"))),
Ok(ResyncedEvent::Marker(ResyncMarker::ResyncEnd)),
Ok(ResyncedEvent::Event((1, "del"))),
Ok(ResyncedEvent::Event((4, "d"))),
];
let _ = synth(items).reflect(store, op).collect::<Vec<_>>().await;
assert!(!reader.contains_key(&1), "post-resync delete applied");
assert_eq!(reader.get(&4), Some((4, "d")), "post-resync insert applied");
assert_eq!(reader.len(), 1);
}
#[tokio::test]
async fn errors_pass_through_without_touching_store() {
let store: Store<u32, (u32, &'static str)> = Store::new();
let reader = store.clone();
let items = vec![
Ok(ResyncedEvent::Event((1, "a"))),
Err(crate::Error::InvalidMessage("synth".into())),
Ok(ResyncedEvent::Event((2, "b"))),
];
let drained: Vec<_> = synth(items).reflect(store, op).collect().await;
assert_eq!(drained.len(), 3);
assert!(drained[1].is_err(), "error forwarded");
assert_eq!(reader.len(), 2, "both ok events still applied");
}
#[tokio::test]
async fn cloned_store_shares_state() {
let store: Store<u32, (u32, &'static str)> = Store::new();
let a = store.clone();
let b = store.clone();
let _ = synth(vec![Ok(ResyncedEvent::Event((7, "x")))])
.reflect(store, op)
.collect::<Vec<_>>()
.await;
assert_eq!(a.get(&7), Some((7, "x")));
assert_eq!(b.get(&7), Some((7, "x")), "clones observe same map");
}
#[test]
fn empty_store_accessors() {
let store: Store<u32, u32> = Store::new();
assert!(store.is_empty());
assert_eq!(store.len(), 0);
assert_eq!(store.get(&1), None);
assert!(store.keys().is_empty());
assert!(store.values().is_empty());
assert!(store.snapshot().is_empty());
assert_eq!(store.with_read(|m| m.len()), 0);
}
}