actix-cloud 0.5.1

Actix Cloud is an all-in-one web framework based on Actix Web.
use std::time::Duration;

use async_trait::async_trait;

use crate::Result;

#[async_trait]
pub trait MemoryDB: Send + Sync {
    async fn set(&self, key: &str, value: &str) -> Result<()>;
    async fn get(&self, key: &str) -> Result<Option<String>>;
    async fn get_del(&self, key: &str) -> Result<Option<String>>;
    async fn get_ex(&self, key: &str, ttl: &Duration) -> Result<Option<String>>;
    async fn set_ex(&self, key: &str, value: &str, ttl: &Duration) -> Result<()>;
    async fn del(&self, key: &str) -> Result<bool>;
    async fn expire(&self, key: &str, ttl: i64) -> Result<bool>;
    async fn flush(&self) -> Result<()>;
    async fn keys(&self, key: &str) -> Result<Vec<String>>;
    async fn dels(&self, keys: &[String]) -> Result<u64>;
    async fn ttl(&self, key: &str) -> Result<Option<i64>>;
}

#[cfg(test)]
mod tests {
    use std::time::Duration;
    use tokio::time::sleep;

    use super::*;
    use crate::memorydb::default::DefaultBackend;

    #[cfg(feature = "redis")]
    async fn setup_redis() -> impl MemoryDB {
        crate::memorydb::redis::RedisBackend::new("redis://127.0.0.1:6379/0")
            .await
            .unwrap()
    }

    fn setup_default() -> impl MemoryDB {
        DefaultBackend::new(None)
    }

    #[tokio::test]
    async fn test_normal() {
        test_normal_fn("default", setup_default()).await;
        #[cfg(feature = "redis")]
        test_normal_fn("redis", setup_redis().await).await;
    }

    async fn test_normal_fn(name: &str, r: impl MemoryDB) {
        let key = "_actix_cloud_key1";
        let value1 = "value1";
        let value2 = "value2";

        println!("Backend: {}", name);

        let _ = r.del(key).await;

        assert_eq!(r.get(key).await.unwrap(), None);

        r.set(key, value1).await.unwrap();
        assert_eq!(r.get(key).await.unwrap().unwrap(), value1);
        r.set(key, value2).await.unwrap();
        assert_eq!(r.get(key).await.unwrap().unwrap(), value2);

        assert_eq!(r.del(key).await.unwrap(), true);
        assert_eq!(r.del(key).await.unwrap(), false);
        assert_eq!(r.get(key).await.unwrap(), None);
    }

    #[tokio::test]
    async fn test_ex() {
        test_ex_fn("default", setup_default()).await;
        #[cfg(feature = "redis")]
        test_ex_fn("redis", setup_redis().await).await;
    }

    async fn test_ex_fn(name: &str, r: impl MemoryDB) {
        let key = "_actix_cloud_key2";
        let value = "value";

        println!("Backend: {}", name);

        let _ = r.del(key).await;

        r.set(key, value).await.unwrap();
        assert_eq!(r.ttl(key).await.unwrap(), None);
        assert_eq!(r.get_del(key).await.unwrap().unwrap(), value);
        assert_eq!(r.get(key).await.unwrap(), None);

        r.set_ex(key, value, &Duration::from_secs(2)).await.unwrap();
        assert_eq!(r.get(key).await.unwrap().unwrap(), value);
        assert_eq!(r.ttl(key).await.unwrap(), Some(2));
        sleep(Duration::from_secs(1)).await;
        assert_eq!(r.ttl(key).await.unwrap(), Some(1));
        assert_eq!(
            r.get_ex(key, &Duration::from_secs(2))
                .await
                .unwrap()
                .unwrap(),
            value
        );
        assert_eq!(r.ttl(key).await.unwrap(), Some(2));
        sleep(Duration::from_secs(1)).await;
        assert_eq!(r.ttl(key).await.unwrap(), Some(1));
        assert_eq!(r.get(key).await.unwrap().unwrap(), value);
        sleep(Duration::from_secs(2)).await;
        assert_eq!(r.ttl(key).await.unwrap(), None);
        assert_eq!(r.get(key).await.unwrap(), None);
    }

    #[tokio::test]
    async fn test_expire() {
        test_expire_fn("default", setup_default()).await;
        #[cfg(feature = "redis")]
        test_expire_fn("redis", setup_redis().await).await;
    }

