use serde::{Deserialize, Serialize};
use crate::events::Event;
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct SessionId(String);
impl SessionId {
pub fn new() -> Self {
Self(uuid::Uuid::new_v4().to_string())
}
pub fn from_string(s: impl Into<String>) -> Self {
Self(s.into())
}
pub fn as_str(&self) -> &str {
&self.0
}
}
impl Default for SessionId {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Display for SessionId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Session {
pub id: SessionId,
pub app_name: String,
pub user_id: String,
pub state: std::collections::HashMap<String, serde_json::Value>,
pub created_at: String,
pub updated_at: String,
#[serde(default)]
pub events: Vec<Event>,
}
impl Session {
pub fn new(app_name: impl Into<String>, user_id: impl Into<String>) -> Self {
let now = now_iso8601();
Self {
id: SessionId::new(),
app_name: app_name.into(),
user_id: user_id.into(),
state: std::collections::HashMap::new(),
created_at: now.clone(),
updated_at: now,
events: Vec::new(),
}
}
pub fn export(&self) -> serde_json::Value {
serde_json::to_value(self).unwrap_or_else(|_| serde_json::json!({}))
}
pub fn import(value: &serde_json::Value) -> Result<Self, super::SessionError> {
serde_json::from_value(value.clone())
.map_err(|e| super::SessionError::Storage(format!("Import failed: {e}")))
}
pub fn rewind_to_invocation(&mut self, invocation_id: &str) -> usize {
let cutoff = self
.events
.iter()
.rposition(|e| e.invocation_id == invocation_id);
match cutoff {
Some(idx) => {
let removed = self.events.len() - (idx + 1);
self.events.truncate(idx + 1);
removed
}
None => 0, }
}
}
fn now_iso8601() -> String {
let dur = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default();
format!("{}Z", dur.as_secs())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn session_id_display() {
let id = SessionId::from_string("test-123");
assert_eq!(id.to_string(), "test-123");
assert_eq!(id.as_str(), "test-123");
}
#[test]
fn session_id_equality() {
let a = SessionId::from_string("abc");
let b = SessionId::from_string("abc");
assert_eq!(a, b);
}
#[test]
fn session_new() {
let s = Session::new("my-app", "user-1");
assert_eq!(s.app_name, "my-app");
assert_eq!(s.user_id, "user-1");
assert!(s.state.is_empty());
}
}