Skip to main content

rs_adk/session/
types.rs

1//! Session and SessionId types.
2
3use serde::{Deserialize, Serialize};
4
5use crate::events::Event;
6
7/// Unique identifier for a session.
8#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
9pub struct SessionId(String);
10
11impl SessionId {
12    /// Create a new random session ID.
13    pub fn new() -> Self {
14        Self(uuid::Uuid::new_v4().to_string())
15    }
16
17    /// Create a session ID from an existing string.
18    pub fn from_string(s: impl Into<String>) -> Self {
19        Self(s.into())
20    }
21
22    /// Get the ID as a string slice.
23    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/// A persistent session with metadata and state.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42pub struct Session {
43    /// Unique session identifier.
44    pub id: SessionId,
45    /// Application name this session belongs to.
46    pub app_name: String,
47    /// User identifier.
48    pub user_id: String,
49    /// Session state as key-value pairs.
50    pub state: std::collections::HashMap<String, serde_json::Value>,
51    /// When the session was created (ISO 8601).
52    pub created_at: String,
53    /// When the session was last updated (ISO 8601).
54    pub updated_at: String,
55    /// Events in this session (populated when loaded with events).
56    #[serde(default)]
57    pub events: Vec<Event>,
58}
59
60impl Session {
61    /// Create a new session.
62    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    /// Export session to a JSON-serializable format.
76    ///
77    /// Produces a complete snapshot of the session including metadata,
78    /// state, and all events. Suitable for backup, migration, or
79    /// transfer between session service backends.
80    pub fn export(&self) -> serde_json::Value {
81        serde_json::to_value(self).unwrap_or_else(|_| serde_json::json!({}))
82    }
83
84    /// Import a session from an exported JSON value.
85    ///
86    /// Reconstructs a [`Session`] from a value previously produced by
87    /// [`export`](Self::export). Returns an error if the JSON structure
88    /// does not match the expected session format.
89    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    /// Rewind the session to a previous invocation state.
95    /// All events after the given invocation ID are removed.
96    /// Returns the number of events removed.
97    pub fn rewind_to_invocation(&mut self, invocation_id: &str) -> usize {
98        // Find the last event index that belongs to the target invocation.
99        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, // Invocation ID not found — no change.
111        }
112    }
113}
114
115fn now_iso8601() -> String {
116    // Simple UTC timestamp without chrono dependency
117    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}