rok-cache 0.1.0

Cache façade for the rok ecosystem — Memory/Redis drivers, Cache::get/set/remember
Documentation
use std::{fmt, future::Future, sync::Arc, time::Duration};

use serde::{de::DeserializeOwned, Serialize};

use crate::{driver::CacheHandle, CacheError};

tokio::task_local! {
    pub(crate) static CURRENT_CACHE: Arc<CacheHandle>;
}

pub fn scope_cache<F: Future>(handle: Arc<CacheHandle>, f: F) -> impl Future<Output = F::Output> {
    CURRENT_CACHE.scope(handle, f)
}

pub struct Cache;

impl Cache {
    fn handle() -> Result<Arc<CacheHandle>, CacheError> {
        CURRENT_CACHE
            .try_with(|h| h.clone())
            .map_err(|_| CacheError::NotConfigured)
    }

    /// Retrieve a cached value.  Returns `None` on miss or expiry.
    pub async fn get<T: DeserializeOwned>(key: &str) -> Result<Option<T>, CacheError> {
        let h = Self::handle()?;
        let full_key = h.key(key);
        match h.driver.get(&full_key).await? {
            None => Ok(None),
            Some(json) => serde_json::from_str(&json)
                .map(Some)
                .map_err(|e| CacheError::Deserialize(e.to_string())),
        }
    }

    /// Store a value.  `ttl = None` means no expiry.
    pub async fn set<T: Serialize>(
        key: &str,
        value: &T,
        ttl: Option<Duration>,
    ) -> Result<(), CacheError> {
        let h = Self::handle()?;
        let full_key = h.key(key);
        let json =
            serde_json::to_string(value).map_err(|e| CacheError::Serialize(e.to_string()))?;
        h.driver.set(&full_key, json, ttl).await
    }

    /// Remove a key.
    pub async fn forget(key: &str) -> Result<(), CacheError> {
        let h = Self::handle()?;
        h.driver.forget(&h.key(key)).await
    }

    /// Remove all keys (scoped to the current database / memory store).
    pub async fn flush() -> Result<(), CacheError> {
        Self::handle()?.driver.flush().await
    }

    /// Return the cached value if present; otherwise call `f`, store the
    /// result for `ttl`, and return it.
    ///
    /// The fallback closure may return any error type that implements
    /// `Display` — it is converted to `CacheError::Fetch`.
    pub async fn remember<T, F, Fut, E>(key: &str, ttl: Duration, f: F) -> Result<T, CacheError>
    where
        T: Serialize + DeserializeOwned + Send,
        F: FnOnce() -> Fut,
        Fut: Future<Output = Result<T, E>>,
        E: fmt::Display,
    {
        if let Some(cached) = Self::get::<T>(key).await? {
            return Ok(cached);
        }
        let value = f().await.map_err(|e| CacheError::Fetch(e.to_string()))?;
        Self::set(key, &value, Some(ttl)).await?;
        Ok(value)
    }
}