1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
7#[serde(tag = "type")]
8pub enum StorageConfig {
9 #[serde(rename = "none")]
10 None,
11
12 #[serde(rename = "file")]
13 File(FileStorageConfig),
14
15 #[serde(rename = "sqlite")]
16 Sqlite(SqliteStorageConfig),
17
18 #[serde(rename = "redis")]
19 Redis(RedisStorageConfig),
20}
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub struct FileStorageConfig {
24 pub path: String,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28pub struct SqliteStorageConfig {
29 pub path: String,
30
31 #[serde(default)]
32 pub table: Option<String>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct RedisStorageConfig {
37 pub url: String,
38
39 #[serde(default)]
40 pub prefix: Option<String>,
41
42 #[serde(default)]
43 pub ttl_seconds: Option<u64>,
44}
45
46impl Default for StorageConfig {
47 fn default() -> Self {
48 StorageConfig::None
49 }
50}
51
52impl StorageConfig {
53 pub fn none() -> Self {
54 StorageConfig::None
55 }
56
57 pub fn file(path: impl Into<String>) -> Self {
58 StorageConfig::File(FileStorageConfig { path: path.into() })
59 }
60
61 pub fn sqlite(path: impl Into<String>) -> Self {
62 StorageConfig::Sqlite(SqliteStorageConfig {
63 path: path.into(),
64 table: None,
65 })
66 }
67
68 pub fn redis(url: impl Into<String>) -> Self {
69 StorageConfig::Redis(RedisStorageConfig {
70 url: url.into(),
71 prefix: None,
72 ttl_seconds: None,
73 })
74 }
75
76 pub fn is_none(&self) -> bool {
77 matches!(self, StorageConfig::None)
78 }
79
80 pub fn is_file(&self) -> bool {
81 matches!(self, StorageConfig::File(_))
82 }
83
84 pub fn is_sqlite(&self) -> bool {
85 matches!(self, StorageConfig::Sqlite(_))
86 }
87
88 pub fn is_redis(&self) -> bool {
89 matches!(self, StorageConfig::Redis(_))
90 }
91
92 pub fn storage_type(&self) -> &'static str {
93 match self {
94 StorageConfig::None => "none",
95 StorageConfig::File(_) => "file",
96 StorageConfig::Sqlite(_) => "sqlite",
97 StorageConfig::Redis(_) => "redis",
98 }
99 }
100
101 pub fn get_path(&self) -> Option<&str> {
102 match self {
103 StorageConfig::File(c) => Some(&c.path),
104 StorageConfig::Sqlite(c) => Some(&c.path),
105 _ => None,
106 }
107 }
108
109 pub fn get_url(&self) -> Option<&str> {
110 match self {
111 StorageConfig::Redis(c) => Some(&c.url),
112 _ => None,
113 }
114 }
115
116 pub fn get_prefix(&self) -> &str {
117 match self {
118 StorageConfig::Redis(c) => c.prefix.as_deref().unwrap_or("agent:"),
119 _ => "agent:",
120 }
121 }
122
123 pub fn get_ttl(&self) -> Option<u64> {
124 match self {
125 StorageConfig::Redis(c) => c.ttl_seconds,
126 _ => None,
127 }
128 }
129
130 pub fn get_table(&self) -> Option<&str> {
131 match self {
132 StorageConfig::Sqlite(c) => c.table.as_deref(),
133 _ => None,
134 }
135 }
136
137 pub fn as_file(&self) -> Option<&FileStorageConfig> {
138 match self {
139 StorageConfig::File(c) => Some(c),
140 _ => None,
141 }
142 }
143
144 pub fn as_sqlite(&self) -> Option<&SqliteStorageConfig> {
145 match self {
146 StorageConfig::Sqlite(c) => Some(c),
147 _ => None,
148 }
149 }
150
151 pub fn as_redis(&self) -> Option<&RedisStorageConfig> {
152 match self {
153 StorageConfig::Redis(c) => Some(c),
154 _ => None,
155 }
156 }
157}
158
159impl RedisStorageConfig {
160 pub fn with_prefix(mut self, prefix: impl Into<String>) -> Self {
161 self.prefix = Some(prefix.into());
162 self
163 }
164
165 pub fn with_ttl(mut self, ttl_seconds: u64) -> Self {
166 self.ttl_seconds = Some(ttl_seconds);
167 self
168 }
169}
170
171impl SqliteStorageConfig {
172 pub fn with_table(mut self, table: impl Into<String>) -> Self {
173 self.table = Some(table.into());
174 self
175 }
176}
177
178use ai_agents_storage::StorageConfig as StorageStorageConfig;
179
180pub fn to_storage_config(config: &StorageConfig) -> StorageStorageConfig {
182 match config {
183 StorageConfig::None => StorageStorageConfig::None,
184 StorageConfig::File(fc) => StorageStorageConfig::File {
185 path: fc.path.clone(),
186 },
187 StorageConfig::Sqlite(sc) => StorageStorageConfig::Sqlite {
188 path: sc.path.clone(),
189 },
190 StorageConfig::Redis(rc) => StorageStorageConfig::Redis {
191 url: rc.url.clone(),
192 prefix: rc.prefix.clone(),
193 ttl_seconds: rc.ttl_seconds,
194 },
195 }
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn test_storage_config_default() {
204 let config = StorageConfig::default();
205 assert!(config.is_none());
206 assert!(!config.is_file());
207 assert!(!config.is_sqlite());
208 assert!(!config.is_redis());
209 assert_eq!(config.storage_type(), "none");
210 }
211
212 #[test]
213 fn test_storage_config_none_yaml() {
214 let yaml = "type: none\n";
215 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
216 assert!(config.is_none());
217 }
218
219 #[test]
220 fn test_storage_config_file() {
221 let yaml = r#"
222type: file
223path: "./data/sessions"
224"#;
225 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
226 assert!(config.is_file());
227 assert_eq!(config.get_path(), Some("./data/sessions"));
228 assert_eq!(config.storage_type(), "file");
229 }
230
231 #[test]
232 fn test_storage_config_file_builder() {
233 let config = StorageConfig::file("./data/sessions");
234 assert!(config.is_file());
235 assert_eq!(config.get_path(), Some("./data/sessions"));
236 }
237
238 #[test]
239 fn test_storage_config_sqlite() {
240 let yaml = r#"
241type: sqlite
242path: "./data/sessions.db"
243"#;
244 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
245 assert!(config.is_sqlite());
246 assert_eq!(config.get_path(), Some("./data/sessions.db"));
247 assert_eq!(config.storage_type(), "sqlite");
248 }
249
250 #[test]
251 fn test_storage_config_sqlite_with_table() {
252 let yaml = r#"
253type: sqlite
254path: "./data/sessions.db"
255table: "custom_sessions"
256"#;
257 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
258 assert!(config.is_sqlite());
259 assert_eq!(config.get_table(), Some("custom_sessions"));
260 }
261
262 #[test]
263 fn test_storage_config_sqlite_builder() {
264 let config = StorageConfig::sqlite("./data/sessions.db");
265 assert!(config.is_sqlite());
266 assert_eq!(config.get_path(), Some("./data/sessions.db"));
267 }
268
269 #[test]
270 fn test_storage_config_redis() {
271 let yaml = r#"
272type: redis
273url: "redis://localhost:6379"
274prefix: "myagent:"
275ttl_seconds: 86400
276"#;
277 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
278 assert!(config.is_redis());
279 assert_eq!(config.get_url(), Some("redis://localhost:6379"));
280 assert_eq!(config.get_prefix(), "myagent:");
281 assert_eq!(config.get_ttl(), Some(86400));
282 assert_eq!(config.storage_type(), "redis");
283 }
284
285 #[test]
286 fn test_storage_config_redis_builder() {
287 let config = StorageConfig::redis("redis://localhost:6379");
288 assert!(config.is_redis());
289 assert_eq!(config.get_url(), Some("redis://localhost:6379"));
290 assert_eq!(config.get_prefix(), "agent:");
291 assert_eq!(config.get_ttl(), None);
292 }
293
294 #[test]
295 fn test_storage_config_default_prefix() {
296 let config = StorageConfig::default();
297 assert_eq!(config.get_prefix(), "agent:");
298
299 let config = StorageConfig::file("./data");
300 assert_eq!(config.get_prefix(), "agent:");
301
302 let yaml = r#"
303type: redis
304url: "redis://localhost:6379"
305"#;
306 let config: StorageConfig = serde_yaml::from_str(yaml).unwrap();
307 assert_eq!(config.get_prefix(), "agent:");
308 }
309
310 #[test]
311 fn test_storage_config_accessors() {
312 let file_config = StorageConfig::file("./data");
313 assert!(file_config.as_file().is_some());
314 assert!(file_config.as_sqlite().is_none());
315 assert!(file_config.as_redis().is_none());
316
317 let sqlite_config = StorageConfig::sqlite("./data.db");
318 assert!(sqlite_config.as_file().is_none());
319 assert!(sqlite_config.as_sqlite().is_some());
320 assert!(sqlite_config.as_redis().is_none());
321
322 let redis_config = StorageConfig::redis("redis://localhost");
323 assert!(redis_config.as_file().is_none());
324 assert!(redis_config.as_sqlite().is_none());
325 assert!(redis_config.as_redis().is_some());
326 }
327
328 #[test]
329 fn test_redis_config_builder_methods() {
330 let config = RedisStorageConfig {
331 url: "redis://localhost:6379".to_string(),
332 prefix: None,
333 ttl_seconds: None,
334 }
335 .with_prefix("test:")
336 .with_ttl(3600);
337
338 assert_eq!(config.prefix, Some("test:".to_string()));
339 assert_eq!(config.ttl_seconds, Some(3600));
340 }
341
342 #[test]
343 fn test_sqlite_config_builder_methods() {
344 let config = SqliteStorageConfig {
345 path: "./data.db".to_string(),
346 table: None,
347 }
348 .with_table("custom_table");
349
350 assert_eq!(config.table, Some("custom_table".to_string()));
351 }
352
353 #[test]
354 fn test_storage_config_serialization() {
355 let config = StorageConfig::redis("redis://localhost:6379");
356 let yaml = serde_yaml::to_string(&config).unwrap();
357 assert!(yaml.contains("type: redis"));
358 assert!(yaml.contains("url: redis://localhost:6379"));
359 }
360
361 #[test]
362 fn test_to_storage_config_none() {
363 use ai_agents_storage::StorageConfig as SC;
364 let result = to_storage_config(&StorageConfig::None);
365 assert!(matches!(result, SC::None));
366 }
367
368 #[test]
369 fn test_to_storage_config_file() {
370 use ai_agents_storage::StorageConfig as SC;
371 let config = StorageConfig::file("./data/sessions");
372 let result = to_storage_config(&config);
373 match result {
374 SC::File { path } => assert_eq!(path, "./data/sessions"),
375 other => panic!("expected File, got {:?}", other),
376 }
377 }
378
379 #[test]
380 fn test_to_storage_config_sqlite() {
381 use ai_agents_storage::StorageConfig as SC;
382 let config = StorageConfig::sqlite("./data/db.sqlite");
383 let result = to_storage_config(&config);
384 match result {
385 SC::Sqlite { path } => assert_eq!(path, "./data/db.sqlite"),
386 other => panic!("expected Sqlite, got {:?}", other),
387 }
388 }
389}