1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4use super::algorithms;
5use super::edge::Edge;
6
7#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21pub struct EdgeStore<E> {
22 edges: Vec<E>,
23}
24
25impl<E> Default for EdgeStore<E> {
26 fn default() -> Self {
27 Self { edges: Vec::new() }
28 }
29}
30
31impl<E> EdgeStore<E> {
32 pub fn new() -> Self {
34 Self::default()
35 }
36
37 pub(crate) fn add_edge(&mut self, edge: E) {
47 self.edges.push(edge);
48 }
49
50 pub fn retain<F: FnMut(&E) -> bool>(&mut self, f: F) {
55 self.edges.retain(f);
56 }
57
58 pub fn edges(&self) -> &[E] {
60 &self.edges
61 }
62
63 pub fn edge_count(&self) -> usize {
65 self.edges.len()
66 }
67}
68
69impl<E: Edge> EdgeStore<E> {
70 pub fn remove_node(&mut self, node_id: E::NodeId) {
72 self.edges.retain(|e| !e.involves(node_id));
73 }
74
75 pub fn archive_node(&mut self, node_id: E::NodeId) {
77 for edge in &mut self.edges {
78 if edge.involves(node_id) {
79 edge.archive();
80 }
81 }
82 }
83
84 pub fn unarchive_node(&mut self, node_id: E::NodeId) {
86 for edge in &mut self.edges {
87 if edge.involves(node_id) {
88 edge.unarchive();
89 }
90 }
91 }
92
93 pub fn remove_directed_edge(&mut self, source: E::NodeId, target: E::NodeId) -> bool {
100 let before = self.edges.len();
101 self.edges
102 .retain(|e| !(e.is_active() && e.source() == source && e.target() == target));
103 self.edges.len() < before
104 }
105
106 pub fn remove_undirected_edge(&mut self, a: E::NodeId, b: E::NodeId) -> bool {
111 let before = self.edges.len();
112 self.edges.retain(|e| {
113 !(e.is_active()
114 && ((e.source() == a && e.target() == b) || (e.source() == b && e.target() == a)))
115 });
116 self.edges.len() < before
117 }
118
119 pub fn outgoing(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
121 self.edges.iter().filter(move |e| e.source() == node_id)
122 }
123
124 pub fn incoming(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
126 self.edges.iter().filter(move |e| e.target() == node_id)
127 }
128
129 pub fn outgoing_active(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
131 self.edges
132 .iter()
133 .filter(move |e| e.source() == node_id && e.is_active())
134 }
135
136 pub fn incoming_active(&self, node_id: E::NodeId) -> impl Iterator<Item = &E> {
138 self.edges
139 .iter()
140 .filter(move |e| e.target() == node_id && e.is_active())
141 }
142
143 pub fn active_edges(&self) -> impl Iterator<Item = &E> {
145 self.edges.iter().filter(|e| e.is_active())
146 }
147
148 pub fn adjacency_list(&self) -> HashMap<E::NodeId, Vec<E::NodeId>> {
152 let mut adj_list: HashMap<E::NodeId, Vec<E::NodeId>> = HashMap::new();
153 for edge in self.active_edges() {
154 adj_list
155 .entry(edge.source())
156 .or_default()
157 .push(edge.target());
158 }
159 adj_list
160 }
161
162 pub fn active_edge_count(&self) -> usize {
164 self.edges.iter().filter(|e| e.is_active()).count()
165 }
166
167 pub fn would_create_cycle(&self, source: E::NodeId, target: E::NodeId) -> bool {
170 let adj_list = self.adjacency_list();
171 algorithms::would_create_cycle(&adj_list, source, target)
172 }
173
174 pub fn has_cycle(&self) -> bool {
176 let adj_list = self.adjacency_list();
177 algorithms::has_cycle(&adj_list)
178 }
179
180 pub fn reachable_from(&self, start: E::NodeId) -> std::collections::HashSet<E::NodeId> {
183 let adj_list = self.adjacency_list();
184 algorithms::reachable_from(&adj_list, start)
185 }
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191 use crate::graph::edge::EdgeBase;
192 use uuid::Uuid;
193
194 fn base(source: Uuid, target: Uuid) -> EdgeBase {
195 EdgeBase::new(source, target)
196 }
197
198 #[test]
199 fn test_new_returns_empty_edge_store() {
200 let graph: EdgeStore<EdgeBase> = EdgeStore::new();
201 assert_eq!(graph.edge_count(), 0);
202 }
203
204 #[test]
205 fn test_add_edge_increments_count() {
206 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
207 let source = Uuid::new_v4();
208 let target = Uuid::new_v4();
209
210 graph.add_edge(base(source, target));
211 assert_eq!(graph.edge_count(), 1);
212 }
213
214 #[test]
215 fn test_remove_directed_edge_only_matches_exact_orientation() {
216 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
217 let a = Uuid::new_v4();
218 let b = Uuid::new_v4();
219
220 graph.add_edge(base(a, b));
221 assert!(!graph.remove_directed_edge(b, a), "wrong orientation");
222 assert_eq!(graph.edge_count(), 1);
223 assert!(graph.remove_directed_edge(a, b));
224 assert_eq!(graph.edge_count(), 0);
225 }
226
227 #[test]
228 fn test_remove_undirected_edge_matches_either_orientation() {
229 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
230 let a = Uuid::new_v4();
231 let b = Uuid::new_v4();
232
233 graph.add_edge(base(a, b));
234 assert!(
235 graph.remove_undirected_edge(b, a),
236 "symmetric on either ordering"
237 );
238 assert_eq!(graph.edge_count(), 0);
239 }
240
241 #[test]
242 fn test_remove_directed_edge_preserves_archived_record() {
243 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
244 let a = Uuid::new_v4();
245 let b = Uuid::new_v4();
246
247 let mut archived = base(a, b);
248 archived.archive();
249 graph.add_edge(archived);
250 graph.add_edge(base(a, b)); assert!(graph.remove_directed_edge(a, b));
253 assert_eq!(
254 graph.edge_count(),
255 1,
256 "archived record must survive the active remove"
257 );
258 assert_eq!(graph.active_edge_count(), 0);
259 }
260
261 #[test]
262 fn test_remove_directed_edge_returns_false_when_only_archived_exists() {
263 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
264 let a = Uuid::new_v4();
265 let b = Uuid::new_v4();
266 let mut archived = base(a, b);
267 archived.archive();
268 graph.add_edge(archived);
269
270 assert!(
271 !graph.remove_directed_edge(a, b),
272 "no active edge means remove is a no-op"
273 );
274 assert_eq!(graph.edge_count(), 1, "archived record untouched");
275 }
276
277 #[test]
278 fn test_remove_undirected_edge_preserves_archived_record() {
279 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
280 let a = Uuid::new_v4();
281 let b = Uuid::new_v4();
282
283 let mut archived = base(a, b);
284 archived.archive();
285 graph.add_edge(archived);
286 graph.add_edge(base(b, a)); assert!(graph.remove_undirected_edge(a, b));
289 assert_eq!(graph.edge_count(), 1);
290 assert_eq!(graph.active_edge_count(), 0);
291 }
292
293 #[test]
294 fn test_remove_node_drops_every_incident_edge() {
295 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
296 let node_a = Uuid::new_v4();
297 let node_b = Uuid::new_v4();
298 let node_c = Uuid::new_v4();
299
300 graph.add_edge(base(node_a, node_b));
301 graph.add_edge(base(node_b, node_c));
302 graph.add_edge(base(node_c, node_a));
303
304 graph.remove_node(node_b);
305 assert_eq!(graph.edge_count(), 1);
306 }
307
308 #[test]
309 fn test_archive_node_hides_incident_edges_from_active_count() {
310 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
311 let node_a = Uuid::new_v4();
312 let node_b = Uuid::new_v4();
313 let node_c = Uuid::new_v4();
314
315 graph.add_edge(base(node_a, node_b));
316 graph.add_edge(base(node_b, node_c));
317
318 assert_eq!(graph.active_edge_count(), 2);
319
320 graph.archive_node(node_b);
321 assert_eq!(graph.edge_count(), 2);
322 assert_eq!(graph.active_edge_count(), 0);
323 }
324
325 #[test]
326 fn test_unarchive_node_restores_active_count() {
327 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
328 let node_a = Uuid::new_v4();
329 let node_b = Uuid::new_v4();
330
331 graph.add_edge(base(node_a, node_b));
332 graph.archive_node(node_a);
333 assert_eq!(graph.active_edge_count(), 0);
334
335 graph.unarchive_node(node_a);
336 assert_eq!(graph.active_edge_count(), 1);
337 }
338
339 #[test]
340 fn test_outgoing_and_incoming_partition_by_endpoint_role() {
341 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
342 let node_a = Uuid::new_v4();
343 let node_b = Uuid::new_v4();
344 let node_c = Uuid::new_v4();
345
346 graph.add_edge(base(node_a, node_b));
347 graph.add_edge(base(node_a, node_c));
348 graph.add_edge(base(node_c, node_a));
349
350 assert_eq!(graph.outgoing(node_a).count(), 2);
351 assert_eq!(graph.incoming(node_a).count(), 1);
352 assert_eq!(graph.outgoing(node_b).count(), 0);
353 assert_eq!(graph.incoming(node_b).count(), 1);
354 }
355
356 #[test]
357 fn test_would_create_cycle_detects_directed_path_closing() {
358 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
359 let node_a = Uuid::new_v4();
360 let node_b = Uuid::new_v4();
361 let node_c = Uuid::new_v4();
362
363 graph.add_edge(base(node_a, node_b));
364 graph.add_edge(base(node_b, node_c));
365
366 assert!(graph.would_create_cycle(node_c, node_a));
367 assert!(graph.would_create_cycle(node_c, node_b));
368 }
369
370 #[test]
371 fn test_adjacency_list_counts_active_outgoing_per_node() {
372 let mut graph: EdgeStore<EdgeBase> = EdgeStore::new();
373 let node_a = Uuid::new_v4();
374 let node_b = Uuid::new_v4();
375 let node_c = Uuid::new_v4();
376
377 graph.add_edge(base(node_a, node_b));
378 graph.add_edge(base(node_b, node_c));
379
380 let adj_list = graph.adjacency_list();
381 assert_eq!(adj_list.len(), 2);
382 assert_eq!(adj_list.get(&node_a).unwrap().len(), 1);
383 assert_eq!(adj_list.get(&node_b).unwrap().len(), 1);
384 }
385}