1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
use casbin::Cache;

use redis::{Client, Connection, IntoConnectionInfo, RedisResult};
use serde::{de::DeserializeOwned, Serialize};

use std::{borrow::Cow, hash::Hash, marker::PhantomData};

const CACHE_PREFIX: &str = "casbin";

pub struct RedisCache<K, V>
where
    K: Eq + Hash + Send + Sync + Serialize + 'static,
    V: Send + Sync + Clone + Serialize + DeserializeOwned + 'static,
{
    conn: Connection,
    _marker: PhantomData<(K, V)>,
}

impl<K, V> RedisCache<K, V>
where
    K: Eq + Hash + Send + Sync + Serialize + 'static,
    V: Send + Sync + Clone + Serialize + DeserializeOwned + 'static,
{
    pub fn new<U: IntoConnectionInfo>(url: U) -> RedisResult<RedisCache<K, V>> {
        let client = Client::open(url)?;
        let conn = client.get_connection()?;

        Ok(RedisCache {
            conn,
            _marker: PhantomData,
        })
    }
}

impl<K, V> Cache<K, V> for RedisCache<K, V>
where
    K: Eq + Hash + Send + Sync + Serialize + 'static,
    V: Send + Sync + Clone + Serialize + DeserializeOwned + 'static,
{
    // This function shouldn't be implemented, users can configure redis maxmium memory & lru
    // elimination strategy to achive lru-cache similar effect
    fn set_capacity(&mut self, _c: usize) {}

    fn get(&mut self, k: &K) -> Option<Cow<'_, V>> {
        if let Ok(ser_key) = serde_json::to_string(&k) {
            let cache_key = format!("{}::{}", CACHE_PREFIX, ser_key);
            if let Ok(res) = redis::cmd("GET")
                .arg(cache_key)
                .query::<String>(&mut self.conn)
            {
                return serde_json::from_str(&res).ok().map(|x| Cow::Owned(x));
            }
        }

        None
    }

    fn has(&mut self, k: &K) -> bool {
        self.get(k).is_some()
    }

    fn set(&mut self, k: K, v: V) {
        if let (Ok(ser_key), Ok(ser_val)) = (serde_json::to_string(&k), serde_json::to_string(&v)) {
            let cache_key = format!("{}::{}", CACHE_PREFIX, ser_key);
            let _ = redis::cmd("SET")
                .arg(cache_key)
                .arg(ser_val)
                .query::<String>(&mut self.conn);
        }
    }

    fn clear(&mut self) {
        let cache_key = format!("{}::*", CACHE_PREFIX);

        let script = format!(
            r#"
            EVAL "for i, name in ipairs(redis.call('KEYS', '{}')) do redis.call('del', name, 0); end" 0
        "#,
            cache_key
        );

        let script = redis::Script::new(&script);
        let _ = script
            .arg(1)
            .arg(2)
            .invoke::<Option<String>>(&mut self.conn);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_set_and_get() {
        let mut cache = RedisCache::new("redis://localhost:6379").unwrap();

        cache.set(vec!["alice", "/data1", "read"], false);
        assert!(cache.has(&vec!["alice", "/data1", "read"]));
        assert!(cache.get(&vec!["alice", "/data1", "read"]) == Some(Cow::Borrowed(&false)));
    }

    #[test]
    fn test_clear() {
        let mut cache = RedisCache::new("redis://localhost:6379").unwrap();

        cache.set(vec!["alice", "/data1", "read"], false);
        cache.clear();
        assert!(cache.get(&vec!["alice", "/data1", "read"]) == None);
    }
}