Skip to main content

xz_memory_engine/
config.rs

1use std::sync::Arc;
2
3use serde::{Deserialize, Serialize};
4
5use xz_memory_core::StoreError;
6use xz_memory_core::traits::store::EntryStore;
7
8use crate::backends::InMemoryEntryStore;
9
10#[cfg(feature = "sqlite-backend")]
11use crate::backends::SqliteEntryStore;
12
13#[cfg(feature = "markdown-backend")]
14use crate::backends::MarkdownEntryStore;
15
16/// Configuration for the memory system.
17#[derive(Debug, Clone, Serialize, Deserialize, Default)]
18pub struct MemoryConfig {
19    pub storage: StorageConfig,
20}
21
22impl MemoryConfig {
23    /// Build an [`EntryStore`] from this configuration.
24    ///
25    /// # Errors
26    ///
27    /// Returns [`StoreError::Config`] if the `backend` string is unrecognised.
28    /// Returns [`StoreError::Backend`] if the backend fails to initialise (e.g. SQLite connection error).
29    pub async fn build(&self) -> Result<Arc<dyn EntryStore>, StoreError> {
30        match self.storage.backend.as_str() {
31            "memory" => Ok(Arc::new(InMemoryEntryStore::new())),
32            #[cfg(feature = "sqlite-backend")]
33            "sqlite" => Ok(Arc::new(SqliteEntryStore::new(&self.storage.path).await?)),
34            #[cfg(feature = "markdown-backend")]
35            "markdown" => {
36                Ok(Arc::new(MarkdownEntryStore::new(std::path::PathBuf::from(&self.storage.path))))
37            }
38            unknown => Err(StoreError::Config(format!("Unknown backend: {}", unknown))),
39        }
40    }
41}
42
43/// Storage backend configuration.
44#[derive(Debug, Clone, Serialize, Deserialize)]
45pub struct StorageConfig {
46    /// Backend type identifier (e.g. `"sqlite"`, `"memory"`).
47    pub backend: String,
48    /// Path to the backend resource (file path, directory, or connection string).
49    pub path: String,
50    /// Number of connections in the backend connection pool.
51    pub pool_size: u32,
52}
53
54impl Default for StorageConfig {
55    fn default() -> Self {
56        Self { backend: "sqlite".into(), path: "./data/memory.db".into(), pool_size: 5 }
57    }
58}
59
60#[cfg(test)]
61mod tests {
62    use super::*;
63
64    #[tokio::test]
65    async fn build_memory_backend() {
66        let config = MemoryConfig {
67            storage: StorageConfig { backend: "memory".into(), path: String::new(), pool_size: 1 },
68        };
69        let store = config.build().await.unwrap();
70        let entry = xz_memory_core::types::entry::Entry {
71            id: "test-1".into(),
72            partition: "p".into(),
73            body: "hello".into(),
74            recorded_at: 100,
75        };
76        store.append(entry).await.unwrap();
77        let results = store
78            .query(
79                "p",
80                &xz_memory_core::types::entry::TimeRange { start: None, end: None },
81                &xz_memory_core::types::entry::QueryOptions {
82                    limit: 10,
83                    sort: xz_memory_core::types::entry::SortOrder::Ascending,
84                },
85            )
86            .await
87            .unwrap();
88        assert_eq!(results.len(), 1);
89        assert_eq!(results[0].body, "hello");
90    }
91
92    #[cfg(feature = "sqlite-backend")]
93    #[tokio::test]
94    async fn build_sqlite_backend() {
95        let config = MemoryConfig {
96            storage: StorageConfig {
97                backend: "sqlite".into(),
98                path: "sqlite::memory:".into(),
99                pool_size: 1,
100            },
101        };
102        let store = config.build().await.unwrap();
103        let entry = xz_memory_core::types::entry::Entry {
104            id: "sqlite-test-1".into(),
105            partition: "p".into(),
106            body: "from sqlite".into(),
107            recorded_at: 200,
108        };
109        store.append(entry).await.unwrap();
110        let results = store
111            .query(
112                "p",
113                &xz_memory_core::types::entry::TimeRange { start: None, end: None },
114                &xz_memory_core::types::entry::QueryOptions {
115                    limit: 10,
116                    sort: xz_memory_core::types::entry::SortOrder::Ascending,
117                },
118            )
119            .await
120            .unwrap();
121        assert_eq!(results.len(), 1);
122        assert_eq!(results[0].body, "from sqlite");
123    }
124
125    #[tokio::test]
126    async fn build_unknown_backend_returns_config_error() {
127        let config = MemoryConfig {
128            storage: StorageConfig {
129                backend: "postgres".into(),
130                path: String::new(),
131                pool_size: 1,
132            },
133        };
134        match config.build().await {
135            Err(e) => {
136                assert!(matches!(e, StoreError::Config(_)));
137                assert!(e.to_string().contains("postgres"));
138            }
139            Ok(_) => panic!("expected Err, got Ok"),
140        }
141    }
142}