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