claw_branch/diff/
formatter.rs1use std::collections::HashMap;
4
5use crate::{
6 error::BranchResult,
7 types::{DiffKind, DiffResult, DiffStats, FieldDiff},
8};
9
10#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
12pub struct DiffSummary {
13 pub added: u32,
15 pub removed: u32,
17 pub modified: u32,
19 pub unchanged: u32,
21 pub divergence_score: f64,
23 pub is_identical: bool,
25 pub entity_type_breakdown: HashMap<String, DiffStats>,
27}
28
29pub fn format_diff_human(diff: &DiffResult) -> String {
31 let mut out = String::new();
32 out.push_str(&format!(
33 "diff {} → {}\n",
34 diff.branch_a_id, diff.branch_b_id
35 ));
36 out.push_str(&format!(
37 " compared_at : {}\n",
38 diff.compared_at.format("%Y-%m-%d %H:%M:%S UTC")
39 ));
40 out.push_str(&format!(" divergence : {:.4}\n", diff.divergence_score));
41 out.push_str(&format!(
42 " stats : +{} -{} ~{} ={}\n\n",
43 diff.stats.added, diff.stats.removed, diff.stats.modified, diff.stats.unchanged
44 ));
45
46 for ed in &diff.entity_diffs {
47 if matches!(ed.diff_kind, DiffKind::Unchanged) {
48 continue;
49 }
50 let tag = match ed.diff_kind {
51 DiffKind::Added => "[+]",
52 DiffKind::Removed => "[-]",
53 DiffKind::Modified => "[~]",
54 DiffKind::Unchanged => "[=]",
55 };
56 out.push_str(&format!("{tag} {} ({:?})\n", ed.entity_id, ed.entity_type));
57 for fd in &ed.field_diffs {
58 out.push_str(&format!(" {}\n", format_field_diff(fd)));
59 }
60 }
61 out
62}
63
64pub fn format_diff_json(diff: &DiffResult) -> BranchResult<serde_json::Value> {
66 Ok(serde_json::to_value(diff)?)
67}
68
69pub fn format_field_diff(fd: &FieldDiff) -> String {
71 format!("{}: {} → {}", fd.field, fd.before, fd.after)
72}
73
74pub fn summarise_diff(diff: &DiffResult) -> DiffSummary {
76 let mut breakdown: HashMap<String, DiffStats> = HashMap::new();
77
78 for ed in &diff.entity_diffs {
79 let key = format!("{:?}", ed.entity_type);
80 let entry = breakdown.entry(key).or_default();
81 entry.total_entities += 1;
82 match ed.diff_kind {
83 DiffKind::Added => entry.added += 1,
84 DiffKind::Removed => entry.removed += 1,
85 DiffKind::Modified => entry.modified += 1,
86 DiffKind::Unchanged => entry.unchanged += 1,
87 }
88 }
89
90 let is_identical = diff.stats.added == 0 && diff.stats.removed == 0 && diff.stats.modified == 0;
91
92 DiffSummary {
93 added: diff.stats.added,
94 removed: diff.stats.removed,
95 modified: diff.stats.modified,
96 unchanged: diff.stats.unchanged,
97 divergence_score: diff.divergence_score,
98 is_identical,
99 entity_type_breakdown: breakdown,
100 }
101}
102
103pub fn format_text(diff: &DiffResult) -> String {
105 format!(
106 "diff {} → {}: {} entities, divergence {:.3}",
107 diff.branch_a_id, diff.branch_b_id, diff.stats.total_entities, diff.divergence_score
108 )
109}