diesel_migration_test/
main.rs

1//! A very small binary which diffs the input with the rendered output and fails if they are different.
2//! Which allows this to be checked via a bazel test.
3
4use std::collections::BTreeMap;
5use std::fmt;
6
7use anyhow::Context;
8use camino::Utf8PathBuf;
9use clap::Parser;
10use console::{Style, style};
11use similar::{ChangeTag, TextDiff};
12
13/// Simple program to greet a person
14#[derive(Parser, Debug)]
15#[command(version, about, long_about = None)]
16struct Args {
17    #[arg(long, env = "RESULT_PATH")]
18    result_path: Utf8PathBuf,
19
20    #[arg(long, env = "SCHEMAS_PATHS", value_delimiter = ';')]
21    schemas_paths: Vec<Utf8PathBuf>,
22
23    #[arg(long, env = "SCHEMAS_SHORT_PATHS", value_delimiter = ';')]
24    schemas_short_paths: Vec<Utf8PathBuf>,
25}
26
27struct Line(Option<usize>);
28
29impl fmt::Display for Line {
30    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
31        match self.0 {
32            None => write!(f, "    "),
33            Some(idx) => write!(f, "{:<4}", idx + 1),
34        }
35    }
36}
37
38fn main() -> anyhow::Result<()> {
39    let args = Args::parse();
40
41    let runfiles = runfiles::Runfiles::create().expect("failed to create runfiles");
42    let path_map = args
43        .schemas_paths
44        .iter()
45        .zip(args.schemas_short_paths.iter())
46        .collect::<BTreeMap<_, _>>();
47    let result_path = runfiles::rlocation!(&runfiles, args.result_path).expect("failed to get result path");
48
49    let results = std::fs::read_to_string(&result_path).context("read results")?;
50    let results: BTreeMap<Utf8PathBuf, String> = serde_json::from_str(&results).context("parse results")?;
51
52    let mut diff_found = false;
53    for (path, content) in results {
54        let actual_path = runfiles::rlocation!(&runfiles, path_map[&path]).expect("failed to get actual path");
55        let expected = std::fs::read_to_string(&actual_path).context("read expected")?;
56
57        if content != expected {
58            diff_found = true;
59            println!("Difference found in {}", path);
60            println!("{}", diff(&expected, &content));
61        }
62    }
63
64    if diff_found {
65        std::process::exit(1)
66    }
67
68    Ok(())
69}
70
71pub(crate) fn diff(old: &str, new: &str) -> String {
72    use std::fmt::Write;
73
74    let diff = TextDiff::from_lines(old, new);
75    let mut output = String::new();
76
77    for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
78        if idx > 0 {
79            writeln!(&mut output, "{0:─^1$}┼{0:─^2$}", "─", 9, 120).unwrap();
80        }
81        for op in group {
82            for change in diff.iter_inline_changes(op) {
83                let (sign, s) = match change.tag() {
84                    ChangeTag::Delete => ("-", Style::new().red()),
85                    ChangeTag::Insert => ("+", Style::new().green()),
86                    ChangeTag::Equal => (" ", Style::new().dim()),
87                };
88                write!(
89                    &mut output,
90                    "{}{} │{}",
91                    style(Line(change.old_index())).dim(),
92                    style(Line(change.new_index())).dim(),
93                    s.apply_to(sign).bold(),
94                )
95                .unwrap();
96                for (_, value) in change.iter_strings_lossy() {
97                    write!(&mut output, "{}", s.apply_to(value)).unwrap();
98                }
99                if change.missing_newline() {
100                    writeln!(&mut output).unwrap();
101                }
102            }
103        }
104    }
105
106    output
107}