Skip to main content

agentic_evolve_core/storage/
store.rs

1//! PatternStore — in-memory + disk persistence for patterns.
2
3use std::collections::HashMap;
4use std::path::{Path, PathBuf};
5
6use crate::types::error::{EvolveError, EvolveResult};
7use crate::types::pattern::Pattern;
8
9/// Pattern storage engine.
10#[derive(Debug)]
11pub struct PatternStore {
12    patterns: HashMap<String, Pattern>,
13    data_dir: Option<PathBuf>,
14}
15
16impl PatternStore {
17    pub fn new() -> Self {
18        Self {
19            patterns: HashMap::new(),
20            data_dir: None,
21        }
22    }
23
24    pub fn with_data_dir(data_dir: &Path) -> EvolveResult<Self> {
25        std::fs::create_dir_all(data_dir)?;
26        let mut store = Self {
27            patterns: HashMap::new(),
28            data_dir: Some(data_dir.to_path_buf()),
29        };
30        store.load_all()?;
31        Ok(store)
32    }
33
34    pub fn save(&mut self, pattern: &Pattern) -> EvolveResult<()> {
35        self.patterns
36            .insert(pattern.id.as_str().to_string(), pattern.clone());
37        if let Some(dir) = &self.data_dir {
38            let path = dir.join(format!("{}.json", pattern.id.as_str()));
39            let json = serde_json::to_string_pretty(pattern)?;
40            std::fs::write(path, json)?;
41        }
42        Ok(())
43    }
44
45    pub fn get(&self, id: &str) -> EvolveResult<&Pattern> {
46        self.patterns
47            .get(id)
48            .ok_or_else(|| EvolveError::PatternNotFound(id.to_string()))
49    }
50
51    pub fn get_mut(&mut self, id: &str) -> EvolveResult<&mut Pattern> {
52        self.patterns
53            .get_mut(id)
54            .ok_or_else(|| EvolveError::PatternNotFound(id.to_string()))
55    }
56
57    pub fn delete(&mut self, id: &str) -> EvolveResult<Pattern> {
58        let pattern = self
59            .patterns
60            .remove(id)
61            .ok_or_else(|| EvolveError::PatternNotFound(id.to_string()))?;
62        if let Some(dir) = &self.data_dir {
63            let path = dir.join(format!("{id}.json"));
64            if path.exists() {
65                std::fs::remove_file(path)?;
66            }
67        }
68        Ok(pattern)
69    }
70
71    pub fn list(&self) -> Vec<&Pattern> {
72        self.patterns.values().collect()
73    }
74
75    pub fn list_by_domain(&self, domain: &str) -> Vec<&Pattern> {
76        self.patterns
77            .values()
78            .filter(|p| p.domain == domain)
79            .collect()
80    }
81
82    pub fn list_by_language(&self, language: &str) -> Vec<&Pattern> {
83        self.patterns
84            .values()
85            .filter(|p| p.language.as_str() == language)
86            .collect()
87    }
88
89    pub fn search(&self, query: &str) -> Vec<&Pattern> {
90        let query_lower = query.to_lowercase();
91        self.patterns
92            .values()
93            .filter(|p| {
94                p.name.to_lowercase().contains(&query_lower)
95                    || p.domain.to_lowercase().contains(&query_lower)
96                    || p.template.to_lowercase().contains(&query_lower)
97                    || p.tags
98                        .iter()
99                        .any(|t| t.to_lowercase().contains(&query_lower))
100            })
101            .collect()
102    }
103
104    pub fn count(&self) -> usize {
105        self.patterns.len()
106    }
107
108    pub fn contains(&self, id: &str) -> bool {
109        self.patterns.contains_key(id)
110    }
111
112    pub fn clear(&mut self) -> EvolveResult<()> {
113        self.patterns.clear();
114        if let Some(dir) = &self.data_dir {
115            if dir.exists() {
116                for entry in std::fs::read_dir(dir)? {
117                    let entry = entry?;
118                    if entry.path().extension().is_some_and(|ext| ext == "json") {
119                        std::fs::remove_file(entry.path())?;
120                    }
121                }
122            }
123        }
124        Ok(())
125    }
126
127    fn load_all(&mut self) -> EvolveResult<()> {
128        let dir = match &self.data_dir {
129            Some(d) => d.clone(),
130            None => return Ok(()),
131        };
132        if !dir.exists() {
133            return Ok(());
134        }
135        for entry in std::fs::read_dir(&dir)? {
136            let entry = entry?;
137            let path = entry.path();
138            if path.extension().is_some_and(|ext| ext == "json") {
139                let content = std::fs::read_to_string(&path)?;
140                match serde_json::from_str::<Pattern>(&content) {
141                    Ok(pattern) => {
142                        self.patterns
143                            .insert(pattern.id.as_str().to_string(), pattern);
144                    }
145                    Err(e) => {
146                        tracing::warn!("Failed to load pattern from {:?}: {}", path, e);
147                    }
148                }
149            }
150        }
151        Ok(())
152    }
153}
154
155impl Default for PatternStore {
156    fn default() -> Self {
157        Self::new()
158    }
159}