tulpje-cache 0.4.0

A redis cache for twilight
Documentation
use std::{collections::HashSet, hash::Hash, marker::PhantomData};

use redis::{AsyncCommands as _, aio::ConnectionManager as RedisConnectionManager};
use serde::{Serialize, de::DeserializeOwned};

use crate::hash;

pub struct Repository<K: Hash, V: Serialize + DeserializeOwned> {
    name: String,
    wanted: bool,

    redis: RedisConnectionManager,

    key: PhantomData<K>,
    value: PhantomData<V>,
}

impl<K: Hash, V: Serialize + DeserializeOwned> Repository<K, V> {
    pub(crate) fn new(name: &str, wanted: bool, redis: RedisConnectionManager) -> Self {
        Self {
            name: format!("cache:{}", name),
            wanted,

            redis,

            key: PhantomData,
            value: PhantomData,
        }
    }

    pub async fn get(&self, key: &K) -> Result<Option<V>, crate::Error> {
        Ok(self
            .redis
            .clone()
            .hget::<_, _, Option<String>>(&self.name, hash(key))
            .await?
            .map(|json| serde_json::from_str(&json))
            .transpose()?)
    }

    pub(crate) async fn insert(&self, key: &K, value: &V) -> Result<bool, crate::Error> {
        if !self.wanted {
            return Ok(false);
        }

        Ok(self
            .redis
            .clone()
            .hset::<_, _, _, usize>(&self.name, hash(key), serde_json::to_string(value)?)
            .await?
            > 0)
    }

    pub(crate) async fn remove(&self, key: &K) -> Result<bool, crate::Error> {
        if !self.wanted {
            return Ok(false);
        }

        Ok(self
            .redis
            .clone()
            .hdel::<_, _, usize>(&self.name, hash(key))
            .await?
            > 0)
    }

    pub(crate) async fn remove_multi<'a>(
        &self,
        keys: impl IntoIterator<Item = &'a K>,
    ) -> Result<usize, crate::Error>
    where
        K: 'a,
    {
        if !self.wanted {
            return Ok(0);
        }

        let keys: Vec<u64> = keys.into_iter().map(hash).collect();
        if keys.is_empty() {
            return Ok(0);
        }

        Ok(self.redis.clone().hdel(&self.name, &keys).await?)
    }
}

pub struct SetRepository<T: Serialize + DeserializeOwned + Eq + Hash> {
    name: String,
    wanted: bool,

    redis: RedisConnectionManager,

    value: PhantomData<T>,
}

impl<T: Serialize + DeserializeOwned + Eq + Hash> SetRepository<T> {
    pub(crate) fn new(name: &str, wanted: bool, redis: RedisConnectionManager) -> Self {
        Self {
            name: format!("cache:{}", name),
            wanted,

            redis,

            value: PhantomData,
        }
    }

    pub async fn members(&self) -> Result<HashSet<T>, crate::Error> {
        let json_set: HashSet<String> = self.redis.clone().smembers(&self.name).await?;

        let mut result_set = HashSet::new();
        if json_set.is_empty() {
            return Ok(result_set);
        }

        for json_item in json_set {
            result_set.insert(serde_json::from_str(&json_item)?);
        }

        Ok(result_set)
    }

    pub async fn contains(&self, value: &T) -> Result<bool, crate::Error> {
        Ok(self
            .redis
            .clone()
            .sismember::<_, _, usize>(&self.name, serde_json::to_string(value)?)
            .await?
            > 0)
    }

    pub async fn is_empty(&self) -> Result<bool, crate::Error> {
        if !self.wanted {
            return Ok(false);
        }

        Ok(self.redis.clone().exists(&self.name).await?)
    }

    pub(crate) async fn insert(&self, value: &T) -> Result<bool, crate::Error> {
        if !self.wanted {
            return Ok(false);
        }

        Ok(self
            .redis
            .clone()
            .sadd::<_, _, usize>(&self.name, serde_json::to_string(value)?)
            .await?
            > 0)
    }

