ricecoder_agents/domain/
context.rs

1//! Shared context manager for cross-domain coordination
2
3use crate::domain::error::{DomainError, DomainResult};
4use crate::domain::models::{Recommendation, SharedContext};
5use std::collections::HashMap;
6use std::sync::{Arc, RwLock};
7
8/// Manages shared context across domain agents
9///
10/// This struct maintains cross-domain context that is shared between agents,
11/// enabling coordination and dependency tracking between recommendations.
12///
13/// # Examples
14///
15/// ```ignore
16/// use ricecoder_agents::domain::SharedContextManager;
17///
18/// let manager = SharedContextManager::new();
19/// manager.update_context("project_type", serde_json::json!("web-app"))?;
20/// let value = manager.get_context("project_type")?;
21/// ```
22#[derive(Debug, Clone)]
23pub struct SharedContextManager {
24    context: Arc<RwLock<SharedContext>>,
25    agent_recommendations: Arc<RwLock<HashMap<String, Vec<Recommendation>>>>,
26}
27
28impl SharedContextManager {
29    /// Create a new shared context manager
30    pub fn new() -> Self {
31        Self {
32            context: Arc::new(RwLock::new(SharedContext::default())),
33            agent_recommendations: Arc::new(RwLock::new(HashMap::new())),
34        }
35    }
36
37    /// Update a context value
38    ///
39    /// # Arguments
40    ///
41    /// * `key` - Context key
42    /// * `value` - Context value
43    pub fn update_context(&self, key: &str, value: serde_json::Value) -> DomainResult<()> {
44        let mut context = self.context.write().map_err(|e| {
45            DomainError::internal(format!("Failed to acquire write lock: {}", e))
46        })?;
47
48        context.cross_domain_state.insert(key.to_string(), value);
49        Ok(())
50    }
51
52    /// Get a context value
53    ///
54    /// # Arguments
55    ///
56    /// * `key` - Context key
57    ///
58    /// # Returns
59    ///
60    /// Returns the context value if found
61    pub fn get_context(&self, key: &str) -> DomainResult<serde_json::Value> {
62        let context = self.context.read().map_err(|e| {
63            DomainError::internal(format!("Failed to acquire read lock: {}", e))
64        })?;
65
66        context
67            .cross_domain_state
68            .get(key)
69            .cloned()
70            .ok_or_else(|| DomainError::context_error(format!("Context key not found: {}", key)))
71    }
72
73    /// Update project type
74    ///
75    /// # Arguments
76    ///
77    /// * `project_type` - Project type
78    pub fn set_project_type(&self, project_type: &str) -> DomainResult<()> {
79        let mut context = self.context.write().map_err(|e| {
80            DomainError::internal(format!("Failed to acquire write lock: {}", e))
81        })?;
82
83        context.project_type = project_type.to_string();
84        Ok(())
85    }
86
87    /// Get project type
88    pub fn get_project_type(&self) -> DomainResult<String> {
89        let context = self.context.read().map_err(|e| {
90            DomainError::internal(format!("Failed to acquire read lock: {}", e))
91        })?;
92
93        Ok(context.project_type.clone())
94    }
95
96    /// Add technology to tech stack
97    ///
98    /// # Arguments
99    ///
100    /// * `technology` - Technology to add
101    pub fn add_technology(&self, technology: &str) -> DomainResult<()> {
102        let mut context = self.context.write().map_err(|e| {
103            DomainError::internal(format!("Failed to acquire write lock: {}", e))
104        })?;
105
106        if !context.tech_stack.contains(&technology.to_string()) {
107            context.tech_stack.push(technology.to_string());
108        }
109
110        Ok(())
111    }
112
113    /// Get tech stack
114    pub fn get_tech_stack(&self) -> DomainResult<Vec<String>> {
115        let context = self.context.read().map_err(|e| {
116            DomainError::internal(format!("Failed to acquire read lock: {}", e))
117        })?;
118
119        Ok(context.tech_stack.clone())
120    }
121
122    /// Add constraint
123    ///
124    /// # Arguments
125    ///
126    /// * `constraint` - Constraint to add
127    pub fn add_constraint(&self, constraint: &str) -> DomainResult<()> {
128        let mut context = self.context.write().map_err(|e| {
129            DomainError::internal(format!("Failed to acquire write lock: {}", e))
130        })?;
131
132        if !context.constraints.contains(&constraint.to_string()) {
133            context.constraints.push(constraint.to_string());
134        }
135
136        Ok(())
137    }
138
139    /// Get constraints
140    pub fn get_constraints(&self) -> DomainResult<Vec<String>> {
141        let context = self.context.read().map_err(|e| {
142            DomainError::internal(format!("Failed to acquire read lock: {}", e))
143        })?;
144
145        Ok(context.constraints.clone())
146    }
147
148    /// Store agent recommendations
149    ///
150    /// # Arguments
151    ///
152    /// * `agent_id` - Agent identifier
153    /// * `recommendations` - Recommendations from the agent
154    pub fn store_agent_recommendations(
155        &self,
156        agent_id: &str,
157        recommendations: Vec<Recommendation>,
158    ) -> DomainResult<()> {
159        let mut agent_recs = self.agent_recommendations.write().map_err(|e| {
160            DomainError::internal(format!("Failed to acquire write lock: {}", e))
161        })?;
162
163        agent_recs.insert(agent_id.to_string(), recommendations);
164        Ok(())
165    }
166
167    /// Get agent recommendations
168    ///
169    /// # Arguments
170    ///
171    /// * `agent_id` - Agent identifier
172    ///
173    /// # Returns
174    ///
175    /// Returns recommendations from the agent
176    pub fn get_agent_recommendations(&self, agent_id: &str) -> DomainResult<Vec<Recommendation>> {
177        let agent_recs = self.agent_recommendations.read().map_err(|e| {
178            DomainError::internal(format!("Failed to acquire read lock: {}", e))
179        })?;
180
181        Ok(agent_recs
182            .get(agent_id)
183            .cloned()
184            .unwrap_or_default())
185    }
186
187    /// Get all agent recommendations
188    pub fn get_all_recommendations(&self) -> DomainResult<Vec<Recommendation>> {
189        let agent_recs = self.agent_recommendations.read().map_err(|e| {
190            DomainError::internal(format!("Failed to acquire read lock: {}", e))
191        })?;
192
193        let mut all_recs = Vec::new();
194        for recs in agent_recs.values() {
195            all_recs.extend(recs.clone());
196        }
197
198        Ok(all_recs)
199    }
200
201    /// Get shared context
202    pub fn get_shared_context(&self) -> DomainResult<SharedContext> {
203        let context = self.context.read().map_err(|e| {
204            DomainError::internal(format!("Failed to acquire read lock: {}", e))
205        })?;
206
207        Ok(context.clone())
208    }
209
210    /// Clear all context
211    pub fn clear(&self) -> DomainResult<()> {
212        let mut context = self.context.write().map_err(|e| {
213            DomainError::internal(format!("Failed to acquire write lock: {}", e))
214        })?;
215
216        *context = SharedContext::default();
217
218        let mut agent_recs = self.agent_recommendations.write().map_err(|e| {
219            DomainError::internal(format!("Failed to acquire write lock: {}", e))
220        })?;
221
222        agent_recs.clear();
223
224        Ok(())
225    }
226}
227
228impl Default for SharedContextManager {
229    fn default() -> Self {
230        Self::new()
231    }
232}
233
234#[cfg(test)]
235mod tests {
236    use super::*;
237
238    fn create_test_recommendation(domain: &str) -> Recommendation {
239        Recommendation {
240            domain: domain.to_string(),
241            category: "test".to_string(),
242            content: "Test recommendation".to_string(),
243            technologies: vec!["Tech1".to_string()],
244            rationale: "Test rationale".to_string(),
245        }
246    }
247
248    #[test]
249    fn test_context_manager_creation() {
250        let manager = SharedContextManager::new();
251        assert!(manager.get_project_type().unwrap().is_empty());
252    }
253
254    #[test]
255    fn test_update_context() {
256        let manager = SharedContextManager::new();
257        manager
258            .update_context("key", serde_json::json!("value"))
259            .unwrap();
260
261        let value = manager.get_context("key").unwrap();
262        assert_eq!(value, serde_json::json!("value"));
263    }
264
265    #[test]
266    fn test_get_nonexistent_context() {
267        let manager = SharedContextManager::new();
268        let result = manager.get_context("nonexistent");
269
270        assert!(result.is_err());
271    }
272
273    #[test]
274    fn test_set_project_type() {
275        let manager = SharedContextManager::new();
276        manager.set_project_type("web-app").unwrap();
277
278        let project_type = manager.get_project_type().unwrap();
279        assert_eq!(project_type, "web-app");
280    }
281
282    #[test]
283    fn test_add_technology() {
284        let manager = SharedContextManager::new();
285        manager.add_technology("React").unwrap();
286
287        let tech_stack = manager.get_tech_stack().unwrap();
288        assert_eq!(tech_stack.len(), 1);
289        assert_eq!(tech_stack[0], "React");
290    }
291
292    #[test]
293    fn test_add_multiple_technologies() {
294        let manager = SharedContextManager::new();
295        manager.add_technology("React").unwrap();
296        manager.add_technology("Node.js").unwrap();
297
298        let tech_stack = manager.get_tech_stack().unwrap();
299        assert_eq!(tech_stack.len(), 2);
300    }
301
302    #[test]
303    fn test_add_duplicate_technology() {
304        let manager = SharedContextManager::new();
305        manager.add_technology("React").unwrap();
306        manager.add_technology("React").unwrap();
307
308        let tech_stack = manager.get_tech_stack().unwrap();
309        assert_eq!(tech_stack.len(), 1);
310    }
311
312    #[test]
313    fn test_add_constraint() {
314        let manager = SharedContextManager::new();
315        manager.add_constraint("Must support IE11").unwrap();
316
317        let constraints = manager.get_constraints().unwrap();
318        assert_eq!(constraints.len(), 1);
319        assert_eq!(constraints[0], "Must support IE11");
320    }
321
322    #[test]
323    fn test_add_multiple_constraints() {
324        let manager = SharedContextManager::new();
325        manager.add_constraint("Must support IE11").unwrap();
326        manager.add_constraint("Must be mobile-friendly").unwrap();
327
328        let constraints = manager.get_constraints().unwrap();
329        assert_eq!(constraints.len(), 2);
330    }
331
332    #[test]
333    fn test_store_agent_recommendations() {
334        let manager = SharedContextManager::new();
335        let recommendations = vec![create_test_recommendation("web")];
336
337        manager
338            .store_agent_recommendations("web-agent", recommendations)
339            .unwrap();
340
341        let retrieved = manager.get_agent_recommendations("web-agent").unwrap();
342        assert_eq!(retrieved.len(), 1);
343    }
344
345    #[test]
346    fn test_get_all_recommendations() {
347        let manager = SharedContextManager::new();
348
349        manager
350            .store_agent_recommendations("web-agent", vec![create_test_recommendation("web")])
351            .unwrap();
352        manager
353            .store_agent_recommendations(
354                "backend-agent",
355                vec![create_test_recommendation("backend")],
356            )
357            .unwrap();
358
359        let all_recs = manager.get_all_recommendations().unwrap();
360        assert_eq!(all_recs.len(), 2);
361    }
362
363    #[test]
364    fn test_get_shared_context() {
365        let manager = SharedContextManager::new();
366        manager.set_project_type("web-app").unwrap();
367        manager.add_technology("React").unwrap();
368
369        let context = manager.get_shared_context().unwrap();
370        assert_eq!(context.project_type, "web-app");
371        assert_eq!(context.tech_stack.len(), 1);
372    }
373
374    #[test]
375    fn test_clear() {
376        let manager = SharedContextManager::new();
377        manager.set_project_type("web-app").unwrap();
378        manager.add_technology("React").unwrap();
379        manager
380            .store_agent_recommendations("web-agent", vec![create_test_recommendation("web")])
381            .unwrap();
382
383        manager.clear().unwrap();
384
385        assert!(manager.get_project_type().unwrap().is_empty());
386        assert!(manager.get_tech_stack().unwrap().is_empty());
387        assert!(manager.get_agent_recommendations("web-agent").unwrap().is_empty());
388    }
389
390    #[test]
391    fn test_default_manager() {
392        let manager = SharedContextManager::default();
393        assert!(manager.get_project_type().unwrap().is_empty());
394    }
395
396    #[test]
397    fn test_context_isolation() {
398        let manager1 = SharedContextManager::new();
399        let manager2 = SharedContextManager::new();
400
401        manager1.set_project_type("web-app").unwrap();
402        manager2.set_project_type("mobile-app").unwrap();
403
404        assert_eq!(manager1.get_project_type().unwrap(), "web-app");
405        assert_eq!(manager2.get_project_type().unwrap(), "mobile-app");
406    }
407}