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