ricecoder_learning/
rule_application.rs

1/// Rule application engine for guiding code generation
2use crate::error::{LearningError, Result};
3use crate::models::Rule;
4use serde_json::{json, Value};
5use std::collections::HashMap;
6
7/// Context for code generation that rules can match against
8#[derive(Debug, Clone)]
9pub struct GenerationContext {
10    /// Type of generation (e.g., "function", "class", "module")
11    pub generation_type: String,
12    /// Language being generated
13    pub language: String,
14    /// Current code or prompt
15    pub input: String,
16    /// Additional context metadata
17    pub metadata: HashMap<String, Value>,
18}
19
20impl GenerationContext {
21    /// Create a new generation context
22    pub fn new(generation_type: String, language: String, input: String) -> Self {
23        Self {
24            generation_type,
25            language,
26            input,
27            metadata: HashMap::new(),
28        }
29    }
30
31    /// Add metadata to the context
32    pub fn with_metadata(mut self, key: String, value: Value) -> Self {
33        self.metadata.insert(key, value);
34        self
35    }
36
37    /// Convert context to JSON for pattern matching
38    pub fn to_json(&self) -> Value {
39        json!({
40            "generation_type": self.generation_type,
41            "language": self.language,
42            "input_length": self.input.len(),
43            "metadata": self.metadata,
44        })
45    }
46}
47
48/// Result of applying a rule to a generation context
49#[derive(Debug, Clone)]
50pub struct RuleApplicationResult {
51    /// The rule that was applied
52    pub rule: Rule,
53    /// Whether the rule matched the context
54    pub matched: bool,
55    /// The action to apply (if matched)
56    pub action: Option<String>,
57    /// Confidence in the match
58    pub confidence: f32,
59    /// Additional details about the application
60    pub details: HashMap<String, Value>,
61}
62
63impl RuleApplicationResult {
64    /// Create a new rule application result
65    pub fn new(rule: Rule, matched: bool) -> Self {
66        Self {
67            confidence: rule.confidence,
68            action: if matched { Some(rule.action.clone()) } else { None },
69            rule,
70            matched,
71            details: HashMap::new(),
72        }
73    }
74
75    /// Add a detail to the result
76    pub fn with_detail(mut self, key: String, value: Value) -> Self {
77        self.details.insert(key, value);
78        self
79    }
80}
81
82/// Rule application engine for matching and applying rules
83pub struct RuleApplicationEngine;
84
85impl RuleApplicationEngine {
86    /// Check if a rule pattern matches a generation context
87    pub fn matches_pattern(rule: &Rule, context: &GenerationContext) -> bool {
88        // Parse the pattern as a simple JSON pattern
89        if let Ok(pattern_value) = serde_json::from_str::<Value>(&rule.pattern) {
90            Self::pattern_matches(&pattern_value, context)
91        } else {
92            // Fallback to simple string matching
93            rule.pattern.contains(&context.generation_type)
94                || rule.pattern.contains(&context.language)
95        }
96    }
97
98    /// Check if a pattern value matches the generation context
99    fn pattern_matches(pattern: &Value, context: &GenerationContext) -> bool {
100        match pattern {
101            Value::Object(obj) => {
102                // Check each field in the pattern
103                for (key, value) in obj {
104                    match key.as_str() {
105                        "generation_type" => {
106                            if let Value::String(expected) = value {
107                                if context.generation_type != *expected {
108                                    return false;
109                                }
110                            }
111                        }
112                        "language" => {
113                            if let Value::String(expected) = value {
114                                if context.language != *expected {
115                                    return false;
116                                }
117                            }
118                        }
119                        "metadata" => {
120                            if let Value::Object(expected_meta) = value {
121                                for (meta_key, meta_value) in expected_meta {
122                                    if let Some(actual_value) = context.metadata.get(meta_key) {
123                                        if actual_value != meta_value {
124                                            return false;
125                                        }
126                                    } else {
127                                        return false;
128                                    }
129                                }
130                            }
131                        }
132                        _ => {
133                            // Unknown pattern field, skip
134                        }
135                    }
136                }
137                true
138            }
139            Value::String(pattern_str) => {
140                // Simple string pattern matching
141                pattern_str.contains(&context.generation_type)
142                    || pattern_str.contains(&context.language)
143            }
144            _ => false,
145        }
146    }
147
148    /// Apply a single rule to a generation context
149    pub fn apply_rule(rule: &Rule, context: &GenerationContext) -> RuleApplicationResult {
150        let matched = Self::matches_pattern(rule, context);
151        let mut result = RuleApplicationResult::new(rule.clone(), matched);
152
153        if matched {
154            result = result.with_detail(
155                "applied_at".to_string(),
156                json!(chrono::Utc::now().to_rfc3339()),
157            );
158        }
159
160        result
161    }
162
163    /// Apply multiple rules to a generation context
164    pub fn apply_rules(
165        rules: &[Rule],
166        context: &GenerationContext,
167    ) -> Vec<RuleApplicationResult> {
168        rules
169            .iter()
170            .map(|rule| Self::apply_rule(rule, context))
171            .collect()
172    }
173
174    /// Apply rules and get the highest confidence matched rule
175    pub fn apply_rules_with_precedence(
176        rules: &[Rule],
177        context: &GenerationContext,
178    ) -> Option<RuleApplicationResult> {
179        let results = Self::apply_rules(rules, context);
180        results
181            .into_iter()
182            .filter(|r| r.matched)
183            .max_by(|a, b| {
184                a.confidence
185                    .partial_cmp(&b.confidence)
186                    .unwrap_or(std::cmp::Ordering::Equal)
187            })
188    }
189
190    /// Chain multiple rules together
191    pub fn chain_rules(
192        rules: &[Rule],
193        context: &GenerationContext,
194    ) -> Result<Vec<RuleApplicationResult>> {
195        let mut results = Vec::new();
196        let mut current_context = context.clone();
197
198        for rule in rules {
199            let result = Self::apply_rule(rule, &current_context);
200
201            if result.matched {
202                // Update context with the action for the next rule
203                if let Some(action) = &result.action {
204                    current_context = current_context.with_metadata(
205                        "last_action".to_string(),
206                        json!(action),
207                    );
208                }
209            }
210
211            results.push(result);
212        }
213
214        Ok(results)
215    }
216
217    /// Compose multiple rules into a single action
218    pub fn compose_rules(
219        rules: &[Rule],
220        context: &GenerationContext,
221    ) -> Result<Option<String>> {
222        let results = Self::apply_rules(rules, context);
223        let matched_actions: Vec<String> = results
224            .iter()
225            .filter(|r| r.matched)
226            .filter_map(|r| r.action.clone())
227            .collect();
228
229        if matched_actions.is_empty() {
230            Ok(None)
231        } else {
232            // Compose actions by joining them
233            Ok(Some(matched_actions.join("\n")))
234        }
235    }
236
237    /// Validate that a rule can be applied to a context
238    pub fn validate_rule_application(rule: &Rule, context: &GenerationContext) -> Result<()> {
239        // Validate that the pattern is valid JSON or a simple string
240        if let Err(_) = serde_json::from_str::<Value>(&rule.pattern) {
241            // If not JSON, check if it's a simple string pattern
242            if rule.pattern.is_empty() {
243                return Err(LearningError::RuleApplicationFailed(
244                    "Rule pattern cannot be empty".to_string(),
245                ));
246            }
247        }
248
249        // Validate that the action is not empty
250        if rule.action.is_empty() {
251            return Err(LearningError::RuleApplicationFailed(
252                "Rule action cannot be empty".to_string(),
253            ));
254        }
255
256        // Validate that the context is valid
257        if context.generation_type.is_empty() {
258            return Err(LearningError::RuleApplicationFailed(
259                "Generation context type cannot be empty".to_string(),
260            ));
261        }
262
263        if context.language.is_empty() {
264            return Err(LearningError::RuleApplicationFailed(
265                "Generation context language cannot be empty".to_string(),
266            ));
267        }
268
269        Ok(())
270    }
271
272    /// Get all matching rules for a context
273    pub fn get_matching_rules(rules: &[Rule], context: &GenerationContext) -> Vec<Rule> {
274        rules
275            .iter()
276            .filter(|rule| Self::matches_pattern(rule, context))
277            .cloned()
278            .collect()
279    }
280
281    /// Get matching rules sorted by confidence
282    pub fn get_matching_rules_sorted(rules: &[Rule], context: &GenerationContext) -> Vec<Rule> {
283        let mut matching = Self::get_matching_rules(rules, context);
284        matching.sort_by(|a, b| {
285            b.confidence
286                .partial_cmp(&a.confidence)
287                .unwrap_or(std::cmp::Ordering::Equal)
288        });
289        matching
290    }
291
292    /// Get matching rules sorted by usage
293    pub fn get_matching_rules_by_usage(rules: &[Rule], context: &GenerationContext) -> Vec<Rule> {
294        let mut matching = Self::get_matching_rules(rules, context);
295        matching.sort_by(|a, b| b.usage_count.cmp(&a.usage_count));
296        matching
297    }
298
299    /// Get matching rules sorted by success rate
300    pub fn get_matching_rules_by_success(rules: &[Rule], context: &GenerationContext) -> Vec<Rule> {
301        let mut matching = Self::get_matching_rules(rules, context);
302        matching.sort_by(|a, b| {
303            b.success_rate
304                .partial_cmp(&a.success_rate)
305                .unwrap_or(std::cmp::Ordering::Equal)
306        });
307        matching
308    }
309}
310
311#[cfg(test)]
312mod tests {
313    use super::*;
314
315    #[test]
316    fn test_generation_context_creation() {
317        let context = GenerationContext::new(
318            "function".to_string(),
319            "rust".to_string(),
320            "fn test() {}".to_string(),
321        );
322
323        assert_eq!(context.generation_type, "function");
324        assert_eq!(context.language, "rust");
325        assert_eq!(context.input, "fn test() {}");
326    }
327
328    #[test]
329    fn test_generation_context_with_metadata() {
330        let context = GenerationContext::new(
331            "function".to_string(),
332            "rust".to_string(),
333            "fn test() {}".to_string(),
334        )
335        .with_metadata("style".to_string(), json!("async"));
336
337        assert_eq!(context.metadata.get("style").unwrap(), &json!("async"));
338    }
339
340    #[test]
341    fn test_rule_application_result() {
342        let rule = Rule::new(
343            crate::models::RuleScope::Session,
344            "function".to_string(),
345            "add_documentation".to_string(),
346            crate::models::RuleSource::Learned,
347        );
348
349        let result = RuleApplicationResult::new(rule.clone(), true);
350        assert!(result.matched);
351        assert_eq!(result.action, Some("add_documentation".to_string()));
352    }
353
354    #[test]
355    fn test_simple_pattern_matching() {
356        let rule = Rule::new(
357            crate::models::RuleScope::Session,
358            "function".to_string(),
359            "add_documentation".to_string(),
360            crate::models::RuleSource::Learned,
361        );
362
363        let context = GenerationContext::new(
364            "function".to_string(),
365            "rust".to_string(),
366            "fn test() {}".to_string(),
367        );
368
369        assert!(RuleApplicationEngine::matches_pattern(&rule, &context));
370    }
371
372    #[test]
373    fn test_pattern_not_matching() {
374        let rule = Rule::new(
375            crate::models::RuleScope::Session,
376            "class".to_string(),
377            "add_documentation".to_string(),
378            crate::models::RuleSource::Learned,
379        );
380
381        let context = GenerationContext::new(
382            "function".to_string(),
383            "rust".to_string(),
384            "fn test() {}".to_string(),
385        );
386
387        assert!(!RuleApplicationEngine::matches_pattern(&rule, &context));
388    }
389
390    #[test]
391    fn test_apply_single_rule() {
392        let rule = Rule::new(
393            crate::models::RuleScope::Session,
394            "function".to_string(),
395            "add_documentation".to_string(),
396            crate::models::RuleSource::Learned,
397        );
398
399        let context = GenerationContext::new(
400            "function".to_string(),
401            "rust".to_string(),
402            "fn test() {}".to_string(),
403        );
404
405        let result = RuleApplicationEngine::apply_rule(&rule, &context);
406        assert!(result.matched);
407        assert_eq!(result.action, Some("add_documentation".to_string()));
408    }
409
410    #[test]
411    fn test_apply_multiple_rules() {
412        let rule1 = Rule::new(
413            crate::models::RuleScope::Session,
414            "function".to_string(),
415            "add_documentation".to_string(),
416            crate::models::RuleSource::Learned,
417        );
418
419        let rule2 = Rule::new(
420            crate::models::RuleScope::Session,
421            "rust".to_string(),
422            "add_error_handling".to_string(),
423            crate::models::RuleSource::Learned,
424        );
425
426        let context = GenerationContext::new(
427            "function".to_string(),
428            "rust".to_string(),
429            "fn test() {}".to_string(),
430        );
431
432        let results = RuleApplicationEngine::apply_rules(&[rule1, rule2], &context);
433        assert_eq!(results.len(), 2);
434        assert!(results[0].matched);
435        assert!(results[1].matched);
436    }
437
438    #[test]
439    fn test_apply_rules_with_precedence() {
440        let mut rule1 = Rule::new(
441            crate::models::RuleScope::Session,
442            "function".to_string(),
443            "add_documentation".to_string(),
444            crate::models::RuleSource::Learned,
445        );
446        rule1.confidence = 0.7;
447
448        let mut rule2 = Rule::new(
449            crate::models::RuleScope::Session,
450            "function".to_string(),
451            "add_error_handling".to_string(),
452            crate::models::RuleSource::Learned,
453        );
454        rule2.confidence = 0.9;
455
456        let context = GenerationContext::new(
457            "function".to_string(),
458            "rust".to_string(),
459            "fn test() {}".to_string(),
460        );
461
462        let result = RuleApplicationEngine::apply_rules_with_precedence(&[rule1, rule2], &context);
463        assert!(result.is_some());
464        assert_eq!(
465            result.unwrap().action,
466            Some("add_error_handling".to_string())
467        );
468    }
469
470    #[test]
471    fn test_chain_rules() {
472        let rule1 = Rule::new(
473            crate::models::RuleScope::Session,
474            "function".to_string(),
475            "add_documentation".to_string(),
476            crate::models::RuleSource::Learned,
477        );
478
479        let rule2 = Rule::new(
480            crate::models::RuleScope::Session,
481            "function".to_string(),
482            "add_error_handling".to_string(),
483            crate::models::RuleSource::Learned,
484        );
485
486        let context = GenerationContext::new(
487            "function".to_string(),
488            "rust".to_string(),
489            "fn test() {}".to_string(),
490        );
491
492        let results = RuleApplicationEngine::chain_rules(&[rule1, rule2], &context);
493        assert!(results.is_ok());
494        let results = results.unwrap();
495        assert_eq!(results.len(), 2);
496    }
497
498    #[test]
499    fn test_compose_rules() {
500        let rule1 = Rule::new(
501            crate::models::RuleScope::Session,
502            "function".to_string(),
503            "add_documentation".to_string(),
504            crate::models::RuleSource::Learned,
505        );
506
507        let rule2 = Rule::new(
508            crate::models::RuleScope::Session,
509            "function".to_string(),
510            "add_error_handling".to_string(),
511            crate::models::RuleSource::Learned,
512        );
513
514        let context = GenerationContext::new(
515            "function".to_string(),
516            "rust".to_string(),
517            "fn test() {}".to_string(),
518        );
519
520        let result = RuleApplicationEngine::compose_rules(&[rule1, rule2], &context);
521        assert!(result.is_ok());
522        let composed = result.unwrap();
523        assert!(composed.is_some());
524        let composed_str = composed.unwrap();
525        assert!(composed_str.contains("add_documentation"));
526        assert!(composed_str.contains("add_error_handling"));
527    }
528
529    #[test]
530    fn test_validate_rule_application() {
531        let rule = Rule::new(
532            crate::models::RuleScope::Session,
533            "function".to_string(),
534            "add_documentation".to_string(),
535            crate::models::RuleSource::Learned,
536        );
537
538        let context = GenerationContext::new(
539            "function".to_string(),
540            "rust".to_string(),
541            "fn test() {}".to_string(),
542        );
543
544        let result = RuleApplicationEngine::validate_rule_application(&rule, &context);
545        assert!(result.is_ok());
546    }
547
548    #[test]
549    fn test_validate_rule_application_empty_pattern() {
550        let mut rule = Rule::new(
551            crate::models::RuleScope::Session,
552            "function".to_string(),
553            "add_documentation".to_string(),
554            crate::models::RuleSource::Learned,
555        );
556        rule.pattern = String::new();
557
558        let context = GenerationContext::new(
559            "function".to_string(),
560            "rust".to_string(),
561            "fn test() {}".to_string(),
562        );
563
564        let result = RuleApplicationEngine::validate_rule_application(&rule, &context);
565        assert!(result.is_err());
566    }
567
568    #[test]
569    fn test_get_matching_rules() {
570        let rule1 = Rule::new(
571            crate::models::RuleScope::Session,
572            "function".to_string(),
573            "add_documentation".to_string(),
574            crate::models::RuleSource::Learned,
575        );
576
577        let rule2 = Rule::new(
578            crate::models::RuleScope::Session,
579            "class".to_string(),
580            "add_error_handling".to_string(),
581            crate::models::RuleSource::Learned,
582        );
583
584        let context = GenerationContext::new(
585            "function".to_string(),
586            "rust".to_string(),
587            "fn test() {}".to_string(),
588        );
589
590        let matching = RuleApplicationEngine::get_matching_rules(&[rule1, rule2], &context);
591        assert_eq!(matching.len(), 1);
592        assert_eq!(matching[0].action, "add_documentation");
593    }
594
595    #[test]
596    fn test_get_matching_rules_sorted_by_confidence() {
597        let mut rule1 = Rule::new(
598            crate::models::RuleScope::Session,
599            "function".to_string(),
600            "add_documentation".to_string(),
601            crate::models::RuleSource::Learned,
602        );
603        rule1.confidence = 0.5;
604
605        let mut rule2 = Rule::new(
606            crate::models::RuleScope::Session,
607            "function".to_string(),
608            "add_error_handling".to_string(),
609            crate::models::RuleSource::Learned,
610        );
611        rule2.confidence = 0.9;
612
613        let context = GenerationContext::new(
614            "function".to_string(),
615            "rust".to_string(),
616            "fn test() {}".to_string(),
617        );
618
619        let matching = RuleApplicationEngine::get_matching_rules_sorted(&[rule1, rule2], &context);
620        assert_eq!(matching.len(), 2);
621        assert_eq!(matching[0].confidence, 0.9);
622        assert_eq!(matching[1].confidence, 0.5);
623    }
624}