Skip to main content

cargo_ferris_wheel/reports/
github.rs

1//! GitHub Actions format report generation
2
3use std::fmt::Write;
4
5use super::ReportGenerator;
6use crate::detector::CycleDetector;
7use crate::error::FerrisWheelError;
8
9pub struct GitHubReportGenerator;
10
11impl Default for GitHubReportGenerator {
12    fn default() -> Self {
13        Self::new()
14    }
15}
16
17impl GitHubReportGenerator {
18    pub fn new() -> Self {
19        Self
20    }
21}
22
23impl ReportGenerator for GitHubReportGenerator {
24    fn generate_report(&self, detector: &CycleDetector) -> Result<String, FerrisWheelError> {
25        let mut output = String::new();
26
27        if !detector.has_cycles() {
28            writeln!(
29                output,
30                "::notice title=Dependency Check::No workspace dependency cycles detected! ✅"
31            )?;
32            return Ok(output);
33        }
34
35        writeln!(
36            output,
37            "::error title=Dependency Cycles::Found {} workspace dependency cycle{}",
38            detector.cycle_count(),
39            if detector.cycle_count() == 1 { "" } else { "s" }
40        )?;
41
42        let mut sorted_cycles: Vec<_> = detector.cycles().iter().collect();
43        sorted_cycles.sort_by(|a, b| {
44            let a_names = a.workspace_names();
45            let b_names = b.workspace_names();
46            let a_first = a_names.first().map(|s| s.as_str()).unwrap_or("");
47            let b_first = b_names.first().map(|s| s.as_str()).unwrap_or("");
48            a_first.cmp(b_first)
49        });
50
51        for (i, cycle) in sorted_cycles.iter().enumerate() {
52            let mut workspace_names = cycle.workspace_names().to_vec();
53            workspace_names.sort();
54            writeln!(
55                output,
56                "::warning title=Cycle {}::Workspaces: {}",
57                i + 1,
58                workspace_names.join(" → ")
59            )?;
60
61            let mut sorted_edges = cycle.edges().to_vec();
62            sorted_edges.sort_by(|a, b| match a.from_crate().cmp(b.from_crate()) {
63                std::cmp::Ordering::Equal => a.to_crate().cmp(b.to_crate()),
64                other => other,
65            });
66
67            for edge in sorted_edges {
68                writeln!(
69                    output,
70                    "::notice::  {} → {} ({})",
71                    edge.from_crate(),
72                    edge.to_crate(),
73                    edge.dependency_type()
74                )?;
75            }
76        }
77
78        writeln!(
79            output,
80            "::notice title=Recommendation::To break these cycles, consider extracting shared \
81             code into a separate workspace that both can depend on."
82        )?;
83
84        Ok(output)
85    }
86}