Skip to main content

llm_optimizer_processor/state/
backend.rs

1//! State backend trait definition
2//!
3//! This module defines the core `StateBackend` trait that all state storage
4//! implementations must implement. The trait provides a simple key-value
5//! interface with support for listing keys by prefix.
6
7use async_trait::async_trait;
8
9use crate::error::StateResult;
10
11/// Core trait for state backend implementations
12///
13/// This trait defines the interface for storing and retrieving state in the
14/// stream processor. All operations are async to support both in-memory and
15/// persistent backends efficiently.
16///
17/// ## Key Design Principles
18///
19/// - **Byte-oriented**: Keys and values are byte slices for maximum flexibility
20/// - **Async-first**: All operations return futures for non-blocking I/O
21/// - **Simple interface**: CRUD operations plus prefix listing
22/// - **Error handling**: All operations return `StateResult<T>`
23///
24/// ## Implementation Requirements
25///
26/// Implementations must ensure:
27///
28/// - **Thread safety**: Concurrent access from multiple tasks
29/// - **Atomicity**: Individual operations are atomic
30/// - **Durability** (optional): Persistent backends should fsync writes
31/// - **Cleanup**: Automatic cleanup of expired/deleted state
32///
33/// ## Example Implementation
34///
35/// ```rust,no_run
36/// use async_trait::async_trait;
37/// use processor::state::{StateBackend, StateResult};
38/// use std::collections::HashMap;
39/// use tokio::sync::RwLock;
40///
41/// struct SimpleBackend {
42///     data: RwLock<HashMap<Vec<u8>, Vec<u8>>>,
43/// }
44///
45/// #[async_trait]
46/// impl StateBackend for SimpleBackend {
47///     async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>> {
48///         Ok(self.data.read().await.get(key).cloned())
49///     }
50///
51///     async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()> {
52///         self.data.write().await.insert(key.to_vec(), value.to_vec());
53///         Ok(())
54///     }
55///
56///     async fn delete(&self, key: &[u8]) -> StateResult<()> {
57///         self.data.write().await.remove(key);
58///         Ok(())
59///     }
60///
61///     async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>> {
62///         Ok(self.data.read().await
63///             .keys()
64///             .filter(|k| k.starts_with(prefix))
65///             .cloned()
66///             .collect())
67///     }
68/// }
69/// ```
70#[async_trait]
71pub trait StateBackend: Send + Sync {
72    /// Retrieve a value for the given key
73    ///
74    /// Returns `Ok(None)` if the key does not exist or has expired.
75    ///
76    /// # Arguments
77    ///
78    /// * `key` - The key to look up (as byte slice)
79    ///
80    /// # Returns
81    ///
82    /// * `Ok(Some(value))` - Value found
83    /// * `Ok(None)` - Key not found or expired
84    /// * `Err(StateError)` - Backend error occurred
85    ///
86    /// # Example
87    ///
88    /// ```rust,no_run
89    /// # use processor::state::{StateBackend, MemoryStateBackend};
90    /// # #[tokio::main]
91    /// # async fn main() -> anyhow::Result<()> {
92    /// let backend = MemoryStateBackend::new(None);
93    /// backend.put(b"key1", b"value1").await?;
94    ///
95    /// match backend.get(b"key1").await? {
96    ///     Some(value) => println!("Found: {:?}", value),
97    ///     None => println!("Not found"),
98    /// }
99    /// # Ok(())
100    /// # }
101    /// ```
102    async fn get(&self, key: &[u8]) -> StateResult<Option<Vec<u8>>>;
103
104    /// Store a value for the given key
105    ///
106    /// If the key already exists, its value is overwritten.
107    ///
108    /// # Arguments
109    ///
110    /// * `key` - The key to store (as byte slice)
111    /// * `value` - The value to store (as byte slice)
112    ///
113    /// # Returns
114    ///
115    /// * `Ok(())` - Value stored successfully
116    /// * `Err(StateError)` - Backend error occurred
117    ///
118    /// # Example
119    ///
120    /// ```rust,no_run
121    /// # use processor::state::{StateBackend, MemoryStateBackend};
122    /// # #[tokio::main]
123    /// # async fn main() -> anyhow::Result<()> {
124    /// let backend = MemoryStateBackend::new(None);
125    /// backend.put(b"window:123", b"aggregated_state").await?;
126    /// # Ok(())
127    /// # }
128    /// ```
129    async fn put(&self, key: &[u8], value: &[u8]) -> StateResult<()>;
130
131    /// Delete a key and its associated value
132    ///
133    /// This operation is idempotent - deleting a non-existent key is not an error.
134    ///
135    /// # Arguments
136    ///
137    /// * `key` - The key to delete (as byte slice)
138    ///
139    /// # Returns
140    ///
141    /// * `Ok(())` - Key deleted or didn't exist
142    /// * `Err(StateError)` - Backend error occurred
143    ///
144    /// # Example
145    ///
146    /// ```rust,no_run
147    /// # use processor::state::{StateBackend, MemoryStateBackend};
148    /// # #[tokio::main]
149    /// # async fn main() -> anyhow::Result<()> {
150    /// let backend = MemoryStateBackend::new(None);
151    /// backend.put(b"temp:123", b"data").await?;
152    /// backend.delete(b"temp:123").await?;
153    /// # Ok(())
154    /// # }
155    /// ```
156    async fn delete(&self, key: &[u8]) -> StateResult<()>;
157
158    /// List all keys with the given prefix
159    ///
160    /// This is useful for finding all windows for a specific key, or all state
161    /// entries of a certain type.
162    ///
163    /// # Arguments
164    ///
165    /// * `prefix` - The prefix to match (as byte slice). Empty prefix matches all keys.
166    ///
167    /// # Returns
168    ///
169    /// * `Ok(Vec<Vec<u8>>)` - List of matching keys
170    /// * `Err(StateError)` - Backend error occurred
171    ///
172    /// # Example
173    ///
174    /// ```rust,no_run
175    /// # use processor::state::{StateBackend, MemoryStateBackend};
176    /// # #[tokio::main]
177    /// # async fn main() -> anyhow::Result<()> {
178    /// let backend = MemoryStateBackend::new(None);
179    /// backend.put(b"window:1", b"data1").await?;
180    /// backend.put(b"window:2", b"data2").await?;
181    /// backend.put(b"meta:1", b"metadata").await?;
182    ///
183    /// // List all window keys
184    /// let window_keys = backend.list_keys(b"window:").await?;
185    /// assert_eq!(window_keys.len(), 2);
186    /// # Ok(())
187    /// # }
188    /// ```
189    async fn list_keys(&self, prefix: &[u8]) -> StateResult<Vec<Vec<u8>>>;
190
191    /// Clear all state from the backend
192    ///
193    /// This is primarily used for testing and cleanup. Use with caution.
194    ///
195    /// # Returns
196    ///
197    /// * `Ok(())` - All state cleared
198    /// * `Err(StateError)` - Backend error occurred
199    async fn clear(&self) -> StateResult<()> {
200        let keys = self.list_keys(b"").await?;
201        for key in keys {
202            self.delete(&key).await?;
203        }
204        Ok(())
205    }
206
207    /// Get the number of keys in the backend
208    ///
209    /// This is an optional operation that may be expensive for some backends.
210    ///
211    /// # Returns
212    ///
213    /// * `Ok(count)` - Number of keys
214    /// * `Err(StateError)` - Backend error occurred
215    async fn count(&self) -> StateResult<usize> {
216        Ok(self.list_keys(b"").await?.len())
217    }
218
219    /// Check if a key exists
220    ///
221    /// This is more efficient than `get()` when you only need to check existence.
222    ///
223    /// # Arguments
224    ///
225    /// * `key` - The key to check (as byte slice)
226    ///
227    /// # Returns
228    ///
229    /// * `Ok(true)` - Key exists
230    /// * `Ok(false)` - Key does not exist
231    /// * `Err(StateError)` - Backend error occurred
232    async fn contains(&self, key: &[u8]) -> StateResult<bool> {
233        Ok(self.get(key).await?.is_some())
234    }
235}
236
237#[cfg(test)]
238pub mod tests {
239    use super::*;
240
241    // Helper to create a test key
242    fn test_key(id: u32) -> Vec<u8> {
243        format!("test:{}", id).into_bytes()
244    }
245
246    // Generic tests that any StateBackend implementation should pass
247    pub async fn test_backend_basic_ops<B: StateBackend>(backend: B) {
248        // Test put and get
249        backend.put(&test_key(1), b"value1").await.unwrap();
250        let value = backend.get(&test_key(1)).await.unwrap();
251        assert_eq!(value, Some(b"value1".to_vec()));
252
253        // Test get non-existent key
254        let value = backend.get(&test_key(999)).await.unwrap();
255        assert_eq!(value, None);
256
257        // Test overwrite
258        backend.put(&test_key(1), b"new_value").await.unwrap();
259        let value = backend.get(&test_key(1)).await.unwrap();
260        assert_eq!(value, Some(b"new_value".to_vec()));
261
262        // Test delete
263        backend.delete(&test_key(1)).await.unwrap();
264        let value = backend.get(&test_key(1)).await.unwrap();
265        assert_eq!(value, None);
266
267        // Test delete non-existent (should not error)
268        backend.delete(&test_key(999)).await.unwrap();
269    }
270
271    pub async fn test_backend_list_keys<B: StateBackend>(backend: B) {
272        // Clear any existing data
273        backend.clear().await.unwrap();
274
275        // Add some keys with different prefixes
276        backend.put(b"window:1", b"w1").await.unwrap();
277        backend.put(b"window:2", b"w2").await.unwrap();
278        backend.put(b"window:3", b"w3").await.unwrap();
279        backend.put(b"meta:1", b"m1").await.unwrap();
280        backend.put(b"meta:2", b"m2").await.unwrap();
281
282        // Test prefix matching
283        let window_keys = backend.list_keys(b"window:").await.unwrap();
284        assert_eq!(window_keys.len(), 3);
285
286        let meta_keys = backend.list_keys(b"meta:").await.unwrap();
287        assert_eq!(meta_keys.len(), 2);
288
289        // Test empty prefix (all keys)
290        let all_keys = backend.list_keys(b"").await.unwrap();
291        assert_eq!(all_keys.len(), 5);
292    }
293
294    pub async fn test_backend_clear<B: StateBackend>(backend: B) {
295        backend.put(b"key1", b"val1").await.unwrap();
296        backend.put(b"key2", b"val2").await.unwrap();
297        backend.put(b"key3", b"val3").await.unwrap();
298
299        assert_eq!(backend.count().await.unwrap(), 3);
300
301        backend.clear().await.unwrap();
302
303        assert_eq!(backend.count().await.unwrap(), 0);
304    }
305
306    pub async fn test_backend_contains<B: StateBackend>(backend: B) {
307        backend.clear().await.unwrap();
308
309        assert!(!backend.contains(b"test").await.unwrap());
310
311        backend.put(b"test", b"value").await.unwrap();
312
313        assert!(backend.contains(b"test").await.unwrap());
314
315        backend.delete(b"test").await.unwrap();
316
317        assert!(!backend.contains(b"test").await.unwrap());
318    }
319}