1use crate::facts::Ecosystem;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub struct DepNode {
13 pub name: String,
15 pub version: String,
17 pub ecosystem: Ecosystem,
19 pub direct: bool,
21}
22
23#[derive(Debug, Clone, Default)]
26pub struct DepGraph {
27 nodes: Vec<DepNode>,
28 edges: Vec<Vec<usize>>,
29}
30
31impl DepGraph {
32 pub fn new() -> Self {
34 DepGraph::default()
35 }
36
37 pub fn add_node(&mut self, node: DepNode) -> usize {
39 let idx = self.nodes.len();
40 self.nodes.push(node);
41 self.edges.push(Vec::new());
42 idx
43 }
44
45 pub fn add_edge(&mut self, from: usize, to: usize) {
47 if from < self.edges.len() && to < self.nodes.len() && from != to {
48 let e = &mut self.edges[from];
49 if !e.contains(&to) {
50 e.push(to);
51 }
52 }
53 }
54
55 pub fn nodes(&self) -> &[DepNode] {
57 &self.nodes
58 }
59
60 pub fn nodes_mut(&mut self) -> &mut [DepNode] {
63 &mut self.nodes
64 }
65
66 pub fn len(&self) -> usize {
68 self.nodes.len()
69 }
70
71 pub fn is_empty(&self) -> bool {
73 self.nodes.is_empty()
74 }
75
76 pub fn direct_count(&self) -> usize {
78 self.nodes.iter().filter(|n| n.direct).count()
79 }
80
81 fn reverse(&self) -> Vec<Vec<usize>> {
83 let mut rev = vec![Vec::new(); self.nodes.len()];
84 for (from, tos) in self.edges.iter().enumerate() {
85 for &to in tos {
86 rev[to].push(from);
87 }
88 }
89 rev
90 }
91
92 pub fn blast_radii(&self) -> Vec<usize> {
95 let rev = self.reverse();
96 let n = self.nodes.len();
97 let mut out = vec![0usize; n];
98 for start in 0..n {
99 let mut seen = vec![false; n];
100 seen[start] = true;
101 let mut stack = vec![start];
102 let mut count = 0;
103 while let Some(u) = stack.pop() {
104 for &p in &rev[u] {
105 if !seen[p] {
106 seen[p] = true;
107 count += 1;
108 stack.push(p);
109 }
110 }
111 }
112 out[start] = count;
113 }
114 out
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 fn node(name: &str, direct: bool) -> DepNode {
123 DepNode {
124 name: name.into(),
125 version: "1.0.0".into(),
126 ecosystem: Ecosystem::Cargo,
127 direct,
128 }
129 }
130
131 #[test]
132 fn blast_radius_counts_transitive_dependents() {
133 let mut g = DepGraph::new();
135 let app = g.add_node(node("app", true));
136 let lib = g.add_node(node("lib", false));
137 let leaf = g.add_node(node("leaf", false));
138 g.add_edge(app, lib);
139 g.add_edge(lib, leaf);
140 g.add_edge(app, leaf);
141
142 let radii = g.blast_radii();
143 assert_eq!(radii[app], 0, "nothing depends on the app");
144 assert_eq!(radii[lib], 1, "only app depends on lib");
145 assert_eq!(radii[leaf], 2, "both app and lib depend on leaf");
146 }
147
148 #[test]
149 fn duplicate_and_self_edges_are_ignored() {
150 let mut g = DepGraph::new();
151 let a = g.add_node(node("a", true));
152 let b = g.add_node(node("b", false));
153 g.add_edge(a, b);
154 g.add_edge(a, b); g.add_edge(a, a); assert_eq!(g.blast_radii()[b], 1);
157 }
158}