1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct DiffEntry {
6 pub endpoint: String,
8 pub method: String,
10 pub change_type: String,
12 pub field: String,
14 pub baseline: Option<serde_json::Value>,
16 pub target: Option<serde_json::Value>,
18}
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct DiffReport {
23 pub plan_name: String,
25 pub baseline_url: String,
27 pub target_url: String,
29 pub total_endpoints: usize,
31 pub identical: usize,
33 pub different: usize,
35 pub fields_added: usize,
37 pub fields_removed: usize,
39 pub fields_modified: usize,
41 pub duration_secs: f64,
43 pub diffs: Vec<DiffEntry>,
45}
46
47impl std::fmt::Display for DiffReport {
48 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49 writeln!(f, "── Diff Report: {} ──", self.plan_name)?;
50 writeln!(f, " Baseline: {}", self.baseline_url)?;
51 writeln!(f, " Target: {}", self.target_url)?;
52 writeln!(
53 f,
54 " Endpoints: {} identical, {} different",
55 self.identical, self.different
56 )?;
57 writeln!(
58 f,
59 " Fields: {} added, {} removed, {} modified",
60 self.fields_added, self.fields_removed, self.fields_modified
61 )?;
62 writeln!(f, " Duration: {:.1}s", self.duration_secs)?;
63 for d in &self.diffs {
64 writeln!(
65 f,
66 " [{}] {} {} — {}: {:?} → {:?}",
67 d.change_type, d.method, d.endpoint, d.field, d.baseline, d.target
68 )?;
69 }
70 Ok(())
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use super::*;
77
78 #[test]
79 fn test_diff_report_display() {
80 let report = DiffReport {
81 plan_name: "migration".into(),
82 baseline_url: "https://api-v1.example.com".into(),
83 target_url: "https://api-v2.example.com".into(),
84 total_endpoints: 5,
85 identical: 3,
86 different: 2,
87 fields_added: 3,
88 fields_removed: 1,
89 fields_modified: 2,
90 duration_secs: 10.0,
91 diffs: vec![DiffEntry {
92 endpoint: "/users".into(),
93 method: "GET".into(),
94 change_type: "added".into(),
95 field: "$.data[0].email".into(),
96 baseline: None,
97 target: Some(serde_json::json!("test@example.com")),
98 }],
99 };
100 let output = report.to_string();
101 assert!(output.contains("3 added"));
102 assert!(output.contains("api-v1"));
103 }
104}