1use serde::{Deserialize, Serialize};
4
5use crate::events::Event;
6
7#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct SessionId(String);
10
11impl SessionId {
12 pub fn new() -> Self {
14 Self(uuid::Uuid::new_v4().to_string())
15 }
16
17 pub fn from_string(s: impl Into<String>) -> Self {
19 Self(s.into())
20 }
21
22 pub fn as_str(&self) -> &str {
24 &self.0
25 }
26}
27
28impl Default for SessionId {
29 fn default() -> Self {
30 Self::new()
31 }
32}
33
34impl std::fmt::Display for SessionId {
35 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
36 write!(f, "{}", self.0)
37 }
38}
39
40#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct Session {
43 pub id: SessionId,
45 pub app_name: String,
47 pub user_id: String,
49 pub state: std::collections::HashMap<String, serde_json::Value>,
51 pub created_at: String,
53 pub updated_at: String,
55 #[serde(default)]
57 pub events: Vec<Event>,
58}
59
60impl Session {
61 pub fn new(app_name: impl Into<String>, user_id: impl Into<String>) -> Self {
63 let now = now_iso8601();
64 Self {
65 id: SessionId::new(),
66 app_name: app_name.into(),
67 user_id: user_id.into(),
68 state: std::collections::HashMap::new(),
69 created_at: now.clone(),
70 updated_at: now,
71 events: Vec::new(),
72 }
73 }
74
75 pub fn export(&self) -> serde_json::Value {
81 serde_json::to_value(self).unwrap_or_else(|_| serde_json::json!({}))
82 }
83
84 pub fn import(value: &serde_json::Value) -> Result<Self, super::SessionError> {
90 serde_json::from_value(value.clone())
91 .map_err(|e| super::SessionError::Storage(format!("Import failed: {e}")))
92 }
93
94 pub fn rewind_to_invocation(&mut self, invocation_id: &str) -> usize {
98 let cutoff = self
100 .events
101 .iter()
102 .rposition(|e| e.invocation_id == invocation_id);
103
104 match cutoff {
105 Some(idx) => {
106 let removed = self.events.len() - (idx + 1);
107 self.events.truncate(idx + 1);
108 removed
109 }
110 None => 0, }
112 }
113}
114
115fn now_iso8601() -> String {
116 let dur = std::time::SystemTime::now()
118 .duration_since(std::time::UNIX_EPOCH)
119 .unwrap_or_default();
120 format!("{}Z", dur.as_secs())
121}
122
123#[cfg(test)]
124mod tests {
125 use super::*;
126
127 #[test]
128 fn session_id_display() {
129 let id = SessionId::from_string("test-123");
130 assert_eq!(id.to_string(), "test-123");
131 assert_eq!(id.as_str(), "test-123");
132 }
133
134 #[test]
135 fn session_id_equality() {
136 let a = SessionId::from_string("abc");
137 let b = SessionId::from_string("abc");
138 assert_eq!(a, b);
139 }
140
141 #[test]
142 fn session_new() {
143 let s = Session::new("my-app", "user-1");
144 assert_eq!(s.app_name, "my-app");
145 assert_eq!(s.user_id, "user-1");
146 assert!(s.state.is_empty());
147 }
148}