1use crate::memory::error::{MemoryError, Result};
4use crate::memory::event_triggers::{TriggerConfig, TriggerEvent, TriggerPattern};
5use regex::Regex;
6use serde_json::Value;
7use std::collections::HashMap;
8use std::fs;
9use std::path::Path;
10use std::sync::Arc;
11use std::time::{Duration, SystemTime};
12use tokio::sync::RwLock;
13use tokio::time::interval;
14use tracing::{error, info, warn};
15
16pub struct TriggerConfigLoader {
18 config_path: String,
19 last_modified: Arc<RwLock<Option<SystemTime>>>,
20 current_config: Arc<RwLock<TriggerConfig>>,
21 hot_reload_enabled: bool,
22}
23
24impl TriggerConfigLoader {
25 pub fn new(config_path: String) -> Self {
27 Self {
28 config_path,
29 last_modified: Arc::new(RwLock::new(None)),
30 current_config: Arc::new(RwLock::new(TriggerConfig::default())),
31 hot_reload_enabled: false,
32 }
33 }
34
35 pub fn enable_hot_reload(&mut self, check_interval: Duration) {
37 self.hot_reload_enabled = true;
38
39 let config_path = self.config_path.clone();
40 let last_modified = self.last_modified.clone();
41 let current_config = self.current_config.clone();
42
43 tokio::spawn(async move {
44 let mut timer = interval(check_interval);
45
46 loop {
47 timer.tick().await;
48
49 if let Err(e) =
50 Self::check_and_reload_config(&config_path, &last_modified, ¤t_config)
51 .await
52 {
53 error!("Failed to check/reload config: {}", e);
54 }
55 }
56 });
57 }
58
59 pub async fn load_config(&self) -> Result<TriggerConfig> {
61 let config = Self::load_config_from_file(&self.config_path).await?;
62
63 {
65 let mut current = self.current_config.write().await;
66 *current = config.clone();
67 }
68
69 if let Ok(metadata) = fs::metadata(&self.config_path) {
70 if let Ok(modified) = metadata.modified() {
71 let mut last_mod = self.last_modified.write().await;
72 *last_mod = Some(modified);
73 }
74 }
75
76 Ok(config)
77 }
78
79 pub async fn get_current_config(&self) -> TriggerConfig {
81 self.current_config.read().await.clone()
82 }
83
84 pub async fn save_config(&self, config: &TriggerConfig) -> Result<()> {
86 Self::save_config_to_file(&self.config_path, config).await?;
87
88 {
90 let mut current = self.current_config.write().await;
91 *current = config.clone();
92 }
93
94 Ok(())
95 }
96
97 pub async fn validate_config_file(&self) -> Result<()> {
99 Self::validate_config_file_at_path(&self.config_path).await
100 }
101
102 async fn load_config_from_file(config_path: &str) -> Result<TriggerConfig> {
104 if !Path::new(config_path).exists() {
105 info!("Config file not found, creating default: {}", config_path);
106 let default_config = TriggerConfig::default();
107 Self::save_config_to_file(config_path, &default_config).await?;
108 return Ok(default_config);
109 }
110
111 let content = fs::read_to_string(config_path).map_err(|e| {
112 MemoryError::Configuration(format!("Failed to read config file {config_path}: {e}"))
113 })?;
114
115 let json_value: Value = serde_json::from_str(&content).map_err(|e| {
116 MemoryError::Configuration(format!("Invalid JSON in config file {config_path}: {e}"))
117 })?;
118
119 Self::parse_config_from_json(json_value).await
120 }
121
122 async fn parse_config_from_json(json_value: Value) -> Result<TriggerConfig> {
123 let obj = json_value.as_object().ok_or_else(|| {
124 MemoryError::Configuration("Config must be a JSON object".to_string())
125 })?;
126
127 let importance_multiplier = obj
129 .get("importance_multiplier")
130 .and_then(|v| v.as_f64())
131 .unwrap_or(2.0);
132
133 let max_processing_time_ms = obj
134 .get("max_processing_time_ms")
135 .and_then(|v| v.as_u64())
136 .unwrap_or(50);
137
138 let enable_ab_testing = obj
139 .get("enable_ab_testing")
140 .and_then(|v| v.as_bool())
141 .unwrap_or(false);
142
143 let mut patterns = HashMap::new();
145 if let Some(patterns_obj) = obj.get("patterns").and_then(|v| v.as_object()) {
146 for (trigger_name, pattern_value) in patterns_obj {
147 let trigger_event = Self::parse_trigger_event(trigger_name)?;
148 let pattern = Self::parse_trigger_pattern(pattern_value).await?;
149 patterns.insert(trigger_event, pattern);
150 }
151 }
152
153 let mut user_customizations = HashMap::new();
155 if let Some(customizations_obj) = obj.get("user_customizations").and_then(|v| v.as_object())
156 {
157 for (user_id, user_patterns_value) in customizations_obj {
158 if let Some(user_patterns_obj) = user_patterns_value.as_object() {
159 let mut user_patterns = HashMap::new();
160 for (trigger_name, pattern_value) in user_patterns_obj {
161 let trigger_event = Self::parse_trigger_event(trigger_name)?;
162 let pattern = Self::parse_trigger_pattern(pattern_value).await?;
163 user_patterns.insert(trigger_event, pattern);
164 }
165 user_customizations.insert(user_id.clone(), user_patterns);
166 }
167 }
168 }
169
170 Ok(TriggerConfig {
171 patterns,
172 importance_multiplier,
173 max_processing_time_ms,
174 enable_ab_testing,
175 user_customizations,
176 })
177 }
178
179 fn parse_trigger_event(trigger_name: &str) -> Result<TriggerEvent> {
180 match trigger_name {
181 "Security" => Ok(TriggerEvent::Security),
182 "Error" => Ok(TriggerEvent::Error),
183 "Performance" => Ok(TriggerEvent::Performance),
184 "BusinessCritical" => Ok(TriggerEvent::BusinessCritical),
185 "UserExperience" => Ok(TriggerEvent::UserExperience),
186 _ => Err(MemoryError::Configuration(format!(
187 "Unknown trigger event type: {trigger_name}"
188 ))),
189 }
190 }
191
192 async fn parse_trigger_pattern(pattern_value: &Value) -> Result<TriggerPattern> {
193 let pattern_obj = pattern_value.as_object().ok_or_else(|| {
194 MemoryError::Configuration("Trigger pattern must be an object".to_string())
195 })?;
196
197 let regex = pattern_obj
198 .get("regex")
199 .and_then(|v| v.as_str())
200 .ok_or_else(|| MemoryError::Configuration("Missing regex field".to_string()))?
201 .to_string();
202
203 Regex::new(®ex)
205 .map_err(|e| MemoryError::Configuration(format!("Invalid regex pattern: {e}")))?;
206
207 let keywords = pattern_obj
208 .get("keywords")
209 .and_then(|v| v.as_array())
210 .map(|arr| {
211 arr.iter()
212 .filter_map(|v| v.as_str())
213 .map(|s| s.to_string())
214 .collect()
215 })
216 .unwrap_or_default();
217
218 let context_boosters = pattern_obj
219 .get("context_boosters")
220 .and_then(|v| v.as_array())
221 .map(|arr| {
222 arr.iter()
223 .filter_map(|v| v.as_str())
224 .map(|s| s.to_string())
225 .collect()
226 })
227 .unwrap_or_default();
228
229 let confidence_threshold = pattern_obj
230 .get("confidence_threshold")
231 .and_then(|v| v.as_f64())
232 .unwrap_or(0.7);
233
234 let enabled = pattern_obj
235 .get("enabled")
236 .and_then(|v| v.as_bool())
237 .unwrap_or(true);
238
239 let mut pattern = TriggerPattern::new(regex, keywords)?;
240 pattern.context_boosters = context_boosters;
241 pattern.confidence_threshold = confidence_threshold;
242 pattern.enabled = enabled;
243
244 Ok(pattern)
245 }
246
247 async fn save_config_to_file(config_path: &str, config: &TriggerConfig) -> Result<()> {
248 let json_value = Self::config_to_json(config).await?;
249 let content = serde_json::to_string_pretty(&json_value)
250 .map_err(|e| MemoryError::Configuration(format!("Failed to serialize config: {e}")))?;
251
252 fs::write(config_path, content).map_err(|e| {
253 MemoryError::Configuration(format!("Failed to write config file {config_path}: {e}"))
254 })?;
255
256 info!("Configuration saved to: {}", config_path);
257 Ok(())
258 }
259
260 async fn config_to_json(config: &TriggerConfig) -> Result<Value> {
261 let mut patterns_obj = serde_json::Map::new();
262 for (trigger_event, pattern) in &config.patterns {
263 let trigger_name = match trigger_event {
264 TriggerEvent::Security => "Security",
265 TriggerEvent::Error => "Error",
266 TriggerEvent::Performance => "Performance",
267 TriggerEvent::BusinessCritical => "BusinessCritical",
268 TriggerEvent::UserExperience => "UserExperience",
269 };
270
271 let pattern_obj = serde_json::json!({
272 "regex": pattern.regex,
273 "keywords": pattern.keywords,
274 "context_boosters": pattern.context_boosters,
275 "confidence_threshold": pattern.confidence_threshold,
276 "enabled": pattern.enabled
277 });
278
279 patterns_obj.insert(trigger_name.to_string(), pattern_obj);
280 }
281
282 let mut user_customizations_obj = serde_json::Map::new();
283 for (user_id, user_patterns) in &config.user_customizations {
284 let mut user_patterns_obj = serde_json::Map::new();
285 for (trigger_event, pattern) in user_patterns {
286 let trigger_name = match trigger_event {
287 TriggerEvent::Security => "Security",
288 TriggerEvent::Error => "Error",
289 TriggerEvent::Performance => "Performance",
290 TriggerEvent::BusinessCritical => "BusinessCritical",
291 TriggerEvent::UserExperience => "UserExperience",
292 };
293
294 let pattern_obj = serde_json::json!({
295 "regex": pattern.regex,
296 "keywords": pattern.keywords,
297 "context_boosters": pattern.context_boosters,
298 "confidence_threshold": pattern.confidence_threshold,
299 "enabled": pattern.enabled
300 });
301
302 user_patterns_obj.insert(trigger_name.to_string(), pattern_obj);
303 }
304 user_customizations_obj.insert(user_id.clone(), Value::Object(user_patterns_obj));
305 }
306
307 Ok(serde_json::json!({
308 "importance_multiplier": config.importance_multiplier,
309 "max_processing_time_ms": config.max_processing_time_ms,
310 "enable_ab_testing": config.enable_ab_testing,
311 "patterns": Value::Object(patterns_obj),
312 "user_customizations": Value::Object(user_customizations_obj)
313 }))
314 }
315
316 async fn validate_config_file_at_path(config_path: &str) -> Result<()> {
317 if !Path::new(config_path).exists() {
318 return Err(MemoryError::Configuration(format!(
319 "Config file does not exist: {config_path}"
320 )));
321 }
322
323 Self::load_config_from_file(config_path).await?;
325 Ok(())
326 }
327
328 async fn check_and_reload_config(
329 config_path: &str,
330 last_modified: &Arc<RwLock<Option<SystemTime>>>,
331 current_config: &Arc<RwLock<TriggerConfig>>,
332 ) -> Result<()> {
333 if let Ok(metadata) = fs::metadata(config_path) {
334 if let Ok(modified) = metadata.modified() {
335 let should_reload = {
336 let last_mod = last_modified.read().await;
337 match &*last_mod {
338 Some(last) => modified > *last,
339 None => true,
340 }
341 };
342
343 if should_reload {
344 match Self::load_config_from_file(config_path).await {
345 Ok(new_config) => {
346 {
347 let mut config = current_config.write().await;
348 *config = new_config;
349 }
350 {
351 let mut last_mod = last_modified.write().await;
352 *last_mod = Some(modified);
353 }
354 info!("Configuration hot-reloaded from: {}", config_path);
355 }
356 Err(e) => {
357 warn!("Failed to reload config (keeping current): {}", e);
358 }
359 }
360 }
361 }
362 }
363
364 Ok(())
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371 use tempfile::NamedTempFile;
372 use tokio::time::sleep;
373
374 #[tokio::test]
375 async fn test_load_default_config() {
376 let temp_file = NamedTempFile::new().unwrap();
377 let config_path = temp_file.path().to_str().unwrap().to_string();
378
379 std::fs::remove_file(&config_path).unwrap();
381
382 let loader = TriggerConfigLoader::new(config_path.clone());
383 let config = loader.load_config().await.unwrap();
384
385 assert_eq!(config.importance_multiplier, 2.0);
386 assert_eq!(config.max_processing_time_ms, 50);
387 assert_eq!(config.patterns.len(), 5);
388
389 assert!(Path::new(&config_path).exists());
391 }
392
393 #[tokio::test]
394 async fn test_load_custom_config() {
395 let temp_file = NamedTempFile::new().unwrap();
396 let config_path = temp_file.path().to_str().unwrap().to_string();
397
398 let custom_config = r#"{
399 "importance_multiplier": 3.0,
400 "max_processing_time_ms": 100,
401 "enable_ab_testing": true,
402 "patterns": {
403 "Security": {
404 "regex": "(?i)(security|threat)",
405 "keywords": ["security", "threat"],
406 "context_boosters": ["critical"],
407 "confidence_threshold": 0.9,
408 "enabled": true
409 }
410 },
411 "user_customizations": {}
412 }"#;
413
414 std::fs::write(&config_path, custom_config).unwrap();
415
416 let loader = TriggerConfigLoader::new(config_path);
417 let config = loader.load_config().await.unwrap();
418
419 assert_eq!(config.importance_multiplier, 3.0);
420 assert_eq!(config.max_processing_time_ms, 100);
421 assert!(config.enable_ab_testing);
422 assert_eq!(config.patterns.len(), 1);
423 }
424
425 #[tokio::test]
426 async fn test_save_and_reload() {
427 let temp_file = NamedTempFile::new().unwrap();
428 let config_path = temp_file.path().to_str().unwrap().to_string();
429
430 let loader = TriggerConfigLoader::new(config_path.clone());
431
432 let mut config = TriggerConfig::default();
434 config.importance_multiplier = 4.0;
435
436 loader.save_config(&config).await.unwrap();
438
439 let new_loader = TriggerConfigLoader::new(config_path);
441 let loaded_config = new_loader.load_config().await.unwrap();
442
443 assert_eq!(loaded_config.importance_multiplier, 4.0);
444 }
445
446 #[tokio::test]
447 async fn test_config_validation() {
448 let temp_file = NamedTempFile::new().unwrap();
449 let config_path = temp_file.path().to_str().unwrap().to_string();
450
451 std::fs::write(&config_path, "invalid json").unwrap();
453
454 let loader = TriggerConfigLoader::new(config_path);
455 assert!(loader.validate_config_file().await.is_err());
456 }
457
458 #[tokio::test]
459 async fn test_hot_reload() {
460 let temp_file = NamedTempFile::new().unwrap();
461 let config_path = temp_file.path().to_str().unwrap().to_string();
462
463 let initial_config = r#"{
465 "importance_multiplier": 2.0,
466 "max_processing_time_ms": 50,
467 "enable_ab_testing": false,
468 "patterns": {},
469 "user_customizations": {}
470 }"#;
471 std::fs::write(&config_path, initial_config).unwrap();
472
473 let mut loader = TriggerConfigLoader::new(config_path.clone());
474 loader.enable_hot_reload(Duration::from_millis(50));
475
476 let config = loader.load_config().await.unwrap();
478 assert_eq!(config.importance_multiplier, 2.0);
479
480 sleep(Duration::from_millis(100)).await;
482
483 let updated_config = r#"{
485 "importance_multiplier": 5.0,
486 "max_processing_time_ms": 50,
487 "enable_ab_testing": false,
488 "patterns": {},
489 "user_customizations": {}
490 }"#;
491 std::fs::write(&config_path, updated_config).unwrap();
492
493 sleep(Duration::from_millis(200)).await;
495
496 let current_config = loader.get_current_config().await;
497 assert_eq!(current_config.importance_multiplier, 5.0);
498 }
499}