1use crate::surface::Surface;
6
7#[derive(Debug, Default)]
9pub struct SurfaceDiff {
10 pub added: Vec<String>,
12 pub removed: Vec<String>,
14 pub changed: Vec<(String, bool)>,
16}
17
18impl SurfaceDiff {
19 pub fn is_empty(&self) -> bool {
22 self.added.is_empty() && self.removed.is_empty() && self.changed.is_empty()
23 }
24}
25
26pub fn compare(old: &Surface, new: &Surface) -> SurfaceDiff {
29 let mut diff = SurfaceDiff::default();
30 for (path, info) in &new.items {
31 match old.items.get(path) {
32 None => diff.added.push(path.clone()),
33 Some(prev) => {
34 if info.kind != "module" && prev.fingerprint != info.fingerprint {
37 diff.changed
38 .push((path.clone(), prev.signature != info.signature));
39 }
40 }
41 }
42 }
43 for path in old.items.keys() {
44 if !new.items.contains_key(path) {
45 diff.removed.push(path.clone());
46 }
47 }
48 diff
49}
50
51pub fn render(diff: &SurfaceDiff) -> String {
54 if diff.is_empty() {
55 return "surface: no public API changes\n".into();
56 }
57 let mut out = String::new();
58 for p in &diff.added {
59 out.push_str(&format!("ADDED {p}\n"));
60 }
61 for p in &diff.removed {
62 out.push_str(&format!("REMOVED {p}\n"));
63 }
64 for (p, sig) in &diff.changed {
65 let what = if *sig { "signature" } else { "body" };
66 out.push_str(&format!("CHANGED {p} ({what})\n"));
67 }
68 out
69}