Skip to main content

momus_diff/
report.rs

1use serde::{Deserialize, Serialize};
2
3/// A single diff between baseline and target responses.
4#[derive(Debug, Clone, Serialize, Deserialize)]
5pub struct DiffEntry {
6    /// Endpoint path.
7    pub endpoint: String,
8    /// HTTP method.
9    pub method: String,
10    /// Type of change: added, removed, modified
11    pub change_type: String,
12    /// The field path that changed (e.g. "$.data.user.name").
13    pub field: String,
14    /// Value in the baseline.
15    pub baseline: Option<serde_json::Value>,
16    /// Value in the target.
17    pub target: Option<serde_json::Value>,
18}
19
20/// Results of a diff run.
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct DiffReport {
23    /// Plan name.
24    pub plan_name: String,
25    /// Baseline URL.
26    pub baseline_url: String,
27    /// Target URL.
28    pub target_url: String,
29    /// Total endpoints compared.
30    pub total_endpoints: usize,
31    /// Endpoints with identical responses.
32    pub identical: usize,
33    /// Endpoints with differences.
34    pub different: usize,
35    /// Fields present in target but not baseline.
36    pub fields_added: usize,
37    /// Fields present in baseline but not target.
38    pub fields_removed: usize,
39    /// Fields with different values.
40    pub fields_modified: usize,
41    /// Wall-clock duration in seconds.
42    pub duration_secs: f64,
43    /// List of diffs found.
44    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}