1use kanban_core::{
2 Cascadable, DagGraph, Directed, EdgeSet, GraphError, Undirected, UndirectedGraph,
3};
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use super::edges::{BlocksEdge, RelatesEdge, SpawnsEdge};
8use crate::error::DependencyError;
9use crate::{CardId, KanbanResult};
10
11#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
25pub struct DependencyGraph {
26 #[serde(default)]
27 spawns: DagGraph<SpawnsEdge>,
28 #[serde(default)]
29 blocks: DagGraph<BlocksEdge>,
30 #[serde(default)]
31 relates: UndirectedGraph<RelatesEdge>,
32}
33
34impl DependencyGraph {
35 pub fn new() -> Self {
36 Self::default()
37 }
38
39 fn cascadable_parts_mut(&mut self) -> [&mut dyn Cascadable<NodeId = Uuid>; 3] {
49 [&mut self.spawns, &mut self.blocks, &mut self.relates]
50 }
51
52 fn edge_sets(&self) -> [&dyn EdgeSet<NodeId = Uuid>; 3] {
55 [&self.spawns, &self.blocks, &self.relates]
56 }
57
58 pub fn archive_node(&mut self, card: CardId) {
61 for sg in self.cascadable_parts_mut() {
62 sg.archive_node(card);
63 }
64 }
65
66 pub fn unarchive_node(&mut self, card: CardId) {
67 for sg in self.cascadable_parts_mut() {
68 sg.unarchive_node(card);
69 }
70 }
71
72 pub fn remove_node(&mut self, card: CardId) {
73 for sg in self.cascadable_parts_mut() {
74 sg.remove_node(card);
75 }
76 }
77
78 pub fn len(&self) -> usize {
81 self.edge_sets().iter().map(|g| g.len()).sum()
82 }
83
84 pub fn is_empty(&self) -> bool {
85 self.len() == 0
86 }
87
88 pub fn active_len(&self) -> usize {
89 self.edge_sets().iter().map(|g| g.active_len()).sum()
90 }
91
92 pub fn set_parent(&mut self, child: CardId, parent: CardId) -> KanbanResult<()> {
95 self.spawns
96 .add_edge_with_metadata(SpawnsEdge::new(parent, child))
97 .map_err(dep_err)
98 }
99
100 pub fn add_archived_spawns(&mut self, parent: CardId, child: CardId) -> KanbanResult<()> {
107 use kanban_core::Edge as _;
108 let mut edge = SpawnsEdge::new(parent, child);
109 edge.archive();
110 self.spawns.add_edge_with_metadata(edge).map_err(dep_err)
111 }
112
113 pub fn remove_parent(&mut self, child: CardId, parent: CardId) -> KanbanResult<()> {
114 use kanban_core::Graph as _;
118 self.spawns.remove_edge(parent, child).map_err(dep_err)
119 }
120
121 pub fn children(&self, parent: CardId) -> Vec<CardId> {
122 self.spawns.outgoing(parent)
123 }
124
125 pub fn parents(&self, child: CardId) -> Vec<CardId> {
126 self.spawns.incoming(child)
127 }
128
129 pub fn ancestors(&self, child: CardId) -> Vec<CardId> {
130 self.spawns.ancestors(child)
131 }
132
133 pub fn descendants(&self, parent: CardId) -> Vec<CardId> {
134 self.spawns.descendants(parent)
135 }
136
137 pub fn child_count(&self, parent: CardId) -> usize {
138 self.spawns.outgoing(parent).len()
139 }
140
141 pub fn set_block(&mut self, blocker: CardId, blocked: CardId) -> KanbanResult<()> {
146 self.set_block_with_severity(blocker, blocked, super::Severity::default())
147 }
148
149 pub fn set_block_with_severity(
151 &mut self,
152 blocker: CardId,
153 blocked: CardId,
154 severity: super::Severity,
155 ) -> KanbanResult<()> {
156 self.blocks
157 .add_edge_with_metadata(BlocksEdge::new(blocker, blocked, severity))
158 .map_err(dep_err)
159 }
160
161 pub fn add_archived_blocks(
165 &mut self,
166 blocker: CardId,
167 blocked: CardId,
168 severity: super::Severity,
169 ) -> KanbanResult<()> {
170 use kanban_core::Edge as _;
171 let mut edge = BlocksEdge::new(blocker, blocked, severity);
172 edge.archive();
173 self.blocks.add_edge_with_metadata(edge).map_err(dep_err)
174 }
175
176 pub fn unblock(&mut self, blocker: CardId, blocked: CardId) -> KanbanResult<()> {
177 use kanban_core::Graph as _;
178 self.blocks.remove_edge(blocker, blocked).map_err(dep_err)
179 }
180
181 pub fn blocked(&self, card: CardId) -> Vec<CardId> {
182 self.blocks.outgoing(card)
183 }
184
185 pub fn blockers(&self, card: CardId) -> Vec<CardId> {
186 self.blocks.incoming(card)
187 }
188
189 pub fn can_start<F>(&self, card: CardId, is_complete: F) -> bool
190 where
191 F: Fn(CardId) -> bool,
192 {
193 self.blockers(card).into_iter().all(is_complete)
194 }
195
196 pub fn relate(&mut self, a: CardId, b: CardId) -> KanbanResult<()> {
201 self.relate_with_kind(a, b, super::RelatesKind::default())
202 }
203
204 pub fn relate_with_kind(
207 &mut self,
208 a: CardId,
209 b: CardId,
210 kind: super::RelatesKind,
211 ) -> KanbanResult<()> {
212 self.relates
213 .add_edge_with_metadata(RelatesEdge::new(a, b, kind))
214 .map_err(dep_err)
215 }
216
217 pub fn add_archived_relates(
221 &mut self,
222 a: CardId,
223 b: CardId,
224 kind: super::RelatesKind,
225 ) -> KanbanResult<()> {
226 use kanban_core::Edge as _;
227 let mut edge = RelatesEdge::new(a, b, kind);
228 edge.archive();
229 self.relates.add_edge_with_metadata(edge).map_err(dep_err)
230 }
231
232 pub fn dissociate(&mut self, a: CardId, b: CardId) -> KanbanResult<()> {
233 use kanban_core::Graph as _;
234 self.relates.remove_edge(a, b).map_err(dep_err)
235 }
236
237 pub fn related(&self, card: CardId) -> Vec<CardId> {
238 self.relates.neighbors(card)
239 }
240
241 pub fn contains(&self, a: Uuid, b: Uuid) -> bool {
246 self.edge_sets().iter().any(|g| g.contains(a, b))
247 }
248
249 pub fn contains_archived(&self, a: Uuid, b: Uuid) -> bool {
253 self.edge_sets().iter().any(|g| g.contains_archived(a, b))
254 }
255
256 pub fn spawns_edges(&self) -> &[SpawnsEdge] {
264 self.spawns.edges()
265 }
266 pub fn blocks_edges(&self) -> &[BlocksEdge] {
267 self.blocks.edges()
268 }
269 pub fn relates_edges(&self) -> &[RelatesEdge] {
270 self.relates.edges()
271 }
272
273 pub fn from_validated_per_kind_edges(
285 spawns: Vec<SpawnsEdge>,
286 blocks: Vec<BlocksEdge>,
287 relates: Vec<RelatesEdge>,
288 ) -> KanbanResult<Self> {
289 use kanban_core::Edge as _;
290 let mut graph = Self::new();
291 for edge in spawns {
292 let (s, t) = (edge.source(), edge.target());
293 graph
294 .spawns
295 .add_edge_with_metadata(edge)
296 .map_err(|e| load_err_with_context(e, "spawns", s, t))?;
297 }
298 for edge in blocks {
299 let (s, t) = (edge.source(), edge.target());
300 graph
301 .blocks
302 .add_edge_with_metadata(edge)
303 .map_err(|e| load_err_with_context(e, "blocks", s, t))?;
304 }
305 for edge in relates {
306 let (s, t) = (edge.source(), edge.target());
307 graph
308 .relates
309 .add_edge_with_metadata(edge)
310 .map_err(|e| load_err_with_context(e, "relates", s, t))?;
311 }
312 Ok(graph)
313 }
314}
315
316fn load_err_with_context(
320 e: GraphError,
321 kind: &'static str,
322 source: Uuid,
323 target: Uuid,
324) -> crate::KanbanError {
325 crate::KanbanError::validation(format!(
326 "load failed on {kind} edge {source} -> {target}: {e}"
327 ))
328}
329
330fn dep_err(e: GraphError) -> crate::KanbanError {
331 let d: DependencyError = e.into();
332 d.into()
333}
334
335impl From<GraphError> for DependencyError {
336 fn from(e: GraphError) -> Self {
337 match e {
338 GraphError::Cycle => DependencyError::CycleDetected,
339 GraphError::SelfReference => DependencyError::SelfReference,
340 GraphError::EdgeNotFound => DependencyError::EdgeNotFound,
341 GraphError::Duplicate => DependencyError::DuplicateEdge,
342 }
343 }
344}
345
346#[cfg(test)]
347mod tests {
348 use super::*;
349 use uuid::Uuid;
350
351 fn ids() -> (Uuid, Uuid, Uuid) {
352 (Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4())
353 }
354
355 #[test]
356 fn test_new_returns_empty_graph() {
357 let graph = DependencyGraph::new();
358 assert_eq!(graph.len(), 0);
359 }
360
361 #[test]
362 fn test_default_graph_round_trips_through_serde() {
363 let graph = DependencyGraph::new();
364 let json = serde_json::to_string(&graph).unwrap();
365 let deserialized: DependencyGraph = serde_json::from_str(&json).unwrap();
366 assert_eq!(deserialized.len(), 0);
367 }
368
369 #[test]
370 fn test_dependency_graph_serializes_spawns_bucket_key() {
371 let graph = DependencyGraph::new();
377 let json = serde_json::to_value(&graph).unwrap();
378 let obj = json.as_object().expect("graph serialises to a JSON object");
379 assert!(
380 obj.contains_key("spawns"),
381 "DependencyGraph must serialise the spawns bucket under key `spawns`; got keys {:?}",
382 obj.keys().collect::<Vec<_>>()
383 );
384 assert!(
385 !obj.contains_key("parent_child"),
386 "legacy key `parent_child` must be gone from the wire format; got keys {:?}",
387 obj.keys().collect::<Vec<_>>()
388 );
389 }
390
391 #[test]
401 fn test_load_with_blocks_cycle_names_kind_and_endpoints() {
402 let (a, b, c) = ids();
403 let err = DependencyGraph::from_validated_per_kind_edges(
406 Vec::new(),
407 vec![
408 super::super::BlocksEdge::new(a, b, super::super::Severity::Medium),
409 super::super::BlocksEdge::new(b, c, super::super::Severity::Medium),
410 super::super::BlocksEdge::new(c, a, super::super::Severity::Medium),
411 ],
412 Vec::new(),
413 )
414 .unwrap_err();
415 let msg = err.to_string();
416 assert!(
417 msg.contains("blocks"),
418 "load error must name the kind; got: {msg}"
419 );
420 assert!(
421 msg.contains(&c.to_string()) && msg.contains(&a.to_string()),
422 "load error must name the offending endpoints; got: {msg}"
423 );
424 assert!(
425 msg.to_lowercase().contains("cycle"),
426 "load error must name the invariant; got: {msg}"
427 );
428 }
429
430 #[test]
431 fn test_load_with_relates_self_reference_names_kind_and_endpoint() {
432 let a = Uuid::new_v4();
433 let err = DependencyGraph::from_validated_per_kind_edges(
434 Vec::new(),
435 Vec::new(),
436 vec![super::super::RelatesEdge::new(
437 a,
438 a,
439 super::super::RelatesKind::General,
440 )],
441 )
442 .unwrap_err();
443 let msg = err.to_string();
444 assert!(msg.contains("relates"), "kind name in message: {msg}");
445 assert!(msg.contains(&a.to_string()), "endpoint in message: {msg}");
446 }
447
448 #[test]
449 fn test_set_parent_creates_parent_child_edge() {
450 let (parent, child, _) = ids();
451 let mut g = DependencyGraph::new();
452 g.set_parent(child, parent).unwrap();
453 assert_eq!(g.children(parent), vec![child]);
454 assert_eq!(g.parents(child), vec![parent]);
455 }
456
457 #[test]
458 fn test_set_parent_self_reference_returns_error() {
459 let (a, _, _) = ids();
460 let mut g = DependencyGraph::new();
461 assert!(g.set_parent(a, a).unwrap_err().is_self_reference());
462 }
463
464 #[test]
465 fn test_set_parent_cycle_returns_error() {
466 let (a, b, c) = ids();
467 let mut g = DependencyGraph::new();
468 g.set_parent(b, a).unwrap();
469 g.set_parent(c, b).unwrap();
470 assert!(g.set_parent(a, c).unwrap_err().is_cycle_detected());
471 }
472
473 #[test]
474 fn test_remove_parent_existing_succeeds() {
475 let (parent, child, _) = ids();
476 let mut g = DependencyGraph::new();
477 g.set_parent(child, parent).unwrap();
478 g.remove_parent(child, parent).unwrap();
479 assert!(g.children(parent).is_empty());
480 }
481
482 #[test]
483 fn test_set_parent_duplicate_returns_duplicate_edge_error() {
484 let (parent, child, _) = ids();
485 let mut g = DependencyGraph::new();
486 g.set_parent(child, parent).unwrap();
487 assert!(g.set_parent(child, parent).unwrap_err().is_duplicate_edge());
488 assert_eq!(g.children(parent), vec![child], "no duplicate stored");
489 }
490
491 #[test]
492 fn test_remove_parent_missing_returns_edge_not_found() {
493 let (parent, child, _) = ids();
494 let mut g = DependencyGraph::new();
495 assert!(g
496 .remove_parent(child, parent)
497 .unwrap_err()
498 .is_edge_not_found());
499 }
500
501 #[test]
502 fn test_ancestors_returns_transitive_parents() {
503 let (gp, p, c) = ids();
504 let mut g = DependencyGraph::new();
505 g.set_parent(p, gp).unwrap();
506 g.set_parent(c, p).unwrap();
507 let mut anc = g.ancestors(c);
508 anc.sort();
509 let mut expected = vec![gp, p];
510 expected.sort();
511 assert_eq!(anc, expected);
512 }
513
514 #[test]
515 fn test_descendants_returns_transitive_children() {
516 let (parent, child, grandchild) = ids();
517 let mut g = DependencyGraph::new();
518 g.set_parent(child, parent).unwrap();
519 g.set_parent(grandchild, child).unwrap();
520 let mut desc = g.descendants(parent);
521 desc.sort();
522 let mut expected = vec![child, grandchild];
523 expected.sort();
524 assert_eq!(desc, expected);
525 }
526
527 #[test]
528 fn test_child_count_matches_children_len() {
529 let (parent, a, b) = ids();
530 let mut g = DependencyGraph::new();
531 assert_eq!(g.child_count(parent), 0);
532 g.set_parent(a, parent).unwrap();
533 g.set_parent(b, parent).unwrap();
534 assert_eq!(g.child_count(parent), 2);
535 }
536
537 #[test]
540 fn test_add_blocks_creates_directed_edge() {
541 let (a, b, _) = ids();
542 let mut g = DependencyGraph::new();
543 g.set_block(a, b).unwrap();
544 assert_eq!(g.blocked(a), vec![b]);
545 assert_eq!(g.blockers(b), vec![a]);
546 }
547
548 #[test]
549 fn test_set_block_with_severity_preserves_metadata() {
550 let (a, b, _) = ids();
551 let mut g = DependencyGraph::new();
552 g.set_block_with_severity(a, b, super::super::Severity::Critical)
553 .unwrap();
554 assert_eq!(
555 g.blocks_edges()[0].severity,
556 super::super::Severity::Critical
557 );
558 }
559
560 #[test]
561 fn test_set_block_default_is_medium_severity() {
562 let (a, b, _) = ids();
563 let mut g = DependencyGraph::new();
564 g.set_block(a, b).unwrap();
565 assert_eq!(g.blocks_edges()[0].severity, super::super::Severity::Medium);
566 }
567
568 #[test]
569 fn test_add_blocks_cycle_returns_error() {
570 let (a, b, c) = ids();
571 let mut g = DependencyGraph::new();
572 g.set_block(a, b).unwrap();
573 g.set_block(b, c).unwrap();
574 assert!(g.set_block(c, a).unwrap_err().is_cycle_detected());
575 }
576
577 #[test]
578 fn test_set_block_duplicate_returns_duplicate_edge_error() {
579 let (a, b, _) = ids();
580 let mut g = DependencyGraph::new();
581 g.set_block(a, b).unwrap();
582 assert!(g.set_block(a, b).unwrap_err().is_duplicate_edge());
583 }
584
585 #[test]
586 fn test_relate_duplicate_in_either_orientation_returns_duplicate_edge_error() {
587 let (a, b, _) = ids();
588 let mut g = DependencyGraph::new();
589 g.relate(a, b).unwrap();
590 assert!(g.relate(a, b).unwrap_err().is_duplicate_edge());
591 assert!(g.relate(b, a).unwrap_err().is_duplicate_edge());
592 }
593
594 #[test]
595 fn test_add_blocks_self_reference_returns_error() {
596 let (a, _, _) = ids();
597 let mut g = DependencyGraph::new();
598 assert!(g.set_block(a, a).unwrap_err().is_self_reference());
599 }
600
601 #[test]
602 fn test_can_start_returns_true_when_all_blockers_complete() {
603 let (a, b, c) = ids();
604 let mut g = DependencyGraph::new();
605 g.set_block(a, c).unwrap();
606 g.set_block(b, c).unwrap();
607 assert!(!g.can_start(c, |id| id == a));
608 assert!(g.can_start(c, |_| true));
609 }
610
611 #[test]
614 fn test_add_relates_to_creates_bidirectional_edge() {
615 let (a, b, _) = ids();
616 let mut g = DependencyGraph::new();
617 g.relate(a, b).unwrap();
618 assert_eq!(g.related(a), vec![b]);
619 assert_eq!(g.related(b), vec![a]);
620 }
621
622 #[test]
623 fn test_relate_with_kind_preserves_metadata() {
624 let (a, b, _) = ids();
625 let mut g = DependencyGraph::new();
626 g.relate_with_kind(a, b, super::super::RelatesKind::Duplicates)
627 .unwrap();
628 assert_eq!(
629 g.relates_edges()[0].kind,
630 super::super::RelatesKind::Duplicates
631 );
632 }
633
634 #[test]
635 fn test_relate_default_is_general_kind() {
636 let (a, b, _) = ids();
637 let mut g = DependencyGraph::new();
638 g.relate(a, b).unwrap();
639 assert_eq!(
640 g.relates_edges()[0].kind,
641 super::super::RelatesKind::General
642 );
643 }
644
645 #[test]
646 fn test_add_relates_to_self_reference_returns_error() {
647 let (a, _, _) = ids();
648 let mut g = DependencyGraph::new();
649 assert!(g.relate(a, a).unwrap_err().is_self_reference());
650 }
651
652 #[test]
653 fn test_add_relates_permits_cycle() {
654 let (a, b, c) = ids();
655 let mut g = DependencyGraph::new();
656 g.relate(a, b).unwrap();
657 g.relate(b, c).unwrap();
658 assert!(g.relate(c, a).is_ok());
659 }
660
661 #[test]
664 fn test_archive_node_hides_edges_in_all_subgraphs() {
665 let (a, b, c) = ids();
666 let mut g = DependencyGraph::new();
667 g.set_parent(b, a).unwrap();
668 g.set_block(a, c).unwrap();
669 g.relate(a, c).unwrap();
670 g.archive_node(a);
671 assert!(g.children(a).is_empty());
672 assert!(g.blocked(a).is_empty());
673 assert!(g.related(a).is_empty());
674 assert_eq!(g.len(), 3);
675 assert_eq!(g.active_len(), 0);
676 }
677
678 #[test]
679 fn test_remove_node_removes_edges_in_all_subgraphs() {
680 let (a, b, c) = ids();
681 let mut g = DependencyGraph::new();
682 g.set_parent(b, a).unwrap();
683 g.set_block(a, c).unwrap();
684 g.relate(a, c).unwrap();
685 g.remove_node(a);
686 assert_eq!(g.len(), 0);
687 }
688
689 #[test]
692 fn test_contains_returns_false_for_archived_only_edge() {
693 let (a, b, _) = ids();
694 let mut g = DependencyGraph::new();
695 g.set_block(a, b).unwrap();
696 g.archive_node(a);
697 assert!(!g.contains(a, b), "active contains() must skip archived");
698 assert!(
699 g.contains_archived(a, b),
700 "contains_archived() picks up the archived edge"
701 );
702 }
703
704 #[test]
705 fn test_contains_returns_true_for_active_edge() {
706 let (a, b, _) = ids();
707 let mut g = DependencyGraph::new();
708 g.set_block(a, b).unwrap();
709 assert!(g.contains(a, b));
710 assert!(g.contains_archived(a, b));
711 }
712
713 #[test]
714 fn test_parent_child_and_blocks_are_independent() {
715 let (a, b, c) = ids();
716 let mut g = DependencyGraph::new();
717 g.set_parent(b, a).unwrap();
718 g.set_block(b, c).unwrap();
719 assert_eq!(g.children(a), vec![b]);
720 assert_eq!(g.blocked(b), vec![c]);
721 }
722}