actix-cloud 0.5.1

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

use actix_web::cookie::time::Duration;
use serde_json::{Map, Value};

use super::{utils::generate_session_key, SessionKey};
use crate::{memorydb::MemoryDB, Result};

pub(crate) type SessionState = Map<String, Value>;

#[derive(Clone)]
pub struct SessionStore {
    configuration: CacheConfiguration,
    client: Arc<dyn MemoryDB>,
}

#[derive(Clone)]
struct CacheConfiguration {
    cache_keygen: Arc<dyn Fn(&str) -> String + Send + Sync>,
}

impl Default for CacheConfiguration {
    fn default() -> Self {
        Self {
            cache_keygen: Arc::new(str::to_owned),
        }
    }
}

impl SessionStore {
    pub fn new(client: Arc<dyn MemoryDB>) -> Self {
        Self {
            client,
            configuration: CacheConfiguration::default(),
        }
    }

    /// Set a custom cache key generation strategy, expecting a session key as input.
    pub fn cache_keygen<F>(&mut self, keygen: F)
    where
        F: Fn(&str) -> String + 'static + Send + Sync,
    {
        self.configuration.cache_keygen = Arc::new(keygen);
    }

    pub async fn load(&self, session_key: &SessionKey) -> Result<Option<SessionState>> {
        let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());
        let value = self.client.get(&cache_key).await?;

        match value {
            None => Ok(None),
            Some(value) => Ok(serde_json::from_str(&value).ok()),
        }
    }

    pub async fn save(
        &self,
        session_state: SessionState,
        id: &Option<String>,
        ttl: &Duration,
    ) -> Result<SessionKey> {
        let body = serde_json::to_string(&session_state)?;
        let session_key = generate_session_key();
        let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

        self.client
            .set_ex(&cache_key, &body, &Self::parse_ttl(ttl))
            .await?;

        if let Some(id) = id {
            let key =
                (self.configuration.cache_keygen)(&format!("{}_{}", id, session_key.as_ref()));
            self.client.set_ex(&key, "1", &Self::parse_ttl(ttl)).await?;
        }

        Ok(session_key)
    }

    pub async fn update(
        &self,
        session_key: SessionKey,
        session_state: SessionState,
        id: &Option<String>,
        ttl: &Duration,
    ) -> Result<SessionKey> {
        let body = serde_json::to_string(&session_state)?;
        let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

        self.client
            .set_ex(&cache_key, &body, &Self::parse_ttl(ttl))
            .await?;

        if let Some(id) = id {
            let key =
                (self.configuration.cache_keygen)(&format!("{}_{}", id, session_key.as_ref()));
            self.client.set_ex(&key, "1", &Self::parse_ttl(ttl)).await?;
        }

        Ok(session_key)
    }

    pub async fn update_ttl(
        &self,
        session_key: &SessionKey,
        id: &Option<String>,
        ttl: &Duration,
    ) -> Result<()> {
        let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

        self.client.expire(&cache_key, ttl.whole_seconds()).await?;
        if let Some(id) = id {
            let key =
                (self.configuration.cache_keygen)(&format!("{}_{}", id, session_key.as_ref()));
            self.client.expire(&key, ttl.whole_seconds()).await?;
        }
        Ok(())
    }

    pub async fn delete(&self, session_key: &SessionKey, id: &Option<String>) -> Result<()> {
        let cache_key = (self.configuration.cache_keygen)(session_key.as_ref());

        self.client.del(&cache_key).await?;
        if let Some(id) = id {
            let key =
                (self.configuration.cache_keygen)(&format!("{}_{}", id, session_key.as_ref()));
            self.client.del(&key).await?;
        }
        Ok(())
    }

    fn parse_ttl(t: &Duration) -> time::Duration {
        let t = t.whole_seconds();
        let t = if t < 0 { 0 } else { t as u64 };
        time::Duration::from_secs(t)
    }
}