use std::{io::Cursor, pin::Pin, sync::Arc};
use moka2::{
future::Cache,
ops::compute::{CompResult, Op},
};
use tokio::{
io::{AsyncRead, AsyncReadExt},
sync::RwLock,
};
type Key = i32;
type Value = Arc<RwLock<String>>;
#[tokio::main]
async fn main() -> Result<(), tokio::io::Error> {
let cache: Cache<Key, Value> = Cache::new(100);
let key = 0;
let reader = Cursor::new(b"abc");
tokio::pin!(reader);
let result = append_to_cached_string(&cache, key, &mut reader).await?;
let CompResult::Inserted(entry) = result else {
panic!("`Inserted` should be returned: {result:?}");
};
assert_eq!(*entry.into_value().read().await, "a");
let result = append_to_cached_string(&cache, key, &mut reader).await?;
let CompResult::ReplacedWith(entry) = result else {
panic!("`ReplacedWith` should be returned: {result:?}");
};
assert_eq!(*entry.into_value().read().await, "ab");
let result = append_to_cached_string(&cache, key, &mut reader).await?;
let CompResult::ReplacedWith(entry) = result else {
panic!("`ReplacedWith` should be returned: {result:?}");
};
assert_eq!(*entry.into_value().read().await, "abc");
let err = append_to_cached_string(&cache, key, &mut reader).await;
assert_eq!(
err.expect_err("An error should be returned").kind(),
tokio::io::ErrorKind::UnexpectedEof
);
Ok(())
}
async fn append_to_cached_string(
cache: &Cache<Key, Value>,
key: Key,
reader: &mut Pin<&mut impl AsyncRead>,
) -> Result<CompResult<Key, Value>, tokio::io::Error> {
cache
.entry(key)
.and_try_compute_with(|maybe_entry| async {
let byte = reader.read_u8().await?;
let char =
char::from_u32(byte as u32).expect("An ASCII byte should be converted into a char");
if let Some(entry) = maybe_entry {
let v = entry.into_value();
v.write().await.push(char);
Ok(Op::Put(v))
} else {
let v = RwLock::new(String::from(char));
Ok(Op::Put(Arc::new(v)))
}
})
.await
}