ricecoder_hooks/registry/
storage.rs

1//! In-memory hook storage implementation
2
3use crate::error::{HooksError, Result};
4use crate::types::Hook;
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7use uuid::Uuid;
8
9/// In-memory hook registry implementation
10#[derive(Debug, Clone)]
11pub struct InMemoryHookRegistry {
12    hooks: Arc<RwLock<HashMap<String, Hook>>>,
13}
14
15impl InMemoryHookRegistry {
16    /// Create a new in-memory hook registry
17    pub fn new() -> Self {
18        Self {
19            hooks: Arc::new(RwLock::new(HashMap::new())),
20        }
21    }
22}
23
24impl Default for InMemoryHookRegistry {
25    fn default() -> Self {
26        Self::new()
27    }
28}
29
30impl super::HookRegistry for InMemoryHookRegistry {
31    fn register_hook(&mut self, mut hook: Hook) -> Result<String> {
32        // Generate unique ID if not provided
33        if hook.id.is_empty() {
34            hook.id = Uuid::new_v4().to_string();
35        }
36
37        let hook_id = hook.id.clone();
38        let mut hooks = self.hooks.write().map_err(|e| {
39            HooksError::StorageError(format!("Failed to acquire write lock: {}", e))
40        })?;
41
42        hooks.insert(hook_id.clone(), hook);
43        Ok(hook_id)
44    }
45
46    fn unregister_hook(&self, hook_id: &str) -> Result<()> {
47        let mut hooks = self.hooks.write().map_err(|e| {
48            HooksError::StorageError(format!("Failed to acquire write lock: {}", e))
49        })?;
50
51        hooks
52            .remove(hook_id)
53            .ok_or_else(|| HooksError::HookNotFound(hook_id.to_string()))?;
54
55        Ok(())
56    }
57
58    fn get_hook(&self, hook_id: &str) -> Result<Hook> {
59        let hooks = self
60            .hooks
61            .read()
62            .map_err(|e| HooksError::StorageError(format!("Failed to acquire read lock: {}", e)))?;
63
64        hooks
65            .get(hook_id)
66            .cloned()
67            .ok_or_else(|| HooksError::HookNotFound(hook_id.to_string()))
68    }
69
70    fn list_hooks(&self) -> Result<Vec<Hook>> {
71        let hooks = self
72            .hooks
73            .read()
74            .map_err(|e| HooksError::StorageError(format!("Failed to acquire read lock: {}", e)))?;
75
76        Ok(hooks.values().cloned().collect())
77    }
78
79    fn list_hooks_for_event(&self, event: &str) -> Result<Vec<Hook>> {
80        let hooks = self
81            .hooks
82            .read()
83            .map_err(|e| HooksError::StorageError(format!("Failed to acquire read lock: {}", e)))?;
84
85        Ok(hooks
86            .values()
87            .filter(|h| h.event == event && h.enabled)
88            .cloned()
89            .collect())
90    }
91
92    fn enable_hook(&mut self, hook_id: &str) -> Result<()> {
93        let mut hooks = self.hooks.write().map_err(|e| {
94            HooksError::StorageError(format!("Failed to acquire write lock: {}", e))
95        })?;
96
97        let hook = hooks
98            .get_mut(hook_id)
99            .ok_or_else(|| HooksError::HookNotFound(hook_id.to_string()))?;
100
101        hook.enabled = true;
102        Ok(())
103    }
104
105    fn disable_hook(&mut self, hook_id: &str) -> Result<()> {
106        let mut hooks = self.hooks.write().map_err(|e| {
107            HooksError::StorageError(format!("Failed to acquire write lock: {}", e))
108        })?;
109
110        let hook = hooks
111            .get_mut(hook_id)
112            .ok_or_else(|| HooksError::HookNotFound(hook_id.to_string()))?;
113
114        hook.enabled = false;
115        Ok(())
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::registry::HookRegistry;
123    use crate::types::{Action, CommandAction};
124
125    fn create_test_hook(id: &str, event: &str, enabled: bool) -> Hook {
126        Hook {
127            id: id.to_string(),
128            name: format!("Test Hook {}", id),
129            description: None,
130            event: event.to_string(),
131            action: Action::Command(CommandAction {
132                command: "echo".to_string(),
133                args: vec!["test".to_string()],
134                timeout_ms: None,
135                capture_output: false,
136            }),
137            enabled,
138            tags: vec![],
139            metadata: serde_json::json!({}),
140            condition: None,
141        }
142    }
143
144    #[test]
145    fn test_register_hook() {
146        let mut registry = InMemoryHookRegistry::new();
147        let hook = create_test_hook("hook1", "file_saved", true);
148
149        let id = registry.register_hook(hook).unwrap();
150        assert!(!id.is_empty());
151    }
152
153    #[test]
154    fn test_register_hook_generates_id() {
155        let mut registry = InMemoryHookRegistry::new();
156        let mut hook = create_test_hook("", "file_saved", true);
157        hook.id = String::new();
158
159        let id = registry.register_hook(hook).unwrap();
160        assert!(!id.is_empty());
161    }
162
163    #[test]
164    fn test_get_hook() {
165        let mut registry = InMemoryHookRegistry::new();
166        let hook = create_test_hook("hook1", "file_saved", true);
167
168        registry.register_hook(hook.clone()).unwrap();
169        let retrieved = registry.get_hook("hook1").unwrap();
170
171        assert_eq!(retrieved.id, "hook1");
172        assert_eq!(retrieved.name, hook.name);
173    }
174
175    #[test]
176    fn test_get_hook_not_found() {
177        let registry = InMemoryHookRegistry::new();
178        let result = registry.get_hook("nonexistent");
179
180        assert!(result.is_err());
181    }
182
183    #[test]
184    fn test_list_hooks() {
185        let mut registry = InMemoryHookRegistry::new();
186        let hook1 = create_test_hook("hook1", "file_saved", true);
187        let hook2 = create_test_hook("hook2", "test_passed", true);
188
189        registry.register_hook(hook1).unwrap();
190        registry.register_hook(hook2).unwrap();
191
192        let hooks = registry.list_hooks().unwrap();
193        assert_eq!(hooks.len(), 2);
194    }
195
196    #[test]
197    fn test_list_hooks_for_event() {
198        let mut registry = InMemoryHookRegistry::new();
199        let hook1 = create_test_hook("hook1", "file_saved", true);
200        let hook2 = create_test_hook("hook2", "file_saved", true);
201        let hook3 = create_test_hook("hook3", "test_passed", true);
202
203        registry.register_hook(hook1).unwrap();
204        registry.register_hook(hook2).unwrap();
205        registry.register_hook(hook3).unwrap();
206
207        let hooks = registry.list_hooks_for_event("file_saved").unwrap();
208        assert_eq!(hooks.len(), 2);
209    }
210
211    #[test]
212    fn test_list_hooks_for_event_excludes_disabled() {
213        let mut registry = InMemoryHookRegistry::new();
214        let hook1 = create_test_hook("hook1", "file_saved", true);
215        let hook2 = create_test_hook("hook2", "file_saved", false);
216
217        registry.register_hook(hook1).unwrap();
218        registry.register_hook(hook2).unwrap();
219
220        let hooks = registry.list_hooks_for_event("file_saved").unwrap();
221        assert_eq!(hooks.len(), 1);
222        assert_eq!(hooks[0].id, "hook1");
223    }
224
225    #[test]
226    fn test_enable_hook() {
227        let mut registry = InMemoryHookRegistry::new();
228        let hook = create_test_hook("hook1", "file_saved", false);
229
230        registry.register_hook(hook).unwrap();
231        registry.enable_hook("hook1").unwrap();
232
233        let retrieved = registry.get_hook("hook1").unwrap();
234        assert!(retrieved.enabled);
235    }
236
237    #[test]
238    fn test_disable_hook() {
239        let mut registry = InMemoryHookRegistry::new();
240        let hook = create_test_hook("hook1", "file_saved", true);
241
242        registry.register_hook(hook).unwrap();
243        registry.disable_hook("hook1").unwrap();
244
245        let retrieved = registry.get_hook("hook1").unwrap();
246        assert!(!retrieved.enabled);
247    }
248
249    #[test]
250    fn test_unregister_hook() {
251        let mut registry = InMemoryHookRegistry::new();
252        let hook = create_test_hook("hook1", "file_saved", true);
253
254        registry.register_hook(hook).unwrap();
255        registry.unregister_hook("hook1").unwrap();
256
257        let result = registry.get_hook("hook1");
258        assert!(result.is_err());
259    }
260
261    #[test]
262    fn test_unregister_nonexistent_hook() {
263        let registry = InMemoryHookRegistry::new();
264        let result = registry.unregister_hook("nonexistent");
265
266        assert!(result.is_err());
267    }
268}