dce-session 1.0.0

A session lib with user binding and renewable.
Documentation
use std::collections::HashMap;
use std::time::{SystemTime, UNIX_EPOCH};
use rand::random;
use sha2::{Digest, Sha256};
use serde::{Deserialize, Serialize};
use dce_util::mixed::{DceErr, DceResult};
#[cfg(feature = "async")]
use async_trait::async_trait;

pub const DEFAULT_ID_NAME: &str = "dcesid";
pub const DEFAULT_TTL_MINUTES: u16 = 60;

#[derive(Clone)]
pub struct Meta {
    sid_name: &'static str,
    ttl_minutes: u16,
    create_stamp: u64,
    sid: String,
    touches: Option<bool>,
    #[cfg(feature = "test")]
    sid_pool: Vec<String>,
}

impl Meta {
    pub fn sid_name(&self) -> &str {
        self.sid_name
    }

    pub fn ttl_minutes(&self) -> u16 {
        self.ttl_minutes
    }

    pub fn ttl_seconds(&self) -> u32 {
        self.ttl_minutes as u32 * 60
    }

    pub fn create_stamp(&self) -> u64 {
        self.create_stamp
    }

    pub fn sid(&self) -> &str {
        self.sid.as_str()
    }

    pub fn config(&mut self, sid_name: Option<&'static str>, ttl_minutes: Option<u16>) {
        if let Some(sid_name) = sid_name { self.sid_name = sid_name; }
        if let Some(ttl_minutes) = ttl_minutes { self.ttl_minutes = ttl_minutes; }
    }

