mod file;
mod memory;
use crate::Cacheable;
use crate::error::BackendError;
use async_trait::async_trait;
use futures::future::BoxFuture;
use futures::stream::BoxStream;
use serde::{Serialize, de::DeserializeOwned};
use std::sync::Arc;
use std::time::Duration;
pub use file::FileBackend;
pub use memory::MemoryBackend;
pub type BackendInvalidationStream<Id> =
BoxStream<'static, Result<BackendInvalidation<Id>, BackendError>>;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct BackendKeyspace {
pub namespace: Option<Arc<str>>,
pub type_name: &'static str,
}
impl BackendKeyspace {
pub(crate) fn for_type<T: Cacheable>(namespace: Option<&str>) -> Self {
Self {
namespace: namespace.map(Arc::from),
type_name: T::cache_type_name(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, serde::Deserialize)]
pub enum BackendInvalidation<Id> {
Id(Id),
All,
}
#[async_trait]
pub trait CacheBackend<T>: Send + Sync
where
T: Cacheable + Serialize + DeserializeOwned,
T::Id: Serialize + DeserializeOwned,
{
async fn get(&self, keyspace: &BackendKeyspace, id: &T::Id) -> Result<Option<T>, BackendError>;
async fn put(
&self,
keyspace: &BackendKeyspace,
id: &T::Id,
value: &T,
ttl: Option<Duration>,
) -> Result<(), BackendError>;
async fn invalidate(&self, keyspace: &BackendKeyspace, id: &T::Id) -> Result<(), BackendError>;
async fn invalidate_all(&self, keyspace: &BackendKeyspace) -> Result<(), BackendError>;
fn invalidation_stream(&self, _keyspace: BackendKeyspace) -> BackendInvalidationStream<T::Id> {
Box::pin(futures::stream::empty())
}
}
pub(crate) trait BackendRuntime<T: Cacheable>: Send + Sync {
fn get<'a>(
&'a self,
keyspace: &'a BackendKeyspace,
id: &'a T::Id,
) -> BoxFuture<'a, Result<Option<T>, BackendError>>;
fn put<'a>(
&'a self,
keyspace: &'a BackendKeyspace,
id: &'a T::Id,
value: &'a T,
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<(), BackendError>>;
fn invalidate<'a>(
&'a self,
keyspace: &'a BackendKeyspace,
id: &'a T::Id,
) -> BoxFuture<'a, Result<(), BackendError>>;
fn invalidation_stream(&self, keyspace: BackendKeyspace) -> BackendInvalidationStream<T::Id>;
}
struct BackendRuntimeAdapter<B> {
backend: B,
}
pub(crate) fn erase_backend<T, B>(backend: B) -> Arc<dyn BackendRuntime<T>>
where
T: Cacheable + Serialize + DeserializeOwned,
T::Id: Serialize + DeserializeOwned,
B: CacheBackend<T> + 'static,
{
Arc::new(BackendRuntimeAdapter { backend })
}
impl<T, B> BackendRuntime<T> for BackendRuntimeAdapter<B>
where
T: Cacheable + Serialize + DeserializeOwned,
T::Id: Serialize + DeserializeOwned,
B: CacheBackend<T>,
{
fn get<'a>(
&'a self,
keyspace: &'a BackendKeyspace,
id: &'a T::Id,
) -> BoxFuture<'a, Result<Option<T>, BackendError>> {
Box::pin(self.backend.get(keyspace, id))
}
fn put<'a>(
&'a self,
keyspace: &'a BackendKeyspace,
id: &'a T::Id,
value: &'a T,
ttl: Option<Duration>,
) -> BoxFuture<'a, Result<(), BackendError>> {
Box::pin(self.backend.put(keyspace, id, value, ttl))
}
fn invalidate<'a>(
&'a self,
keyspace: &'a BackendKeyspace,
id: &'a T::Id,
) -> BoxFuture<'a, Result<(), BackendError>> {
Box::pin(self.backend.invalidate(keyspace, id))
}
fn invalidation_stream(&self, keyspace: BackendKeyspace) -> BackendInvalidationStream<T::Id> {
self.backend.invalidation_stream(keyspace)
}
}
pub(crate) fn keyspace_storage_key<T>(
keyspace: &BackendKeyspace,
id: &T::Id,
) -> Result<String, BackendError>
where
T: Cacheable,
T::Id: Serialize,
{
let id_json = serde_json::to_vec(id)?;
let id_part = format!("id_{}", encode_hex(&id_json));
Ok(format!(
"{}{}",
keyspace_storage_key_prefix(keyspace),
id_part
))
}
pub(crate) fn keyspace_storage_key_prefix(keyspace: &BackendKeyspace) -> String {
let namespace = match &keyspace.namespace {
Some(ns) => format!("ns_{}", encode_hex(ns.as_bytes())),
None => "ns_none".to_owned(),
};
let type_part = format!("ty_{}", encode_hex(keyspace.type_name.as_bytes()));
format!("{namespace}/{type_part}/")
}
pub(crate) fn encode_hex(bytes: &[u8]) -> String {
const HEX: &[u8; 16] = b"0123456789abcdef";
let mut encoded = String::with_capacity(bytes.len() * 2);
for &byte in bytes {
encoded.push(HEX[(byte >> 4) as usize] as char);
encoded.push(HEX[(byte & 0x0f) as usize] as char);
}
encoded
}