ricecoder_learning/
rule_exchange.rs

1/// Rule export and import functionality
2use crate::error::{LearningError, Result};
3use crate::models::Rule;
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7/// Metadata for exported rules
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ExportMetadata {
10    /// Version of the export format
11    pub version: String,
12    /// When the export was created
13    pub exported_at: String,
14    /// Number of rules in the export
15    pub rule_count: usize,
16    /// Description of the export
17    pub description: Option<String>,
18}
19
20/// Container for exported rules with metadata
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct RuleExport {
23    /// Export metadata
24    pub metadata: ExportMetadata,
25    /// Exported rules
26    pub rules: Vec<Rule>,
27}
28
29impl RuleExport {
30    /// Create a new rule export
31    pub fn new(rules: Vec<Rule>, description: Option<String>) -> Self {
32        let rule_count = rules.len();
33        Self {
34            metadata: ExportMetadata {
35                version: "1.0".to_string(),
36                exported_at: chrono::Utc::now().to_rfc3339(),
37                rule_count,
38                description,
39            },
40            rules,
41        }
42    }
43
44    /// Serialize to JSON string
45    pub fn to_json(&self) -> Result<String> {
46        serde_json::to_string_pretty(self)
47            .map_err(|e| LearningError::SerializationError(e))
48    }
49
50    /// Deserialize from JSON string
51    pub fn from_json(json: &str) -> Result<Self> {
52        serde_json::from_str(json)
53            .map_err(|e| LearningError::SerializationError(e))
54    }
55
56    /// Write to file
57    pub fn write_to_file(&self, path: &Path) -> Result<()> {
58        let json = self.to_json()?;
59        std::fs::write(path, json)
60            .map_err(|e| LearningError::IoError(e))
61    }
62
63    /// Read from file
64    pub fn read_from_file(path: &Path) -> Result<Self> {
65        let json = std::fs::read_to_string(path)
66            .map_err(|e| LearningError::IoError(e))?;
67        Self::from_json(&json)
68    }
69}
70
71/// Rule exporter for exporting rules with metrics
72pub struct RuleExporter;
73
74impl RuleExporter {
75    /// Export rules to JSON format
76    pub fn export_rules(rules: Vec<Rule>, description: Option<String>) -> Result<RuleExport> {
77        Ok(RuleExport::new(rules, description))
78    }
79
80    /// Export rules to JSON string
81    pub fn export_to_json(rules: Vec<Rule>, description: Option<String>) -> Result<String> {
82        let export = RuleExport::new(rules, description);
83        export.to_json()
84    }
85
86    /// Export rules to file
87    pub fn export_to_file(
88        rules: Vec<Rule>,
89        path: &Path,
90        description: Option<String>,
91    ) -> Result<()> {
92        let export = RuleExport::new(rules, description);
93        export.write_to_file(path)
94    }
95}
96
97/// Rule importer for importing rules with validation
98pub struct RuleImporter;
99
100impl RuleImporter {
101    /// Import rules from JSON string
102    pub fn import_from_json(json: &str) -> Result<Vec<Rule>> {
103        let export = RuleExport::from_json(json)?;
104        Ok(export.rules)
105    }
106
107    /// Import rules from file
108    pub fn import_from_file(path: &Path) -> Result<Vec<Rule>> {
109        let export = RuleExport::read_from_file(path)?;
110        Ok(export.rules)
111    }
112
113    /// Import and validate rules
114    pub fn import_and_validate(json: &str) -> Result<(Vec<Rule>, Vec<String>)> {
115        let export = RuleExport::from_json(json)?;
116        let mut valid_rules = Vec::new();
117        let mut validation_errors = Vec::new();
118
119        for rule in export.rules {
120            match Self::validate_rule(&rule) {
121                Ok(_) => valid_rules.push(rule),
122                Err(e) => validation_errors.push(format!("Rule {}: {}", rule.id, e)),
123            }
124        }
125
126        Ok((valid_rules, validation_errors))
127    }
128
129    /// Validate a single rule
130    fn validate_rule(rule: &Rule) -> Result<()> {
131        // Validate pattern is not empty
132        if rule.pattern.is_empty() {
133            return Err(LearningError::RuleValidationFailed(
134                "Rule pattern cannot be empty".to_string(),
135            ));
136        }
137
138        // Validate action is not empty
139        if rule.action.is_empty() {
140            return Err(LearningError::RuleValidationFailed(
141                "Rule action cannot be empty".to_string(),
142            ));
143        }
144
145        // Validate confidence is in valid range
146        if !(0.0..=1.0).contains(&rule.confidence) {
147            return Err(LearningError::RuleValidationFailed(
148                "Rule confidence must be between 0.0 and 1.0".to_string(),
149            ));
150        }
151
152        // Validate success rate is in valid range
153        if !(0.0..=1.0).contains(&rule.success_rate) {
154            return Err(LearningError::RuleValidationFailed(
155                "Rule success_rate must be between 0.0 and 1.0".to_string(),
156            ));
157        }
158
159        Ok(())
160    }
161}
162
163#[cfg(test)]
164mod tests {
165    use super::*;
166    use crate::models::{RuleScope, RuleSource};
167
168    fn create_test_rule() -> Rule {
169        Rule::new(
170            RuleScope::Global,
171            "test_pattern".to_string(),
172            "test_action".to_string(),
173            RuleSource::Learned,
174        )
175    }
176
177    #[test]
178    fn test_rule_export_creation() {
179        let rules = vec![create_test_rule()];
180        let export = RuleExport::new(rules, Some("Test export".to_string()));
181
182        assert_eq!(export.metadata.version, "1.0");
183        assert_eq!(export.metadata.rule_count, 1);
184        assert_eq!(export.rules.len(), 1);
185    }
186
187    #[test]
188    fn test_rule_export_to_json() {
189        let rules = vec![create_test_rule()];
190        let export = RuleExport::new(rules, None);
191        let json = export.to_json().unwrap();
192
193        assert!(json.contains("version") && json.contains("1.0"));
194        assert!(json.contains("rule_count") && json.contains("1"));
195    }
196
197    #[test]
198    fn test_rule_export_from_json() {
199        let rules = vec![create_test_rule()];
200        let export = RuleExport::new(rules.clone(), None);
201        let json = export.to_json().unwrap();
202
203        let imported = RuleExport::from_json(&json).unwrap();
204        assert_eq!(imported.rules.len(), 1);
205        assert_eq!(imported.metadata.rule_count, 1);
206    }
207
208    #[test]
209    fn test_rule_exporter_export_rules() {
210        let rules = vec![create_test_rule()];
211        let export = RuleExporter::export_rules(rules, None).unwrap();
212
213        assert_eq!(export.rules.len(), 1);
214        assert_eq!(export.metadata.rule_count, 1);
215    }
216
217    #[test]
218    fn test_rule_exporter_export_to_json() {
219        let rules = vec![create_test_rule()];
220        let json = RuleExporter::export_to_json(rules, None).unwrap();
221
222        assert!(json.contains("version") && json.contains("1.0"));
223    }
224
225    #[test]
226    fn test_rule_importer_import_from_json() {
227        let rules = vec![create_test_rule()];
228        let export = RuleExport::new(rules, None);
229        let json = export.to_json().unwrap();
230
231        let imported = RuleImporter::import_from_json(&json).unwrap();
232        assert_eq!(imported.len(), 1);
233    }
234
235    #[test]
236    fn test_rule_importer_validate_rule_valid() {
237        let rule = create_test_rule();
238        assert!(RuleImporter::validate_rule(&rule).is_ok());
239    }
240
241    #[test]
242    fn test_rule_importer_validate_rule_empty_pattern() {
243        let mut rule = create_test_rule();
244        rule.pattern = String::new();
245
246        assert!(RuleImporter::validate_rule(&rule).is_err());
247    }
248
249    #[test]
250    fn test_rule_importer_validate_rule_empty_action() {
251        let mut rule = create_test_rule();
252        rule.action = String::new();
253
254        assert!(RuleImporter::validate_rule(&rule).is_err());
255    }
256
257    #[test]
258    fn test_rule_importer_validate_rule_invalid_confidence() {
259        let mut rule = create_test_rule();
260        rule.confidence = 1.5;
261
262        assert!(RuleImporter::validate_rule(&rule).is_err());
263    }
264
265    #[test]
266    fn test_rule_importer_validate_rule_invalid_success_rate() {
267        let mut rule = create_test_rule();
268        rule.success_rate = -0.1;
269
270        assert!(RuleImporter::validate_rule(&rule).is_err());
271    }
272
273    #[test]
274    fn test_rule_importer_import_and_validate() {
275        let mut rules = vec![create_test_rule()];
276        let mut invalid_rule = create_test_rule();
277        invalid_rule.pattern = String::new();
278        rules.push(invalid_rule);
279
280        let export = RuleExport::new(rules, None);
281        let json = export.to_json().unwrap();
282
283        let (valid, errors) = RuleImporter::import_and_validate(&json).unwrap();
284        assert_eq!(valid.len(), 1);
285        assert_eq!(errors.len(), 1);
286    }
287
288    #[test]
289    fn test_rule_export_write_and_read_file() {
290        let rules = vec![create_test_rule()];
291        let export = RuleExport::new(rules, None);
292
293        let temp_file = std::env::temp_dir().join("test_rules.json");
294        export.write_to_file(&temp_file).unwrap();
295
296        let imported = RuleExport::read_from_file(&temp_file).unwrap();
297        assert_eq!(imported.rules.len(), 1);
298
299        // Cleanup
300        let _ = std::fs::remove_file(&temp_file);
301    }
302
303    #[test]
304    fn test_rule_exporter_export_to_file() {
305        let rules = vec![create_test_rule()];
306        let temp_file = std::env::temp_dir().join("test_export.json");
307
308        RuleExporter::export_to_file(rules, &temp_file, None).unwrap();
309
310        assert!(temp_file.exists());
311
312        // Cleanup
313        let _ = std::fs::remove_file(&temp_file);
314    }
315
316    #[test]
317    fn test_rule_importer_import_from_file() {
318        let rules = vec![create_test_rule()];
319        let export = RuleExport::new(rules, None);
320
321        let temp_file = std::env::temp_dir().join("test_import.json");
322        export.write_to_file(&temp_file).unwrap();
323
324        let imported = RuleImporter::import_from_file(&temp_file).unwrap();
325        assert_eq!(imported.len(), 1);
326
327        // Cleanup
328        let _ = std::fs::remove_file(&temp_file);
329    }
330}