1use crate::diff::{ApiChange, ApiDiff, BreakingLevel, ChangeDetail, classify_change};
2use crate::error::ApiError;
3
4pub fn format_diff_report(diff: &ApiDiff) -> String {
6 if diff.is_empty() {
7 return "No API changes detected.".into();
8 }
9
10 let mut report = String::new();
11 report.push_str(&format!("API Diff Summary: {}\n", diff.summary()));
12 report.push_str(&"=".repeat(60));
13 report.push('\n');
14
15 if !diff.removed.is_empty() {
17 report.push_str("\n[BREAKING] Removed items:\n");
18 for item in &diff.removed {
19 report.push_str(&format!(
20 " - {} {} ({})\n",
21 item.kind, item.name, item.visibility
22 ));
23 if !item.module_path.is_empty() {
24 report.push_str(&format!(" path: {}\n", item.module_path.join("::")));
25 }
26 }
27 }
28
29 let breaking_changes: Vec<&ApiChange> = diff.changed.iter()
31 .filter(|c| classify_change(c) == BreakingLevel::Breaking)
32 .collect();
33 let dangerous_changes: Vec<&ApiChange> = diff.changed.iter()
34 .filter(|c| classify_change(c) == BreakingLevel::Dangerous)
35 .collect();
36 let additive_changes: Vec<&ApiChange> = diff.changed.iter()
37 .filter(|c| classify_change(c) == BreakingLevel::Additive)
38 .collect();
39
40 if !breaking_changes.is_empty() {
41 report.push_str("\n[BREAKING] Changed items:\n");
42 for change in &breaking_changes {
43 format_change(&mut report, change);
44 }
45 }
46
47 if !dangerous_changes.is_empty() {
48 report.push_str("\n[DANGEROUS] Changed items:\n");
49 for change in &dangerous_changes {
50 format_change(&mut report, change);
51 }
52 }
53
54 if !additive_changes.is_empty() {
55 report.push_str("\n[ADDITIVE] Changed items:\n");
56 for change in &additive_changes {
57 format_change(&mut report, change);
58 }
59 }
60
61 if !diff.added.is_empty() {
62 report.push_str("\n[ADDITIVE] Added items:\n");
63 for item in &diff.added {
64 report.push_str(&format!(
65 " + {} {} ({})\n",
66 item.kind, item.name, item.visibility
67 ));
68 if !item.module_path.is_empty() {
69 report.push_str(&format!(" path: {}\n", item.module_path.join("::")));
70 }
71 }
72 }
73
74 report
75}
76
77fn format_change(report: &mut String, change: &ApiChange) {
78 report.push_str(&format!(" ~ {} {}\n", change.kind, change.name));
79 if !change.module_path.is_empty() {
80 report.push_str(&format!(" path: {}\n", change.module_path.join("::")));
81 }
82 for detail in &change.changes {
83 match detail {
84 ChangeDetail::SignatureChanged { old, new } => {
85 report.push_str(&format!(" signature: {old} -> {new}\n"));
86 }
87 ChangeDetail::VisibilityChanged { old, new } => {
88 report.push_str(&format!(" visibility: {old} -> {new}\n"));
89 }
90 }
91 }
92}
93
94pub fn format_json_report(diff: &ApiDiff) -> Result<String, ApiError> {
96 let json = serde_json::to_string_pretty(diff)?;
97 Ok(json)
98}