    async fn test_expire_fn(name: &str, r: impl MemoryDB) {
        let key = "_actix_cloud_key3";
        let value = "value";

        println!("Backend: {}", name);

        let _ = r.del(key).await;

        r.set(key, value).await.unwrap();
        assert_eq!(r.get(key).await.unwrap().unwrap(), value);
        assert_eq!(r.expire(key, 1).await.unwrap(), true);
        sleep(Duration::from_secs(2)).await;
        assert_eq!(r.get(key).await.unwrap(), None);
        assert_eq!(r.expire(key, 1).await.unwrap(), false);

        r.set_ex(key, value, &Duration::from_secs(1)).await.unwrap();
        assert_eq!(r.expire(key, 3).await.unwrap(), true);
        sleep(Duration::from_secs(2)).await;
        assert_eq!(r.get(key).await.unwrap().unwrap(), value);
        assert_eq!(r.expire(key, -1).await.unwrap(), true);
        assert_eq!(r.get(key).await.unwrap(), None);
        assert_eq!(r.expire(key, 0).await.unwrap(), false);
    }

    #[tokio::test]
    async fn test_batch() {
        test_batch_fn("default", setup_default()).await;
        #[cfg(feature = "redis")]
        test_batch_fn("redis", setup_redis().await).await;
    }

    async fn test_batch_fn(name: &str, r: impl MemoryDB) {
        let key1 = "_actix_cloud_bkey1";
        let key2 = "_actix_cloud_bkey2";
        let value = "value";

        println!("Backend: {}", name);

        let _ = r.del(key1).await;
        let _ = r.del(key2).await;

        r.set(key1, value).await.unwrap();
        r.set(key2, value).await.unwrap();
        let ret = r.keys("_actix_cl?ud_bkey*").await.unwrap();
        assert_eq!(ret.len(), 2);
        assert_eq!(
            (ret[0] == key1 && ret[1] == key2) || (ret[1] == key1 && ret[0] == key2),
            true
        );
        assert_eq!(
            r.dels(&vec![key1.to_owned(), key2.to_owned()])
                .await
                .unwrap(),
            2
        );
        assert_eq!(r.keys("_actix_cl?ud_bkey*").await.unwrap().len(), 0);
    }

    #[tokio::test]
    async fn test_default_capacity() {
        let key1 = "_actix_cloud_ckey1";
        let key2 = "_actix_cloud_ckey2";
        let key3 = "_actix_cloud_ckey3";
        let value = "value";

        let r = DefaultBackend::new(Some(2));
        let _ = r.del(key1).await;
        let _ = r.del(key2).await;
        let _ = r.del(key3).await;

        assert_eq!(r.set(key1, value).await.is_ok(), true);
        assert_eq!(r.set(key2, value).await.is_ok(), true);
        assert_eq!(r.set(key3, value).await.is_ok(), false);
        assert_eq!(r.set(key1, value).await.is_ok(), true);
        assert_eq!(r.set(key2, value).await.is_ok(), true);

        assert_eq!(
            r.set_ex(key2, value, &Duration::from_secs(1)).await.is_ok(),
            true
        );
        sleep(Duration::from_secs(2)).await;
        assert_eq!(r.set(key3, value).await.is_ok(), true);
        assert_eq!(r.get(key1).await.unwrap().unwrap(), value);
        assert_eq!(r.get(key2).await.unwrap(), None);

        assert_eq!(
            r.set_ex(key3, value, &Duration::from_secs(3)).await.is_ok(),
            true
        );
        sleep(Duration::from_secs(1)).await;
        assert_eq!(r.set(key2, value).await.is_ok(), true);
        assert_eq!(r.get(key1).await.unwrap().unwrap(), value);
        assert_eq!(r.get(key3).await.unwrap(), None);

        assert_eq!(r.del(key1).await.unwrap(), true);
        assert_eq!(r.del(key2).await.unwrap(), true);
        assert_eq!(
            r.set_ex(key1, value, &Duration::from_secs(3)).await.is_ok(),
            true
        );
        assert_eq!(
            r.set_ex(key2, value, &Duration::from_secs(2)).await.is_ok(),
            true
        );
        assert_eq!(
            r.set_ex(key3, value, &Duration::from_secs(2)).await.is_ok(),
            true
        );
        assert_eq!(r.get(key1).await.unwrap().unwrap(), value);
        assert_eq!(r.get(key2).await.unwrap(), None);
        assert_eq!(r.get(key3).await.unwrap().unwrap(), value);

        assert_eq!(r.del(key1).await.unwrap(), true);
        assert_eq!(r.del(key3).await.unwrap(), true);
    }
}