1use std::collections::HashMap;
7
8#[derive(Debug, Clone)]
10pub struct Entity {
11 pub id: String,
12 pub name: String,
13 pub entity_type: String,
14 pub description: String,
15}
16
17#[derive(Debug, Clone)]
19pub struct Relation {
20 pub source: String,
21 pub target: String,
22 pub relation_type: String,
23 pub description: String,
24 pub doc_id: Option<String>,
25}
26
27#[derive(Debug, Clone)]
29pub struct Community {
30 pub id: usize,
31 pub entities: Vec<String>,
32 pub level: usize,
33}
34
35#[derive(Clone)]
37pub struct GraphStore {
38 entities: HashMap<String, Entity>,
39 relations: Vec<Relation>,
40 adjacency: HashMap<String, Vec<usize>>,
41 communities: Vec<Community>,
42 community_summaries: Vec<String>,
43}
44
45impl GraphStore {
46 pub fn new() -> Self {
48 Self {
49 entities: HashMap::new(),
50 relations: Vec::new(),
51 adjacency: HashMap::new(),
52 communities: Vec::new(),
53 community_summaries: Vec::new(),
54 }
55 }
56
57 pub fn add_entity(&mut self, entity: Entity) {
59 self.entities.insert(entity.id.clone(), entity);
60 }
61
62 pub fn add_relation(&mut self, relation: Relation) {
64 let idx = self.relations.len();
65 self.adjacency
66 .entry(relation.source.clone())
67 .or_default()
68 .push(idx);
69 self.adjacency
70 .entry(relation.target.clone())
71 .or_default()
72 .push(idx);
73 self.relations.push(relation);
74 }
75
76 pub fn get_entity(&self, id: &str) -> Option<&Entity> {
78 self.entities.get(id)
79 }
80
81 pub fn entity_ids(&self) -> Vec<String> {
83 self.entities.keys().cloned().collect()
84 }
85
86 pub fn entity_count(&self) -> usize {
88 self.entities.len()
89 }
90
91 pub fn relation_count(&self) -> usize {
93 self.relations.len()
94 }
95
96 pub fn neighbors(&self, entity_id: &str) -> Vec<String> {
98 let mut result = Vec::new();
99 if let Some(indices) = self.adjacency.get(entity_id) {
100 for &idx in indices {
101 let rel = &self.relations[idx];
102 if rel.source == entity_id {
103 result.push(rel.target.clone());
104 } else {
105 result.push(rel.source.clone());
106 }
107 }
108 }
109 result.sort();
110 result.dedup();
111 result
112 }
113
114 pub fn subgraph(&self, seed: &str, depth: usize) -> (Vec<Entity>, Vec<Relation>) {
117 let mut visited = std::collections::HashSet::new();
118 let mut frontier = vec![seed.to_string()];
119 visited.insert(seed.to_string());
120
121 for _ in 0..depth {
122 let mut next_frontier = Vec::new();
123 for eid in &frontier {
124 for nb in self.neighbors(eid) {
125 if visited.insert(nb.clone()) {
126 next_frontier.push(nb);
127 }
128 }
129 }
130 frontier = next_frontier;
131 }
132
133 let entities: Vec<Entity> = visited
134 .iter()
135 .filter_map(|id| self.entities.get(id))
136 .cloned()
137 .collect();
138
139 let relations: Vec<Relation> = self
140 .relations
141 .iter()
142 .filter(|r| visited.contains(&r.source) && visited.contains(&r.target))
143 .cloned()
144 .collect();
145
146 (entities, relations)
147 }
148
149 pub fn relations_for(&self, entity_id: &str) -> Vec<&Relation> {
151 self.adjacency
152 .get(entity_id)
153 .map(|indices| indices.iter().map(|&i| &self.relations[i]).collect())
154 .unwrap_or_default()
155 }
156
157 pub fn all_entities(&self) -> &HashMap<String, Entity> {
159 &self.entities
160 }
161
162 pub fn all_relations(&self) -> &[Relation] {
164 &self.relations
165 }
166
167 pub fn set_communities(&mut self, communities: Vec<Community>) {
171 self.communities = communities;
172 }
173
174 pub fn communities(&self) -> &[Community] {
176 &self.communities
177 }
178
179 pub fn set_community_summaries(&mut self, summaries: Vec<String>) {
181 self.community_summaries = summaries;
182 }
183
184 pub fn community_summaries(&self) -> &[String] {
186 &self.community_summaries
187 }
188
189 pub fn community_summaries_mut(&mut self) -> &mut Vec<String> {
191 &mut self.community_summaries
192 }
193}
194
195impl Default for GraphStore {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 fn make_entity(id: &str, name: &str) -> Entity {
206 Entity {
207 id: id.to_string(),
208 name: name.to_string(),
209 entity_type: "concept".to_string(),
210 description: format!("Entity {}", name),
211 }
212 }
213
214 fn make_relation(source: &str, target: &str, rel_type: &str) -> Relation {
215 Relation {
216 source: source.to_string(),
217 target: target.to_string(),
218 relation_type: rel_type.to_string(),
219 description: format!("{} {}", rel_type, target),
220 doc_id: None,
221 }
222 }
223
224 #[test]
225 fn test_add_and_get_entity() {
226 let mut store = GraphStore::new();
227 store.add_entity(make_entity("e1", "Rust"));
228 assert_eq!(store.entity_count(), 1);
229 assert!(store.get_entity("e1").is_some());
230 assert!(store.get_entity("e2").is_none());
231 }
232
233 #[test]
234 fn test_add_entity_replaces_existing() {
235 let mut store = GraphStore::new();
236 store.add_entity(make_entity("e1", "Rust"));
237 store.add_entity(Entity {
238 id: "e1".to_string(),
239 name: "Rust Lang".to_string(),
240 entity_type: "language".to_string(),
241 description: "Updated".to_string(),
242 });
243 assert_eq!(store.entity_count(), 1);
244 assert_eq!(store.get_entity("e1").unwrap().name, "Rust Lang");
245 }
246
247 #[test]
248 fn test_add_relation_and_count() {
249 let mut store = GraphStore::new();
250 store.add_entity(make_entity("e1", "Alice"));
251 store.add_entity(make_entity("e2", "Bob"));
252 store.add_relation(make_relation("e1", "e2", "mentors"));
253 assert_eq!(store.relation_count(), 1);
254 }
255
256 #[test]
257 fn test_neighbors_bidirectional() {
258 let mut store = GraphStore::new();
259 store.add_entity(make_entity("e1", "Alice"));
260 store.add_entity(make_entity("e2", "Bob"));
261 store.add_relation(make_relation("e1", "e2", "mentors"));
262
263 let alice_neighbors = store.neighbors("e1");
264 assert_eq!(alice_neighbors, vec!["e2"]);
265
266 let bob_neighbors = store.neighbors("e2");
267 assert_eq!(bob_neighbors, vec!["e1"]);
268 }
269
270 #[test]
271 fn test_neighbors_dedup() {
272 let mut store = GraphStore::new();
273 store.add_entity(make_entity("e1", "Alice"));
274 store.add_entity(make_entity("e2", "Bob"));
275 store.add_relation(make_relation("e1", "e2", "mentor"));
276 store.add_relation(make_relation("e1", "e2", "collaborator"));
277
278 let neighbors = store.neighbors("e1");
279 assert_eq!(neighbors, vec!["e2"]); }
281
282 #[test]
283 fn test_subgraph_depth_1() {
284 let mut store = GraphStore::new();
285 store.add_entity(make_entity("e1", "Alice"));
286 store.add_entity(make_entity("e2", "Bob"));
287 store.add_entity(make_entity("e3", "Charlie"));
288 store.add_relation(make_relation("e1", "e2", "mentor"));
289 store.add_relation(make_relation("e2", "e3", "colleague"));
290
291 let (entities, relations) = store.subgraph("e1", 1);
292 assert_eq!(entities.len(), 2);
294 assert_eq!(relations.len(), 1); }
296
297 #[test]
298 fn test_community_management() {
299 let mut store = GraphStore::new();
300 store.set_communities(vec![Community {
301 id: 0,
302 entities: vec!["e1".to_string(), "e2".to_string()],
303 level: 0,
304 }]);
305 assert_eq!(store.communities().len(), 1);
306
307 store.set_community_summaries(vec!["Community of Alice and Bob".to_string()]);
308 assert_eq!(store.community_summaries().len(), 1);
309 assert_eq!(store.community_summaries()[0], "Community of Alice and Bob");
310 }
311
312 #[test]
313 fn test_entity_ids() {
314 let mut store = GraphStore::new();
315 store.add_entity(make_entity("e1", "A"));
316 store.add_entity(make_entity("e2", "B"));
317 let mut ids = store.entity_ids();
318 ids.sort();
319 assert_eq!(ids, vec!["e1", "e2"]);
320 }
321
322 #[test]
323 fn test_relations_for_entity() {
324 let mut store = GraphStore::new();
325 store.add_entity(make_entity("e1", "Alice"));
326 store.add_entity(make_entity("e2", "Bob"));
327 store.add_entity(make_entity("e3", "Charlie"));
328 store.add_relation(make_relation("e1", "e2", "mentor"));
329 store.add_relation(make_relation("e1", "e3", "advisor"));
330
331 let rels = store.relations_for("e1");
332 assert_eq!(rels.len(), 2);
333 }
334
335 #[test]
336 fn test_default_is_empty() {
337 let store = GraphStore::default();
338 assert_eq!(store.entity_count(), 0);
339 assert_eq!(store.relation_count(), 0);
340 assert!(store.communities().is_empty());
341 assert!(store.community_summaries().is_empty());
342 }
343}