    pub fn renew(&mut self, sid: Option<String>) -> DceResult<()> {
        self.touches = None;
        Ok(if let Some(sid) = sid {
            (self.ttl_minutes, self.create_stamp) = Self::parse_sid(sid.as_str())?;
            self.sid = sid;
        } else {
            (self.sid, self.create_stamp) = Self::generate_id(self.ttl_minutes, #[cfg(feature = "test")] &mut self.sid_pool)?;
        })
    }

    pub fn new(ttl_minutes: u16) -> DceResult<Self> {
        #[cfg(feature = "test")] let mut sid_pool = vec![];
        let (sid, create_stamp) = Self::generate_id(ttl_minutes, #[cfg(feature = "test")] &mut sid_pool)?;
        Ok(Self {ttl_minutes, create_stamp, sid, sid_name: DEFAULT_ID_NAME, touches: None, #[cfg(feature = "test")] sid_pool })
    }

    pub fn new_with_sid(mut sid_pool: Vec<String>) -> DceResult<Self> {
        assert!(! sid_pool.is_empty());
        let sid = sid_pool.remove(0);
        let (ttl_minutes, create_stamp) = Self::parse_sid(&sid)?;
        Ok(Self {ttl_minutes, create_stamp, sid, sid_name: DEFAULT_ID_NAME, touches: None, #[cfg(feature = "test")] sid_pool })
    }

    fn parse_sid(sid: &str) -> DceResult<(u16, u64)> {
        const MIN_SID_LEN: usize = 76;
        if sid.len() < MIN_SID_LEN { return DceErr::closed0_wrap(format!(r#"invalid sid "{}", less then {} chars"#, sid, MIN_SID_LEN)); }
        let ttl_minutes = u16::from_str_radix(&sid[64..68], 16).map_err(DceErr::closed0)?;
        let create_stamp = u64::from_str_radix(&sid[68..], 16).map_err(DceErr::closed0)?;
        Ok((ttl_minutes, create_stamp))
    }

    #[allow(unused)]
    fn generate_id(ttl_minutes: u16, #[cfg(feature = "test")] sid_pool: &mut Vec<String>) -> DceResult<(String, u64)> {
        #[cfg(not(feature = "test"))] return Self::gen_id(ttl_minutes);
        #[cfg(feature = "test")] return {
            let sid = sid_pool.remove(0);
            Self::parse_sid(&sid).map(|(_, create_stamp)| (sid, create_stamp))
        };
    }

    pub fn gen_id(ttl_minutes: u16) -> DceResult<(String, u64)> {
        let now = SystemTime::now().duration_since(UNIX_EPOCH).map_err(DceErr::closed0)?;
        let now_secs = now.as_secs();
        let mut hasher = Sha256::new();
        hasher.update(format!("{}-{}", now.as_nanos(), random::<usize>()).as_bytes());
        Ok((format!("{:X}{:04X}{:X}", hasher.finalize(), ttl_minutes, now_secs), now_secs))
    }
}


macro_rules! auto_async {
    { $($(#[$($meta:meta),+])+, $($async: ident)?);+ } => {
        #[cfg_attr(feature = "async", async_trait)]
        pub trait Session: Sized {
            fn new(ttl_minutes: u16) -> DceResult<Self>;
            
            fn new_with_id(sid_pool: Vec<String>) -> DceResult<Self>;
        
            fn meta(&self) -> &Meta;
        
            fn meta_mut(&mut self) -> &mut Meta;
            
            fn id(&self) -> &str {
                self.meta().sid()
            }
        
            fn key(&self) -> String {
                Self::gen_key(self.meta().sid_name, self.id())
            }
        
            fn gen_key(sid_prefix: &str, id: &str) -> String;
        
            $($(#[$($meta),+])+
            $($async)? fn silent_set(&mut self, field: &str, value: &str) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn silent_get(&mut self, field: &str) -> DceResult<String>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn silent_del(&mut self, field: &str) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn destroy(&mut self) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn touch(&mut self) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn load(&mut self, data: HashMap<String, String>) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn raw(&mut self) -> DceResult<HashMap<String, String>>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn ttl_passed(&mut self) -> DceResult<u32>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn set<T: Serialize + Sync>(&mut self, field: &str, value: &T) -> DceResult<bool> {
                let value = serde_json::to_string::<T>(value).map_err(DceErr::closed0)?;
                #[cfg(feature = "async")]
                match self.silent_set(field, &value).await {
                    Ok(res) if res => self.try_touch().await,
                    result => result,
                }
                #[cfg(not(feature = "async"))]
                match self.silent_set(field, &value) {
                    Ok(res) if res => self.try_touch(),
                    result => result,
                }
            } )+
        
            $($(#[$($meta),+])+
            $($async)? fn get<T: for<'a> Deserialize<'a> + Send>(&mut self, field: &str) -> DceResult<T> {
                #[cfg(feature = "async")]
                let value = self.silent_get(field).await;
                #[cfg(not(feature = "async"))]
                let value = self.silent_get(field);
                if value.is_ok() {
                    #[cfg(feature = "async")]
                    self.try_touch().await?;
                    #[cfg(not(feature = "async"))]
                    self.try_touch()?;
                }
                serde_json::from_str(&value?).map_err(DceErr::closed0)
            } )+
        
            $($(#[$($meta),+])+
            $($async)? fn del(&mut self, field: &str) -> DceResult<bool> {
                #[cfg(feature = "async")]
                match self.silent_del(field).await {
                    Ok(res) if res => self.try_touch().await,
                    result => result,
                }
                #[cfg(not(feature = "async"))]
                match self.silent_del(field) {
                    Ok(res) if res => self.try_touch(),
                    result => result,
                }
            } )+
        
            $($(#[$($meta),+])+
            $($async)? fn try_touch(&mut self) -> DceResult<bool> {
                if self.meta().touches == None {
                    #[cfg(feature = "async")]
                    let touches = self.touch().await?;
                    #[cfg(not(feature = "async"))]
                    let touches = self.touch()?;
                    self.meta_mut().touches = Some(touches);
                }
                Ok(matches!(self.meta().touches, Some(true)))
            } )+
            
            $($(#[$($meta),+])+
            $($async)? fn renew(&mut self, filters: HashMap<String, Option<String>>) -> DceResult<bool> where Self: Send {
                #[cfg(feature = "async")]
                return renew(self, filters).await;
                #[cfg(not(feature = "async"))]
                return renew(self, filters);
            } )+
        
            fn clone_with_id(&mut self, id: String) -> DceResult<Self> where Self: Clone {
                clone_with_id(self, id)
            }

            fn cloned_mut(&mut self) -> &mut Option<Box<Self>>;
        
            $($(#[$($meta),+])+
            $($async)? fn cloned_silent_set(&mut self, field: &str, value: &str) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn cloned_destroy(&mut self) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn cloned_touch(&mut self) -> DceResult<bool>; )+
        
            $($(#[$($meta),+])+
            $($async)? fn cloned_ttl_passed(&mut self) -> DceResult<u32>; )+
        }
        
        pub fn clone_with_id<S: Session + Clone>(session: &mut S, id: String) -> DceResult<S> {
            let mut cloned = session.clone();
            cloned.meta_mut().renew(Some(id))?;
            Ok(cloned)
        }
        
        $($(#[$($meta),+])+
        pub $($async)? fn renew<S: Session + Send>(session: &mut S, filters: HashMap<String, Option<String>>) -> DceResult<bool> {
            #[cfg(feature = "async")]
            let mut raw = session.raw().await?;
            #[cfg(not(feature = "async"))]
            let mut raw = session.raw()?;
            for (k, v) in filters {
                if let Some(v) = v {
                    let _ = raw.insert(k, v);
                } else {
                    let _ = raw.remove(&k);
                }
            }
            session.meta_mut().renew(None)?;
            if raw.is_empty() {
                return Ok(true);
            }
            #[cfg(feature = "async")]
            session.load(raw).await?;
            #[cfg(not(feature = "async"))]
            session.load(raw)?;
            #[cfg(feature = "async")]
            return session.try_touch().await;
            #[cfg(not(feature = "async"))]
            return session.try_touch();
        } )+
    };
}

auto_async!{ #[cfg(feature = "async")], async; #[cfg(not(feature = "async"))], }