    pub(crate) async fn remove(&self, value: &T) -> Result<bool, crate::Error> {
        if !self.wanted {
            return Ok(false);
        }

        Ok(self
            .redis
            .clone()
            .srem::<_, _, usize>(&self.name, serde_json::to_string(value)?)
            .await?
            > 0)
    }

    pub(crate) async fn remove_multi<'a>(
        &self,
        values: impl IntoIterator<Item = &'a T>,
    ) -> Result<usize, crate::Error>
    where
        T: 'a,
    {
        if !self.wanted {
            return Ok(0);
        }

        let mut json_values = Vec::new();
        for value in values {
            json_values.push(serde_json::to_string(&value)?);
        }

        if json_values.is_empty() {
            return Ok(0);
        }

        Ok(self.redis.clone().srem(&self.name, json_values).await?)
    }

    pub(crate) async fn clear(&self) -> Result<bool, crate::Error> {
        if !self.wanted {
            return Ok(false);
        }

        Ok(self.redis.clone().del::<_, usize>(&self.name).await? > 0)
    }
}

pub struct MappedSetRepository<K: Hash, V: Serialize + DeserializeOwned + Eq + Hash> {
    name: String,
    wanted: bool,

    redis: RedisConnectionManager,

    key: PhantomData<K>,
    value: PhantomData<V>,
}

impl<K: Hash, V: Serialize + DeserializeOwned + Eq + Hash> MappedSetRepository<K, V> {
    pub(crate) fn new(name: &str, wanted: bool, redis: RedisConnectionManager) -> Self {
        Self {
            name: String::from(name),
            wanted,

            redis,

            key: PhantomData,
            value: PhantomData,
        }
    }

    fn key(&self, key: &K) -> String {
        format!("{}:{}", self.name, hash(key))
    }

    fn set_repository(&self, key: &K) -> SetRepository<V> {
        SetRepository::new(&self.key(key), self.wanted, self.redis.clone())
    }

    pub async fn members(&self, key: &K) -> Result<HashSet<V>, crate::Error> {
        self.set_repository(key).members().await
    }

    pub async fn is_empty(&self, key: &K) -> Result<bool, crate::Error> {
        self.set_repository(key).is_empty().await
    }

    pub async fn contains(&self, key: &K, value: &V) -> Result<bool, crate::Error> {
        self.set_repository(key).contains(value).await
    }

    pub(crate) async fn insert(&self, key: &K, value: &V) -> Result<bool, crate::Error> {
        self.set_repository(key).insert(value).await
    }

    pub(crate) async fn remove(&self, key: &K, value: &V) -> Result<bool, crate::Error> {
        self.set_repository(key).remove(value).await
    }

    pub(crate) async fn clear(&self, key: &K) -> Result<bool, crate::Error> {
        self.set_repository(key).clear().await
    }

    pub(crate) async fn remove_multi<'a>(
        &self,
        key: &K,
        values: impl IntoIterator<Item = &'a V>,
    ) -> Result<usize, crate::Error>
    where
        V: 'a,
    {
        self.set_repository(key).remove_multi(values).await
    }
}

pub struct SingleRepository<T: Serialize + DeserializeOwned> {
    name: String,
    wanted: bool,

    redis: RedisConnectionManager,

    value: PhantomData<T>,
}

impl<T: Serialize + DeserializeOwned> SingleRepository<T> {
    pub(crate) fn new(name: &str, wanted: bool, redis: RedisConnectionManager) -> Self {
        Self {
            name: format!("cache:{}", name),
            wanted,

            redis,

            value: PhantomData,
        }
    }

    pub(crate) async fn set(&self, value: T) -> Result<(), crate::Error> {
        if !self.wanted {
            return Ok(());
        }

        Ok(self
            .redis
            .clone()
            .set(&self.name, serde_json::to_string(&value)?)
            .await?)
    }

    pub async fn get(&self) -> Result<Option<T>, crate::Error> {
        if !self.wanted {
            return Ok(None);
        }

        Ok(self
            .redis
            .clone()
            .get::<_, Option<String>>(&self.name)
            .await?
            .map(|json| serde_json::from_str(&json))
            .transpose()?)
    }
}