kegani 0.1.0

A developer-friendly, ergonomic, production-ready Rust web framework
Documentation
//! Session management for Kegani
//!
//! Provides in-memory session storage.

use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use std::sync::{Arc, Mutex};

/// Session data
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
    /// Session ID
    pub id: String,
    /// Session data
    pub data: HashMap<String, serde_json::Value>,
    /// Creation timestamp
    pub created_at: u64,
    /// Last access timestamp
    pub accessed_at: u64,
    /// Expiration timestamp
    pub expires_at: u64,
}

impl Session {
    /// Create a new session
    pub fn new(id: String, ttl: Duration) -> Self {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();

        Self {
            id,
            data: HashMap::new(),
            created_at: now,
            accessed_at: now,
            expires_at: now + ttl.as_secs(),
        }
    }

    /// Check if session is expired
    pub fn is_expired(&self) -> bool {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        now > self.expires_at
    }

    /// Get a value
    pub fn get(&self, key: &str) -> Option<&serde_json::Value> {
        self.data.get(key)
    }

    /// Set a value
    pub fn set(&mut self, key: &str, value: serde_json::Value) {
        let now = SystemTime::now()
            .duration_since(UNIX_EPOCH)
            .unwrap()
            .as_secs();
        self.accessed_at = now;
        self.data.insert(key.to_string(), value);
    }

    /// Remove a value
    pub fn remove(&mut self, key: &str) {
        self.data.remove(key);
    }
}

/// Session store
pub struct SessionStore {
    sessions: Mutex<HashMap<String, Session>>,
}

impl SessionStore {
    /// Create a new session store
    pub fn new() -> Self {
        Self {
            sessions: Mutex::new(HashMap::new()),
        }
    }

    /// Create a session
    pub async fn create(&self, session: &Session) -> Result<(), SessionError> {
        let mut sessions = self.sessions.lock().unwrap();
        sessions.insert(session.id.clone(), session.clone());
        Ok(())
    }

    /// Get a session by ID
    pub async fn get(&self, id: &str) -> Result<Option<Session>, SessionError> {
        let sessions = self.sessions.lock().unwrap();
        Ok(sessions.get(id).cloned().filter(|s| !s.is_expired()))
    }

    /// Update a session
    pub async fn update(&self, session: &Session) -> Result<(), SessionError> {
        let mut sessions = self.sessions.lock().unwrap();
        sessions.insert(session.id.clone(), session.clone());
        Ok(())
    }

    /// Delete a session
    pub async fn delete(&self, id: &str) -> Result<(), SessionError> {
        let mut sessions = self.sessions.lock().unwrap();
        sessions.remove(id);
        Ok(())
    }

    /// Touch a session
    pub async fn touch(&self, id: &str) -> Result<(), SessionError> {
        let mut sessions = self.sessions.lock().unwrap();
        if let Some(session) = sessions.get_mut(id) {
            session.accessed_at = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap()
                .as_secs();
        }
        Ok(())
    }
}

impl Default for SessionStore {
    fn default() -> Self {
        Self::new()
    }
}

/// Session configuration
#[derive(Debug, Clone)]
pub struct SessionConfig {
    pub name: String,
    pub ttl: Duration,
    pub secure: bool,
    pub http_only: bool,
    pub same_site: String,
}

impl Default for SessionConfig {
    fn default() -> Self {
        Self {
            name: "kegani_session".to_string(),
            ttl: Duration::from_secs(3600 * 24),
            secure: true,
            http_only: true,
            same_site: "lax".to_string(),
        }
    }
}

/// Session error
#[derive(Debug)]
pub enum SessionError {
    NotFound,
    Expired,
    LockFailed,
}

impl std::fmt::Display for SessionError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            SessionError::NotFound => write!(f, "Session not found"),
            SessionError::Expired => write!(f, "Session expired"),
            SessionError::LockFailed => write!(f, "Failed to acquire session lock"),
        }
    }
}

impl std::error::Error for SessionError {}