Skip to main content

padlock_output/
diff.rs

1// padlock-output/src/diff.rs
2
3use padlock_core::ir::{StructLayout, optimal_order};
4use similar::{ChangeTag, TextDiff};
5
6/// Render a unified diff of a struct's current field order vs the optimal order.
7pub fn render_diff(layout: &StructLayout) -> String {
8    let original = fields_to_text(layout.fields.iter().map(|f| f.name.as_str()));
9    let optimal = optimal_order(layout);
10    let optimized = fields_to_text(optimal.iter().map(|f| f.name.as_str()));
11    text_diff(&original, &optimized)
12}
13
14/// Render a unified diff between two arbitrary text blocks.
15pub fn text_diff(original: &str, updated: &str) -> String {
16    if original == updated {
17        return String::from("(no changes)\n");
18    }
19
20    let diff = TextDiff::from_lines(original, updated);
21    let mut out = String::new();
22
23    for change in diff.iter_all_changes() {
24        let prefix = match change.tag() {
25            ChangeTag::Delete => "-",
26            ChangeTag::Insert => "+",
27            ChangeTag::Equal => " ",
28        };
29        out.push_str(&format!("{prefix} {}", change.value()));
30        if !change.value().ends_with('\n') {
31            out.push('\n');
32        }
33    }
34    out
35}
36
37fn fields_to_text<'a>(names: impl Iterator<Item = &'a str>) -> String {
38    names.map(|n| format!("{n}\n")).collect()
39}
40
41// ── tests ─────────────────────────────────────────────────────────────────────
42
43#[cfg(test)]
44mod tests {
45    use super::*;
46    use padlock_core::ir::test_fixtures::{connection_layout, packed_layout};
47
48    #[test]
49    fn diff_misaligned_is_nonempty() {
50        let out = render_diff(&connection_layout());
51        // Connection is not in optimal order so a diff should exist
52        assert_ne!(out, "(no changes)\n");
53        assert!(out.contains("timeout") || out.contains("port"));
54    }
55
56    #[test]
57    fn diff_already_optimal_is_no_changes() {
58        // packed_layout has fields in optimal order (i32 > i16 > i16)
59        let out = render_diff(&packed_layout());
60        assert_eq!(out, "(no changes)\n");
61    }
62
63    #[test]
64    fn text_diff_shows_plus_minus() {
65        let out = text_diff("a\nb\n", "a\nc\n");
66        assert!(out.contains("- b") || out.contains("-b"));
67        assert!(out.contains("+ c") || out.contains("+c"));
68    }
69
70    #[test]
71    fn text_diff_identical_is_no_changes() {
72        assert_eq!(text_diff("x\n", "x\n"), "(no changes)\n");
73    }
74}