ricecoder_agents/domain/
factory.rs

1//! Factory for creating domain agents from configuration
2
3use crate::domain::error::{DomainError, DomainResult};
4use crate::domain::models::{
5    AntiPattern, BestPractice, DomainAgent, DomainCapability, DomainKnowledge, Pattern,
6    TechRecommendation,
7};
8use serde::{Deserialize, Serialize};
9
10/// Configuration for a domain agent
11///
12/// This struct defines the configuration for creating a domain agent,
13/// including capabilities, best practices, and technology recommendations.
14#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct AgentConfig {
16    /// Domain identifier
17    pub domain: String,
18    /// Agent name
19    pub name: String,
20    /// Agent description
21    pub description: String,
22    /// Capabilities configuration
23    pub capabilities: Vec<CapabilityConfig>,
24    /// Best practices configuration
25    #[serde(default)]
26    pub best_practices: Vec<BestPracticeConfig>,
27    /// Technology recommendations configuration
28    #[serde(default)]
29    pub technology_recommendations: Vec<TechRecommendationConfig>,
30    /// Patterns configuration
31    #[serde(default)]
32    pub patterns: Vec<PatternConfig>,
33    /// Anti-patterns configuration
34    #[serde(default)]
35    pub anti_patterns: Vec<AntiPatternConfig>,
36}
37
38/// Configuration for a capability
39#[derive(Debug, Clone, Serialize, Deserialize)]
40pub struct CapabilityConfig {
41    /// Capability name
42    pub name: String,
43    /// Capability description
44    pub description: String,
45    /// Technologies
46    pub technologies: Vec<String>,
47}
48
49/// Configuration for a best practice
50#[derive(Debug, Clone, Serialize, Deserialize)]
51pub struct BestPracticeConfig {
52    /// Practice title
53    pub title: String,
54    /// Practice description
55    pub description: String,
56    /// Technologies
57    pub technologies: Vec<String>,
58    /// Implementation guidance
59    pub implementation: String,
60}
61
62/// Configuration for a technology recommendation
63#[derive(Debug, Clone, Serialize, Deserialize)]
64pub struct TechRecommendationConfig {
65    /// Technology name
66    pub technology: String,
67    /// Use cases
68    pub use_cases: Vec<String>,
69    /// Pros
70    pub pros: Vec<String>,
71    /// Cons
72    pub cons: Vec<String>,
73    /// Alternatives
74    pub alternatives: Vec<String>,
75}
76
77/// Configuration for a pattern
78#[derive(Debug, Clone, Serialize, Deserialize)]
79pub struct PatternConfig {
80    /// Pattern name
81    pub name: String,
82    /// Pattern description
83    pub description: String,
84    /// Technologies
85    pub technologies: Vec<String>,
86    /// Use cases
87    pub use_cases: Vec<String>,
88}
89
90/// Configuration for an anti-pattern
91#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct AntiPatternConfig {
93    /// Anti-pattern name
94    pub name: String,
95    /// Anti-pattern description
96    pub description: String,
97    /// Why avoid
98    pub why_avoid: String,
99    /// Better alternative
100    pub better_alternative: String,
101}
102
103/// Factory for creating domain agents
104///
105/// This struct creates domain agent instances from configuration,
106/// loading domain knowledge and capabilities from configuration files.
107///
108/// # Examples
109///
110/// ```ignore
111/// use ricecoder_agents::domain::AgentFactory;
112///
113/// let factory = AgentFactory::new();
114/// let config = AgentConfig { /* ... */ };
115/// let agent = factory.create_agent("web", &config)?;
116/// ```
117#[derive(Debug, Clone)]
118pub struct AgentFactory;
119
120impl AgentFactory {
121    /// Create a new agent factory
122    pub fn new() -> Self {
123        Self
124    }
125
126    /// Create a domain agent from configuration
127    ///
128    /// # Arguments
129    ///
130    /// * `domain` - Domain identifier
131    /// * `config` - Agent configuration
132    ///
133    /// # Returns
134    ///
135    /// Returns a new domain agent instance
136    ///
137    /// # Examples
138    ///
139    /// ```ignore
140    /// let agent = factory.create_agent("web", &config)?;
141    /// ```
142    pub fn create_agent(&self, domain: &str, config: &AgentConfig) -> DomainResult<DomainAgent> {
143        // Validate configuration
144        self.validate_config(config)?;
145
146        // Create capabilities
147        let capabilities = config
148            .capabilities
149            .iter()
150            .map(|cap| DomainCapability {
151                name: cap.name.clone(),
152                description: cap.description.clone(),
153                technologies: cap.technologies.clone(),
154                patterns: vec![],
155            })
156            .collect();
157
158        // Create best practices
159        let best_practices = config
160            .best_practices
161            .iter()
162            .map(|bp| BestPractice {
163                title: bp.title.clone(),
164                description: bp.description.clone(),
165                domain: domain.to_string(),
166                technologies: bp.technologies.clone(),
167                implementation: bp.implementation.clone(),
168            })
169            .collect();
170
171        // Create technology recommendations
172        let technology_recommendations = config
173            .technology_recommendations
174            .iter()
175            .map(|tr| TechRecommendation {
176                technology: tr.technology.clone(),
177                domain: domain.to_string(),
178                use_cases: tr.use_cases.clone(),
179                pros: tr.pros.clone(),
180                cons: tr.cons.clone(),
181                alternatives: tr.alternatives.clone(),
182            })
183            .collect();
184
185        // Create patterns
186        let patterns = config
187            .patterns
188            .iter()
189            .map(|p| Pattern {
190                name: p.name.clone(),
191                description: p.description.clone(),
192                domain: domain.to_string(),
193                technologies: p.technologies.clone(),
194                use_cases: p.use_cases.clone(),
195            })
196            .collect();
197
198        // Create anti-patterns
199        let anti_patterns = config
200            .anti_patterns
201            .iter()
202            .map(|ap| AntiPattern {
203                name: ap.name.clone(),
204                description: ap.description.clone(),
205                domain: domain.to_string(),
206                why_avoid: ap.why_avoid.clone(),
207                better_alternative: ap.better_alternative.clone(),
208            })
209            .collect();
210
211        // Create knowledge
212        let knowledge = DomainKnowledge {
213            best_practices,
214            technology_recommendations,
215            patterns,
216            anti_patterns,
217        };
218
219        // Create agent
220        let agent = DomainAgent {
221            id: format!("{}-agent", domain),
222            domain: domain.to_string(),
223            capabilities,
224            knowledge,
225        };
226
227        Ok(agent)
228    }
229
230    /// Validate agent configuration
231    ///
232    /// # Arguments
233    ///
234    /// * `config` - Agent configuration to validate
235    ///
236    /// # Returns
237    ///
238    /// Returns Ok if configuration is valid, otherwise returns an error
239    pub fn validate_config(&self, config: &AgentConfig) -> DomainResult<()> {
240        // Validate domain
241        if config.domain.is_empty() {
242            return Err(DomainError::config_error("Domain cannot be empty"));
243        }
244
245        // Validate name
246        if config.name.is_empty() {
247            return Err(DomainError::config_error("Agent name cannot be empty"));
248        }
249
250        // Validate capabilities
251        if config.capabilities.is_empty() {
252            return Err(DomainError::config_error("At least one capability is required"));
253        }
254
255        // Validate each capability
256        for cap in &config.capabilities {
257            if cap.name.is_empty() {
258                return Err(DomainError::config_error("Capability name cannot be empty"));
259            }
260            if cap.technologies.is_empty() {
261                return Err(DomainError::config_error(
262                    "Capability must have at least one technology",
263                ));
264            }
265        }
266
267        Ok(())
268    }
269
270    /// Load configuration from JSON
271    ///
272    /// # Arguments
273    ///
274    /// * `json` - JSON string containing configuration
275    ///
276    /// # Returns
277    ///
278    /// Returns parsed configuration
279    pub fn load_from_json(&self, json: &str) -> DomainResult<AgentConfig> {
280        serde_json::from_str(json).map_err(|e| {
281            DomainError::serialization_error(format!("Failed to parse JSON: {}", e))
282        })
283    }
284
285    /// Load configuration from YAML
286    ///
287    /// # Arguments
288    ///
289    /// * `yaml` - YAML string containing configuration
290    ///
291    /// # Returns
292    ///
293    /// Returns parsed configuration
294    ///
295    /// Note: YAML support requires the `serde_yaml` crate to be added as a dependency
296    pub fn load_from_yaml(&self, _yaml: &str) -> DomainResult<AgentConfig> {
297        Err(DomainError::config_error(
298            "YAML support requires serde_yaml dependency",
299        ))
300    }
301}
302
303impl Default for AgentFactory {
304    fn default() -> Self {
305        Self::new()
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::*;
312
313    fn create_test_config(domain: &str) -> AgentConfig {
314        AgentConfig {
315            domain: domain.to_string(),
316            name: format!("{} Agent", domain),
317            description: format!("Agent for {} development", domain),
318            capabilities: vec![CapabilityConfig {
319                name: "Test Capability".to_string(),
320                description: "A test capability".to_string(),
321                technologies: vec!["Tech1".to_string()],
322            }],
323            best_practices: vec![],
324            technology_recommendations: vec![],
325            patterns: vec![],
326            anti_patterns: vec![],
327        }
328    }
329
330    #[test]
331    fn test_factory_creation() {
332        let factory = AgentFactory::new();
333        assert_eq!(std::mem::size_of_val(&factory), 0); // Zero-sized type
334    }
335
336    #[test]
337    fn test_create_agent() {
338        let factory = AgentFactory::new();
339        let config = create_test_config("web");
340
341        let agent = factory.create_agent("web", &config).unwrap();
342
343        assert_eq!(agent.domain, "web");
344        assert_eq!(agent.id, "web-agent");
345        assert_eq!(agent.capabilities.len(), 1);
346    }
347
348    #[test]
349    fn test_validate_config_valid() {
350        let factory = AgentFactory::new();
351        let config = create_test_config("web");
352
353        assert!(factory.validate_config(&config).is_ok());
354    }
355
356    #[test]
357    fn test_validate_config_empty_domain() {
358        let factory = AgentFactory::new();
359        let mut config = create_test_config("web");
360        config.domain = String::new();
361
362        assert!(factory.validate_config(&config).is_err());
363    }
364
365    #[test]
366    fn test_validate_config_empty_name() {
367        let factory = AgentFactory::new();
368        let mut config = create_test_config("web");
369        config.name = String::new();
370
371        assert!(factory.validate_config(&config).is_err());
372    }
373
374    #[test]
375    fn test_validate_config_no_capabilities() {
376        let factory = AgentFactory::new();
377        let mut config = create_test_config("web");
378        config.capabilities = vec![];
379
380        assert!(factory.validate_config(&config).is_err());
381    }
382
383    #[test]
384    fn test_validate_config_empty_capability_name() {
385        let factory = AgentFactory::new();
386        let mut config = create_test_config("web");
387        config.capabilities[0].name = String::new();
388
389        assert!(factory.validate_config(&config).is_err());
390    }
391
392    #[test]
393    fn test_validate_config_no_technologies() {
394        let factory = AgentFactory::new();
395        let mut config = create_test_config("web");
396        config.capabilities[0].technologies = vec![];
397
398        assert!(factory.validate_config(&config).is_err());
399    }
400
401    #[test]
402    fn test_create_agent_with_best_practices() {
403        let factory = AgentFactory::new();
404        let mut config = create_test_config("web");
405        config.best_practices = vec![BestPracticeConfig {
406            title: "Practice 1".to_string(),
407            description: "Description".to_string(),
408            technologies: vec!["React".to_string()],
409            implementation: "Implementation".to_string(),
410        }];
411
412        let agent = factory.create_agent("web", &config).unwrap();
413
414        assert_eq!(agent.knowledge.best_practices.len(), 1);
415        assert_eq!(agent.knowledge.best_practices[0].title, "Practice 1");
416    }
417
418    #[test]
419    fn test_create_agent_with_tech_recommendations() {
420        let factory = AgentFactory::new();
421        let mut config = create_test_config("web");
422        config.technology_recommendations = vec![TechRecommendationConfig {
423            technology: "React".to_string(),
424            use_cases: vec!["SPAs".to_string()],
425            pros: vec!["Ecosystem".to_string()],
426            cons: vec!["Learning curve".to_string()],
427            alternatives: vec!["Vue".to_string()],
428        }];
429
430        let agent = factory.create_agent("web", &config).unwrap();
431
432        assert_eq!(agent.knowledge.technology_recommendations.len(), 1);
433        assert_eq!(agent.knowledge.technology_recommendations[0].technology, "React");
434    }
435
436    #[test]
437    fn test_create_agent_with_patterns() {
438        let factory = AgentFactory::new();
439        let mut config = create_test_config("web");
440        config.patterns = vec![PatternConfig {
441            name: "Pattern 1".to_string(),
442            description: "Description".to_string(),
443            technologies: vec!["React".to_string()],
444            use_cases: vec!["UI".to_string()],
445        }];
446
447        let agent = factory.create_agent("web", &config).unwrap();
448
449        assert_eq!(agent.knowledge.patterns.len(), 1);
450        assert_eq!(agent.knowledge.patterns[0].name, "Pattern 1");
451    }
452
453    #[test]
454    fn test_create_agent_with_anti_patterns() {
455        let factory = AgentFactory::new();
456        let mut config = create_test_config("web");
457        config.anti_patterns = vec![AntiPatternConfig {
458            name: "Anti-pattern 1".to_string(),
459            description: "Description".to_string(),
460            why_avoid: "Reason".to_string(),
461            better_alternative: "Alternative".to_string(),
462        }];
463
464        let agent = factory.create_agent("web", &config).unwrap();
465
466        assert_eq!(agent.knowledge.anti_patterns.len(), 1);
467        assert_eq!(agent.knowledge.anti_patterns[0].name, "Anti-pattern 1");
468    }
469
470    #[test]
471    fn test_load_from_json() {
472        let factory = AgentFactory::new();
473        let json = r#"{
474            "domain": "web",
475            "name": "Web Agent",
476            "description": "Web development agent",
477            "capabilities": [
478                {
479                    "name": "Framework Selection",
480                    "description": "Select frameworks",
481                    "technologies": ["React"]
482                }
483            ],
484            "best_practices": [],
485            "technology_recommendations": [],
486            "patterns": [],
487            "anti_patterns": []
488        }"#;
489
490        let config = factory.load_from_json(json).unwrap();
491        assert_eq!(config.domain, "web");
492        assert_eq!(config.name, "Web Agent");
493    }
494
495    #[test]
496    fn test_load_from_json_invalid() {
497        let factory = AgentFactory::new();
498        let json = "invalid json";
499
500        assert!(factory.load_from_json(json).is_err());
501    }
502
503    #[test]
504    fn test_default_factory() {
505        let factory = AgentFactory::default();
506        let config = create_test_config("web");
507
508        assert!(factory.create_agent("web", &config).is_ok());
509    }
510
511    #[test]
512    fn test_create_agent_multiple_capabilities() {
513        let factory = AgentFactory::new();
514        let mut config = create_test_config("web");
515        config.capabilities = vec![
516            CapabilityConfig {
517                name: "Capability 1".to_string(),
518                description: "Description 1".to_string(),
519                technologies: vec!["Tech1".to_string()],
520            },
521            CapabilityConfig {
522                name: "Capability 2".to_string(),
523                description: "Description 2".to_string(),
524                technologies: vec!["Tech2".to_string()],
525            },
526        ];
527
528        let agent = factory.create_agent("web", &config).unwrap();
529
530        assert_eq!(agent.capabilities.len(), 2);
531    }
532
533    #[test]
534    fn test_create_agent_preserves_domain() {
535        let factory = AgentFactory::new();
536        let mut config = create_test_config("backend");
537        config.best_practices = vec![BestPracticeConfig {
538            title: "Practice 1".to_string(),
539            description: "Description".to_string(),
540            technologies: vec!["Tech1".to_string()],
541            implementation: "Implementation".to_string(),
542        }];
543
544        let agent = factory.create_agent("backend", &config).unwrap();
545
546        assert_eq!(agent.domain, "backend");
547        assert_eq!(agent.knowledge.best_practices[0].domain, "backend");
548    }
549}