use crate::storage::{CacheData, CacheKey, CachePrefix, GENERIC_CACHE_STORE, StorageError};
use crate::utils::gen_random_string_with_entropy_validation;
pub trait CacheErrorConversion<E> {
fn convert_storage_error(error: StorageError) -> E;
}
pub async fn get_data<T, E>(cache_prefix: CachePrefix, cache_key: CacheKey) -> Result<Option<T>, E>
where
T: TryFrom<CacheData, Error = E>,
E: CacheErrorConversion<E>,
{
match GENERIC_CACHE_STORE
.lock()
.await
.get(cache_prefix, cache_key)
.await
.map_err(E::convert_storage_error)?
{
Some(cache_data) => {
let converted_data = T::try_from(cache_data)?;
Ok(Some(converted_data))
}
None => Ok(None),
}
}
pub async fn remove_data<E>(cache_prefix: CachePrefix, cache_key: CacheKey) -> Result<(), E>
where
E: CacheErrorConversion<E>,
{
GENERIC_CACHE_STORE
.lock()
.await
.remove(cache_prefix, cache_key)
.await
.map_err(E::convert_storage_error)
}
pub async fn store_cache_auto<T, E>(prefix: CachePrefix, data: T, ttl: u64) -> Result<CacheKey, E>
where
T: Into<CacheData>,
E: CacheErrorConversion<E>,
{
let cache_data = data.into();
let ttl_usize = ttl.try_into().map_err(|_| {
E::convert_storage_error(StorageError::InvalidInput(
"TTL value too large for storage backend".to_string(),
))
})?;
let max_attempts = 20;
for attempt in 1..=max_attempts {
let generated_key_str = gen_random_string_with_entropy_validation(32).map_err(|e| {
E::convert_storage_error(StorageError::InvalidInput(format!(
"Key generation failed: {e}"
)))
})?;
let cache_key =
CacheKey::new(generated_key_str.clone()).map_err(E::convert_storage_error)?;
let inserted = GENERIC_CACHE_STORE
.lock()
.await
.put_if_not_exists(prefix.clone(), cache_key, cache_data.clone(), ttl_usize)
.await
.map_err(E::convert_storage_error)?;
if inserted {
let cache_key = CacheKey::new(generated_key_str).map_err(E::convert_storage_error)?;
return Ok(cache_key);
}
tracing::debug!(
"Collision detected on attempt {} for auto-generated key, retrying...",
attempt
);
}
Err(E::convert_storage_error(StorageError::InvalidInput(
format!("Failed to store data after {max_attempts} collision detection attempts"),
)))
}
pub async fn store_cache_keyed<T, E>(
prefix: CachePrefix,
key: CacheKey,
data: T,
ttl: u64,
) -> Result<(), E>
where
T: Into<CacheData>,
E: CacheErrorConversion<E>,
{
let cache_data = data.into();
let ttl_usize = ttl.try_into().map_err(|_| {
E::convert_storage_error(StorageError::InvalidInput(
"TTL value too large for storage backend".to_string(),
))
})?;
GENERIC_CACHE_STORE
.lock()
.await
.put_with_ttl(prefix, key, cache_data, ttl_usize)
.await
.map_err(E::convert_storage_error)
}
#[cfg(test)]
mod tests;