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 = "SCHEMA_PATH")]
18    schema_path: Utf8PathBuf,
19
20    #[arg(long, env = "SCHEMA_RESULT_PATH")]
21    schema_result_path: Utf8PathBuf,
22
23    #[arg(long, env = "SCHEMA_PATCH_PATH")]
24    schema_patch_path: Utf8PathBuf,
25
26    #[arg(long, env = "SCHEMA_PATCH_RESULT_PATH")]
27    schema_patch_result_path: Utf8PathBuf,
28}
29
30struct Line(Option<usize>);
31
32impl fmt::Display for Line {
33    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34        match self.0 {
35            None => write!(f, "    "),
36            Some(idx) => write!(f, "{:<4}", idx + 1),
37        }
38    }
39}
40
41fn get_result(path: &std::path::Path) -> anyhow::Result<String> {
42    let result = std::fs::read_to_string(path).context("read results")?;
43    let result: BTreeMap<Utf8PathBuf, String> = serde_json::from_str(&result).context("parse results")?;
44    result.into_values().next().context("get result")
45}
46
47fn main() -> anyhow::Result<()> {
48    let args = Args::parse();
49
50    let runfiles = runfiles::Runfiles::create().expect("failed to create runfiles");
51    let schema_path = runfiles::rlocation!(&runfiles, &args.schema_path).expect("failed to get schema file");
52    let schema_result_path = runfiles::rlocation!(&runfiles, &args.schema_result_path).expect("failed to get result path");
53    let schema_patch_path =
54        runfiles::rlocation!(&runfiles, &args.schema_patch_path).expect("failed to get schema patch path");
55    let schema_patch_result_path =
56        runfiles::rlocation!(&runfiles, &args.schema_patch_result_path).expect("failed to get schema patch result path");
57
58    let schema = std::fs::read_to_string(&schema_path).context("read schema file")?;
59    let schema_patch = std::fs::read_to_string(&schema_patch_path).context("read schema patch file")?;
60    let schema_result = get_result(&schema_result_path)?;
61    let schema_patch_result = get_result(&schema_patch_result_path)?;
62
63    let mut diff_found = false;
64    if schema_result != schema {
65        println!("Difference found in {}", args.schema_path);
66        println!("{}", diff(&schema, &schema_result));
67        diff_found = true;
68    }
69    if schema_patch_result != schema_patch {
70        println!("Difference found in {}", args.schema_patch_path);
71        println!("{}", diff(&schema_patch, &schema_patch_result));
72        diff_found = true;
73    }
74
75    if diff_found {
76        std::process::exit(1)
77    }
78
79    Ok(())
80}
81
82pub(crate) fn diff(old: &str, new: &str) -> String {
83    use std::fmt::Write;
84
85    let diff = TextDiff::from_lines(old, new);
86    let mut output = String::new();
87
88    for (idx, group) in diff.grouped_ops(3).iter().enumerate() {
89        if idx > 0 {
90            writeln!(&mut output, "{0:─^1$}┼{0:─^2$}", "─", 9, 120).unwrap();
91        }
92        for op in group {
93            for change in diff.iter_inline_changes(op) {
94                let (sign, s) = match change.tag() {
95                    ChangeTag::Delete => ("-", Style::new().red()),
96                    ChangeTag::Insert => ("+", Style::new().green()),
97                    ChangeTag::Equal => (" ", Style::new().dim()),
98                };
99                write!(
100                    &mut output,
101                    "{}{} │{}",
102                    style(Line(change.old_index())).dim(),
103                    style(Line(change.new_index())).dim(),
104                    s.apply_to(sign).bold(),
105                )
106                .unwrap();
107                for (_, value) in change.iter_strings_lossy() {
108                    write!(&mut output, "{}", s.apply_to(value)).unwrap();
109                }
110                if change.missing_newline() {
111                    writeln!(&mut output).unwrap();
112                }
113            }
114        }
115    }
116
117    output
118}