gemini_adk_rs/session/
mod.rs1mod memory;
7#[cfg(feature = "postgres-sessions")]
8mod postgres;
9mod sqlite;
10mod types;
11#[cfg(feature = "vertex-ai-sessions")]
12mod vertex_ai;
13
14#[cfg(feature = "database-sessions")]
15mod database;
16#[cfg(feature = "database-sessions")]
17pub use database::DatabaseSessionService;
18
19pub mod db_schema;
20
21pub use memory::InMemorySessionService;
22#[cfg(feature = "postgres-sessions")]
23pub use postgres::{PostgresSessionConfig, PostgresSessionService};
24pub use sqlite::{SqliteSessionConfig, SqliteSessionService};
25pub use types::{Session, SessionId};
26#[cfg(feature = "vertex-ai-sessions")]
27pub use vertex_ai::{VertexAiSessionConfig, VertexAiSessionService};
28
29use async_trait::async_trait;
30
31use crate::events::Event;
32
33#[derive(Debug, thiserror::Error)]
35pub enum SessionError {
36 #[error("Session not found: {0}")]
38 NotFound(SessionId),
39 #[error("Storage error: {0}")]
41 Storage(String),
42}
43
44#[async_trait]
48pub trait SessionService: Send + Sync {
49 async fn create_session(&self, app_name: &str, user_id: &str) -> Result<Session, SessionError>;
51
52 async fn get_session(&self, id: &SessionId) -> Result<Option<Session>, SessionError>;
54
55 async fn list_sessions(
57 &self,
58 app_name: &str,
59 user_id: &str,
60 ) -> Result<Vec<Session>, SessionError>;
61
62 async fn delete_session(&self, id: &SessionId) -> Result<(), SessionError>;
64
65 async fn append_event(&self, id: &SessionId, event: Event) -> Result<(), SessionError>;
67
68 async fn get_events(&self, id: &SessionId) -> Result<Vec<Event>, SessionError>;
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[tokio::test]
77 async fn create_and_get_session() {
78 let svc = InMemorySessionService::new();
79 let session = svc.create_session("my-app", "user-1").await.unwrap();
80 assert_eq!(session.app_name, "my-app");
81 assert_eq!(session.user_id, "user-1");
82
83 let fetched = svc.get_session(&session.id).await.unwrap();
84 assert!(fetched.is_some());
85 assert_eq!(fetched.unwrap().id, session.id);
86 }
87
88 #[tokio::test]
89 async fn list_sessions_filters_by_app_and_user() {
90 let svc = InMemorySessionService::new();
91 svc.create_session("app-a", "user-1").await.unwrap();
92 svc.create_session("app-a", "user-1").await.unwrap();
93 svc.create_session("app-a", "user-2").await.unwrap();
94 svc.create_session("app-b", "user-1").await.unwrap();
95
96 let list = svc.list_sessions("app-a", "user-1").await.unwrap();
97 assert_eq!(list.len(), 2);
98 }
99
100 #[tokio::test]
101 async fn delete_session_removes_it() {
102 let svc = InMemorySessionService::new();
103 let session = svc.create_session("app", "user").await.unwrap();
104 svc.delete_session(&session.id).await.unwrap();
105 let fetched = svc.get_session(&session.id).await.unwrap();
106 assert!(fetched.is_none());
107 }
108
109 #[tokio::test]
110 async fn append_and_get_events() {
111 let svc = InMemorySessionService::new();
112 let session = svc.create_session("app", "user").await.unwrap();
113
114 let event = Event::new("user", Some("Hello!".to_string()));
115 svc.append_event(&session.id, event).await.unwrap();
116
117 let events = svc.get_events(&session.id).await.unwrap();
118 assert_eq!(events.len(), 1);
119 assert_eq!(events[0].author, "user");
120 }
121
122 #[tokio::test]
123 async fn append_event_to_nonexistent_session() {
124 let svc = InMemorySessionService::new();
125 let id = SessionId::new();
126 let event = Event::new("user", Some("Hello".to_string()));
127 let result = svc.append_event(&id, event).await;
128 assert!(result.is_err());
129 }
130
131 #[tokio::test]
132 async fn session_service_is_object_safe() {
133 fn _assert(_: &dyn SessionService) {}
134 }
135}
136
137#[cfg(test)]
138mod schema_tests {
139 use super::db_schema;
140
141 #[test]
142 fn postgres_schema_has_tables() {
143 assert!(db_schema::POSTGRES_SCHEMA.contains("CREATE TABLE IF NOT EXISTS sessions"));
144 assert!(db_schema::POSTGRES_SCHEMA.contains("CREATE TABLE IF NOT EXISTS events"));
145 }
146
147 #[test]
148 fn sqlite_schema_has_tables() {
149 assert!(db_schema::SQLITE_SCHEMA.contains("CREATE TABLE IF NOT EXISTS sessions"));
150 assert!(db_schema::SQLITE_SCHEMA.contains("CREATE TABLE IF NOT EXISTS events"));
151 }
152
153 #[test]
154 fn postgres_schema_has_indexes() {
155 assert!(db_schema::POSTGRES_SCHEMA.contains("idx_events_session"));
156 assert!(db_schema::POSTGRES_SCHEMA.contains("idx_sessions_app_user"));
157 }
158
159 #[test]
160 fn sqlite_schema_has_indexes() {
161 assert!(db_schema::SQLITE_SCHEMA.contains("idx_events_session"));
162 assert!(db_schema::SQLITE_SCHEMA.contains("idx_sessions_app_user"));
163 }
164
165 #[test]
166 fn postgres_schema_uses_jsonb() {
167 assert!(db_schema::POSTGRES_SCHEMA.contains("JSONB"));
168 }
169
170 #[test]
171 fn sqlite_schema_uses_text_for_json() {
172 assert!(!db_schema::SQLITE_SCHEMA.contains("JSONB"));
174 }
175}