stygian_plugin/storage/
memory_idempotency_store.rs1use crate::Result;
4use crate::domain::{ExtractionResult, IdempotencyKey};
5use crate::ports::IdempotencyKeyStore;
6use async_trait::async_trait;
7use std::collections::HashMap;
8use std::sync::Arc;
9use tokio::sync::RwLock;
10
11pub struct MemoryIdempotencyStore {
25 results: Arc<RwLock<HashMap<IdempotencyKey, ExtractionResult>>>,
26}
27
28impl MemoryIdempotencyStore {
29 #[must_use]
31 pub fn new() -> Self {
32 Self {
33 results: Arc::new(RwLock::new(HashMap::new())),
34 }
35 }
36}
37
38impl Default for MemoryIdempotencyStore {
39 fn default() -> Self {
40 Self::new()
41 }
42}
43
44#[async_trait]
45impl IdempotencyKeyStore for MemoryIdempotencyStore {
46 async fn store_result(&self, key: &IdempotencyKey, result: &ExtractionResult) -> Result<()> {
47 self.results.write().await.insert(*key, result.clone());
48 Ok(())
49 }
50
51 async fn get_result(&self, key: &IdempotencyKey) -> Result<Option<ExtractionResult>> {
52 let results = self.results.read().await;
53 Ok(results.get(key).cloned())
54 }
55
56 async fn delete_result(&self, key: &IdempotencyKey) -> Result<()> {
57 self.results.write().await.remove(key);
58 Ok(())
59 }
60
61 async fn clear_all(&self) -> Result<()> {
62 self.results.write().await.clear();
63 Ok(())
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[tokio::test]
72 async fn test_store_and_get_result() -> std::result::Result<(), Box<dyn std::error::Error>> {
73 let store = MemoryIdempotencyStore::new();
74 let key = IdempotencyKey::new();
75 let result = ExtractionResult::new(key);
76
77 store.store_result(&key, &result).await?;
78 let retrieved = store.get_result(&key).await?;
79
80 assert!(retrieved.is_some());
81 let retrieved = retrieved.ok_or("expected Some result")?;
82 assert_eq!(retrieved.metadata.idempotency_key, key);
83 Ok(())
84 }
85
86 #[tokio::test]
87 async fn test_get_nonexistent_result() -> std::result::Result<(), Box<dyn std::error::Error>> {
88 let store = MemoryIdempotencyStore::new();
89 let key = IdempotencyKey::new();
90
91 let result = store.get_result(&key).await?;
92 assert!(result.is_none());
93 Ok(())
94 }
95
96 #[tokio::test]
97 async fn test_delete_result() -> std::result::Result<(), Box<dyn std::error::Error>> {
98 let store = MemoryIdempotencyStore::new();
99 let key = IdempotencyKey::new();
100 let result = ExtractionResult::new(key);
101
102 store.store_result(&key, &result).await?;
103 store.delete_result(&key).await?;
104
105 let retrieved = store.get_result(&key).await?;
106 assert!(retrieved.is_none());
107 Ok(())
108 }
109
110 #[tokio::test]
111 async fn test_clear_all() -> std::result::Result<(), Box<dyn std::error::Error>> {
112 let store = MemoryIdempotencyStore::new();
113
114 let key1 = IdempotencyKey::new();
115 let key2 = IdempotencyKey::new();
116
117 store
118 .store_result(&key1, &ExtractionResult::new(key1))
119 .await?;
120 store
121 .store_result(&key2, &ExtractionResult::new(key2))
122 .await?;
123
124 store.clear_all().await?;
125
126 assert!(store.get_result(&key1).await?.is_none());
127 assert!(store.get_result(&key2).await?.is_none());
128 Ok(())
129 }
130}