1use serde::{Deserialize, Serialize};
14use std::collections::{HashMap, HashSet, VecDeque};
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct DependencyNode {
19 pub name: String,
20 pub version: String,
21 pub depth: u32,
22 pub children: Vec<DependencyNode>,
23}
24
25impl DependencyNode {
26 pub fn new(name: &str, version: &str, depth: u32) -> Self {
27 Self {
28 name: name.to_string(),
29 version: version.to_string(),
30 depth,
31 children: Vec::new(),
32 }
33 }
34
35 pub fn add_child(&mut self, child: DependencyNode) {
36 self.children.push(child);
37 }
38
39 pub fn child_count(&self) -> usize {
40 self.children.len()
41 }
42
43 pub fn total_descendants(&self) -> usize {
44 self.children.len()
45 + self
46 .children
47 .iter()
48 .map(|c| c.total_descendants())
49 .sum::<usize>()
50 }
51
52 pub fn max_depth(&self) -> u32 {
53 if self.children.is_empty() {
54 self.depth
55 } else {
56 self.children
57 .iter()
58 .map(|c| c.max_depth())
59 .max()
60 .unwrap_or(self.depth)
61 }
62 }
63}
64
65#[derive(Debug, Clone, Serialize, Deserialize)]
67pub struct DependencyEdge {
68 pub from: String,
69 pub to: String,
70 pub version_spec: String,
71 pub optional: bool,
72}
73
74impl DependencyEdge {
75 pub fn new(from: &str, to: &str, version_spec: &str) -> Self {
76 Self {
77 from: from.to_string(),
78 to: to.to_string(),
79 version_spec: version_spec.to_string(),
80 optional: false,
81 }
82 }
83
84 pub fn optional(mut self) -> Self {
85 self.optional = true;
86 self
87 }
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize)]
92pub struct CircularDependency {
93 pub cycle: Vec<String>,
94 pub path_length: usize,
95}
96
97impl CircularDependency {
98 pub fn new(cycle: Vec<String>) -> Self {
99 let path_length = cycle.len();
100 Self { cycle, path_length }
101 }
102
103 pub fn as_string(&self) -> String {
104 self.cycle.join(" → ")
105 }
106}
107
108#[derive(Debug, Clone, Serialize, Deserialize)]
110pub struct DependencyMetrics {
111 pub total_nodes: usize,
112 pub total_edges: usize,
113 pub max_depth: u32,
114 pub avg_depth: f32,
115 pub circular_dependencies: usize,
116 pub optional_dependencies: usize,
117 pub branching_factor: f32,
118}
119
120impl DependencyMetrics {
121 pub fn new() -> Self {
122 Self {
123 total_nodes: 0,
124 total_edges: 0,
125 max_depth: 0,
126 avg_depth: 0.0,
127 circular_dependencies: 0,
128 optional_dependencies: 0,
129 branching_factor: 0.0,
130 }
131 }
132}
133
134impl Default for DependencyMetrics {
135 fn default() -> Self {
136 Self::new()
137 }
138}
139
140pub struct DependencyGraph {
142 pub root: String,
143 pub root_version: String,
144 pub edges: Vec<DependencyEdge>,
145 pub nodes: HashMap<String, String>, }
147
148impl DependencyGraph {
149 pub fn new(root_name: &str, root_version: &str) -> Self {
151 let mut nodes = HashMap::new();
152 nodes.insert(root_name.to_string(), root_version.to_string());
153
154 Self {
155 root: root_name.to_string(),
156 root_version: root_version.to_string(),
157 edges: Vec::new(),
158 nodes,
159 }
160 }
161
162 pub fn add_dependency(&mut self, edge: DependencyEdge) {
164 if !self.nodes.contains_key(&edge.to) {
166 self.nodes
168 .insert(edge.to.clone(), edge.version_spec.clone());
169 }
170 self.edges.push(edge);
171 }
172
173 pub fn detect_circular_dependencies(&self) -> Vec<CircularDependency> {
175 let mut cycles = Vec::new();
176 let mut visited = HashSet::new();
177 let mut rec_stack = HashSet::new();
178 let mut path = Vec::new();
179
180 for node in self.nodes.keys() {
181 if !visited.contains(node) {
182 self.dfs_cycle_detection(
183 node,
184 &mut visited,
185 &mut rec_stack,
186 &mut path,
187 &mut cycles,
188 );
189 }
190 }
191
192 cycles
193 }
194
195 fn dfs_cycle_detection(
196 &self,
197 node: &str,
198 visited: &mut HashSet<String>,
199 rec_stack: &mut HashSet<String>,
200 path: &mut Vec<String>,
201 cycles: &mut Vec<CircularDependency>,
202 ) {
203 visited.insert(node.to_string());
204 rec_stack.insert(node.to_string());
205 path.push(node.to_string());
206
207 for edge in &self.edges {
209 if edge.from == node {
210 let neighbor = &edge.to;
211
212 if !visited.contains(neighbor) {
213 self.dfs_cycle_detection(neighbor, visited, rec_stack, path, cycles);
214 } else if rec_stack.contains(neighbor) {
215 if let Some(start_idx) = path.iter().position(|n| n == neighbor) {
217 let cycle = path[start_idx..].to_vec();
218 if !cycle.is_empty() && cycle[0] != cycle[cycle.len() - 1] {
219 let mut full_cycle = cycle.clone();
220 full_cycle.push(neighbor.to_string());
221 cycles.push(CircularDependency::new(full_cycle));
222 }
223 }
224 }
225 }
226 }
227
228 path.pop();
229 rec_stack.remove(node);
230 }
231
232 pub fn build_tree(&self) -> DependencyNode {
234 let mut visited = HashSet::new();
235 self.build_tree_recursive(&self.root, 0, &mut visited)
236 }
237
238 fn build_tree_recursive(
239 &self,
240 node_name: &str,
241 depth: u32,
242 visited: &mut HashSet<String>,
243 ) -> DependencyNode {
244 let version = self
245 .nodes
246 .get(node_name)
247 .cloned()
248 .unwrap_or_else(|| "unknown".to_string());
249
250 let mut node = DependencyNode::new(node_name, &version, depth);
251
252 if visited.contains(node_name) {
254 return node;
255 }
256
257 visited.insert(node_name.to_string());
258
259 for edge in &self.edges {
261 if edge.from == node_name {
262 let child = self.build_tree_recursive(&edge.to, depth + 1, visited);
263 node.add_child(child);
264 }
265 }
266
267 node
268 }
269
270 pub fn calculate_metrics(&self) -> DependencyMetrics {
272 let tree = self.build_tree();
273 let total_nodes = self.nodes.len();
274 let total_edges = self.edges.len();
275 let max_depth = tree.max_depth();
276
277 let optional_dependencies = self.edges.iter().filter(|e| e.optional).count();
278
279 let mut avg_depth = 0.0;
280 if total_nodes > 0 {
281 avg_depth = total_nodes as f32 / max_depth.max(1) as f32;
282 }
283
284 let mut branching_factor = 0.0;
285 if total_nodes > 1 {
286 branching_factor = total_edges as f32 / (total_nodes - 1) as f32;
287 }
288
289 let circular_dependencies = self.detect_circular_dependencies().len();
290
291 DependencyMetrics {
292 total_nodes,
293 total_edges,
294 max_depth,
295 avg_depth,
296 circular_dependencies,
297 optional_dependencies,
298 branching_factor,
299 }
300 }
301
302 pub fn to_dot(&self) -> String {
304 let mut dot = String::from("digraph dependencies {\n");
305 dot.push_str(" rankdir=LR;\n");
306 dot.push_str(" node [shape=box];\n\n");
307
308 for (name, version) in &self.nodes {
310 dot.push_str(&format!(
311 " \"{}\" [label=\"{}\\n({})\"];\n",
312 name, name, version
313 ));
314 }
315
316 dot.push('\n');
317
318 for edge in &self.edges {
320 let style = if edge.optional { "dashed" } else { "solid" };
321 dot.push_str(&format!(
322 " \"{}\" -> \"{}\" [style=\"{}\", label=\"{}\"];\n",
323 edge.from, edge.to, style, edge.version_spec
324 ));
325 }
326
327 dot.push_str("}\n");
328 dot
329 }
330
331 pub fn to_json(&self) -> serde_json::Value {
333 let tree = self.build_tree();
334 let metrics = self.calculate_metrics();
335 let circular = self.detect_circular_dependencies();
336
337 serde_json::json!({
338 "root": self.root,
339 "root_version": self.root_version,
340 "tree": tree,
341 "metrics": metrics,
342 "circular_dependencies": circular,
343 "edges_count": self.edges.len(),
344 "nodes_count": self.nodes.len(),
345 })
346 }
347
348 pub fn direct_dependencies(&self, node_name: &str) -> Vec<String> {
350 self.edges
351 .iter()
352 .filter(|e| e.from == node_name)
353 .map(|e| e.to.clone())
354 .collect()
355 }
356
357 pub fn transitive_dependencies(&self, node_name: &str) -> Vec<String> {
359 let mut result = HashSet::new();
360 let mut queue = VecDeque::new();
361 let mut visited = HashSet::new();
362
363 queue.push_back(node_name.to_string());
364
365 while let Some(current) = queue.pop_front() {
366 if visited.contains(¤t) {
367 continue;
368 }
369
370 visited.insert(current.clone());
371
372 for edge in &self.edges {
373 if edge.from == current && !edge.to.is_empty() {
374 result.insert(edge.to.clone());
375 queue.push_back(edge.to.clone());
376 }
377 }
378 }
379
380 result.into_iter().collect()
381 }
382
383 pub fn shortest_path(&self, from: &str, to: &str) -> Option<Vec<String>> {
385 if from == to {
386 return Some(vec![from.to_string()]);
387 }
388
389 let mut queue = VecDeque::new();
390 let mut visited = HashSet::new();
391 let mut parent: HashMap<String, String> = HashMap::new();
392
393 queue.push_back(from.to_string());
394 visited.insert(from.to_string());
395
396 while let Some(current) = queue.pop_front() {
397 if current == to {
398 let mut path = vec![to.to_string()];
399 let mut current_node = to.to_string();
400
401 while let Some(p) = parent.get(¤t_node) {
402 path.push(p.clone());
403 current_node = p.clone();
404 }
405
406 path.reverse();
407 return Some(path);
408 }
409
410 for edge in &self.edges {
411 if edge.from == current && !visited.contains(&edge.to) {
412 visited.insert(edge.to.clone());
413 parent.insert(edge.to.clone(), current.clone());
414 queue.push_back(edge.to.clone());
415 }
416 }
417 }
418
419 None
420 }
421
422 pub fn node_depth(&self, node_name: &str) -> Option<u32> {
424 let tree = self.build_tree();
425 self.find_node_depth(&tree, node_name)
426 }
427
428 fn find_node_depth(&self, node: &DependencyNode, target: &str) -> Option<u32> {
429 if node.name == target {
430 return Some(node.depth);
431 }
432
433 for child in &node.children {
434 if let Some(depth) = self.find_node_depth(child, target) {
435 return Some(depth);
436 }
437 }
438
439 None
440 }
441}
442
443#[cfg(test)]
444mod tests {
445 use super::*;
446
447 #[test]
448 fn test_dependency_node_creation() {
449 let node = DependencyNode::new("plugin-a", "1.0.0", 0);
450 assert_eq!(node.name, "plugin-a");
451 assert_eq!(node.version, "1.0.0");
452 assert_eq!(node.depth, 0);
453 assert_eq!(node.child_count(), 0);
454 }
455
456 #[test]
457 fn test_dependency_node_add_child() {
458 let mut parent = DependencyNode::new("plugin-a", "1.0.0", 0);
459 let child = DependencyNode::new("plugin-b", "2.0.0", 1);
460 parent.add_child(child);
461 assert_eq!(parent.child_count(), 1);
462 }
463
464 #[test]
465 fn test_dependency_node_total_descendants() {
466 let mut parent = DependencyNode::new("plugin-a", "1.0.0", 0);
467 let mut child1 = DependencyNode::new("plugin-b", "2.0.0", 1);
468 let grandchild = DependencyNode::new("plugin-c", "3.0.0", 2);
469 child1.add_child(grandchild);
470 parent.add_child(child1);
471 assert_eq!(parent.total_descendants(), 2);
472 }
473
474 #[test]
475 fn test_dependency_node_max_depth() {
476 let mut parent = DependencyNode::new("plugin-a", "1.0.0", 0);
477 let mut child = DependencyNode::new("plugin-b", "2.0.0", 1);
478 let grandchild = DependencyNode::new("plugin-c", "3.0.0", 2);
479 child.add_child(grandchild);
480 parent.add_child(child);
481 assert_eq!(parent.max_depth(), 2);
482 }
483
484 #[test]
485 fn test_dependency_edge_creation() {
486 let edge = DependencyEdge::new("plugin-a", "plugin-b", "^1.0.0");
487 assert_eq!(edge.from, "plugin-a");
488 assert_eq!(edge.to, "plugin-b");
489 assert!(!edge.optional);
490 }
491
492 #[test]
493 fn test_dependency_edge_optional() {
494 let edge = DependencyEdge::new("plugin-a", "plugin-b", "^1.0.0").optional();
495 assert!(edge.optional);
496 }
497
498 #[test]
499 fn test_circular_dependency_creation() {
500 let cycle =
501 CircularDependency::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
502 assert_eq!(cycle.path_length, 3);
503 }
504
505 #[test]
506 fn test_circular_dependency_as_string() {
507 let cycle =
508 CircularDependency::new(vec!["a".to_string(), "b".to_string(), "c".to_string()]);
509 assert_eq!(cycle.as_string(), "a → b → c");
510 }
511
512 #[test]
513 fn test_dependency_graph_creation() {
514 let graph = DependencyGraph::new("root", "1.0.0");
515 assert_eq!(graph.root, "root");
516 assert_eq!(graph.nodes.len(), 1);
517 }
518
519 #[test]
520 fn test_dependency_graph_add_dependency() {
521 let mut graph = DependencyGraph::new("root", "1.0.0");
522 let edge = DependencyEdge::new("root", "dep-a", "^1.0.0");
523 graph.add_dependency(edge);
524 assert_eq!(graph.edges.len(), 1);
525 assert_eq!(graph.nodes.len(), 2);
526 }
527
528 #[test]
529 fn test_dependency_graph_direct_dependencies() {
530 let mut graph = DependencyGraph::new("root", "1.0.0");
531 graph.add_dependency(DependencyEdge::new("root", "dep-a", "^1.0.0"));
532 graph.add_dependency(DependencyEdge::new("root", "dep-b", "^2.0.0"));
533 let deps = graph.direct_dependencies("root");
534 assert_eq!(deps.len(), 2);
535 }
536
537 #[test]
538 fn test_dependency_graph_transitive_dependencies() {
539 let mut graph = DependencyGraph::new("root", "1.0.0");
540 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
541 graph.add_dependency(DependencyEdge::new("a", "b", "^1.0.0"));
542 graph.add_dependency(DependencyEdge::new("b", "c", "^1.0.0"));
543 let deps = graph.transitive_dependencies("root");
544 assert_eq!(deps.len(), 3);
545 }
546
547 #[test]
548 fn test_dependency_graph_shortest_path() {
549 let mut graph = DependencyGraph::new("root", "1.0.0");
550 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
551 graph.add_dependency(DependencyEdge::new("a", "b", "^1.0.0"));
552 graph.add_dependency(DependencyEdge::new("b", "c", "^1.0.0"));
553 let path = graph.shortest_path("root", "c");
554 assert!(path.is_some());
555 assert_eq!(path.unwrap().len(), 4);
556 }
557
558 #[test]
559 fn test_dependency_graph_shortest_path_self() {
560 let graph = DependencyGraph::new("root", "1.0.0");
561 let path = graph.shortest_path("root", "root");
562 assert!(path.is_some());
563 assert_eq!(path.unwrap(), vec!["root"]);
564 }
565
566 #[test]
567 fn test_dependency_graph_build_tree() {
568 let mut graph = DependencyGraph::new("root", "1.0.0");
569 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
570 graph.add_dependency(DependencyEdge::new("a", "b", "^1.0.0"));
571 let tree = graph.build_tree();
572 assert_eq!(tree.name, "root");
573 assert_eq!(tree.depth, 0);
574 assert_eq!(tree.child_count(), 1);
575 }
576
577 #[test]
578 fn test_dependency_graph_calculate_metrics() {
579 let mut graph = DependencyGraph::new("root", "1.0.0");
580 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
581 graph.add_dependency(DependencyEdge::new("root", "b", "^2.0.0"));
582 graph.add_dependency(DependencyEdge::new("a", "c", "^1.0.0"));
583 let metrics = graph.calculate_metrics();
584 assert!(metrics.total_nodes > 0);
585 assert!(metrics.total_edges > 0);
586 }
587
588 #[test]
589 fn test_dependency_graph_to_dot() {
590 let mut graph = DependencyGraph::new("root", "1.0.0");
591 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
592 let dot = graph.to_dot();
593 assert!(dot.contains("digraph dependencies"));
594 assert!(dot.contains("root"));
595 }
596
597 #[test]
598 fn test_dependency_graph_to_json() {
599 let mut graph = DependencyGraph::new("root", "1.0.0");
600 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
601 let json = graph.to_json();
602 assert!(json.get("root").is_some());
603 assert!(json.get("metrics").is_some());
604 }
605
606 #[test]
607 fn test_dependency_graph_detect_no_cycles() {
608 let mut graph = DependencyGraph::new("root", "1.0.0");
609 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
610 graph.add_dependency(DependencyEdge::new("a", "b", "^1.0.0"));
611 let cycles = graph.detect_circular_dependencies();
612 assert!(cycles.len() <= 1);
614 }
615
616 #[test]
617 fn test_dependency_graph_node_depth() {
618 let mut graph = DependencyGraph::new("root", "1.0.0");
619 graph.add_dependency(DependencyEdge::new("root", "a", "^1.0.0"));
620 graph.add_dependency(DependencyEdge::new("a", "b", "^1.0.0"));
621 let depth = graph.node_depth("b");
622 assert!(depth.is_some());
623 }
624
625 #[test]
626 fn test_dependency_metrics_creation() {
627 let metrics = DependencyMetrics::new();
628 assert_eq!(metrics.total_nodes, 0);
629 assert_eq!(metrics.total_edges, 0);
630 }
631
632 #[test]
633 fn test_dependency_node_serialization() {
634 let node = DependencyNode::new("plugin-a", "1.0.0", 0);
635 let json = serde_json::to_string(&node).unwrap();
636 let deserialized: DependencyNode = serde_json::from_str(&json).unwrap();
637 assert_eq!(deserialized.name, node.name);
638 }
639
640 #[test]
641 fn test_dependency_edge_serialization() {
642 let edge = DependencyEdge::new("plugin-a", "plugin-b", "^1.0.0");
643 let json = serde_json::to_string(&edge).unwrap();
644 let deserialized: DependencyEdge = serde_json::from_str(&json).unwrap();
645 assert_eq!(deserialized.from, edge.from);
646 }
647
648 #[test]
649 fn test_circular_dependency_serialization() {
650 let cycle = CircularDependency::new(vec!["a".to_string(), "b".to_string()]);
651 let json = serde_json::to_string(&cycle).unwrap();
652 let deserialized: CircularDependency = serde_json::from_str(&json).unwrap();
653 assert_eq!(deserialized.path_length, cycle.path_length);
654 }
655
656 #[test]
657 fn test_dependency_metrics_serialization() {
658 let metrics = DependencyMetrics::new();
659 let json = serde_json::to_string(&metrics).unwrap();
660 let deserialized: DependencyMetrics = serde_json::from_str(&json).unwrap();
661 assert_eq!(deserialized.total_nodes, 0);
662 }
663}