ricecoder_learning/
rule_exchange.rs1use crate::error::{LearningError, Result};
3use crate::models::Rule;
4use serde::{Deserialize, Serialize};
5use std::path::Path;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ExportMetadata {
10 pub version: String,
12 pub exported_at: String,
14 pub rule_count: usize,
16 pub description: Option<String>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct RuleExport {
23 pub metadata: ExportMetadata,
25 pub rules: Vec<Rule>,
27}
28
29impl RuleExport {
30 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 pub fn to_json(&self) -> Result<String> {
46 serde_json::to_string_pretty(self)
47 .map_err(|e| LearningError::SerializationError(e))
48 }
49
50 pub fn from_json(json: &str) -> Result<Self> {
52 serde_json::from_str(json)
53 .map_err(|e| LearningError::SerializationError(e))
54 }
55
56 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 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
71pub struct RuleExporter;
73
74impl RuleExporter {
75 pub fn export_rules(rules: Vec<Rule>, description: Option<String>) -> Result<RuleExport> {
77 Ok(RuleExport::new(rules, description))
78 }
79
80 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 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
97pub struct RuleImporter;
99
100impl RuleImporter {
101 pub fn import_from_json(json: &str) -> Result<Vec<Rule>> {
103 let export = RuleExport::from_json(json)?;
104 Ok(export.rules)
105 }
106
107 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 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 fn validate_rule(rule: &Rule) -> Result<()> {
131 if rule.pattern.is_empty() {
133 return Err(LearningError::RuleValidationFailed(
134 "Rule pattern cannot be empty".to_string(),
135 ));
136 }
137
138 if rule.action.is_empty() {
140 return Err(LearningError::RuleValidationFailed(
141 "Rule action cannot be empty".to_string(),
142 ));
143 }
144
145 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 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 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 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 let _ = std::fs::remove_file(&temp_file);
329 }
330}