use std::path::Path;
pub const BLESS_ENV: &str = "AUTUMN_GRAPHQL_BLESS";
#[derive(Debug, thiserror::Error)]
pub enum SdlDrift {
#[error("{path}: {source}")]
Io {
path: String,
source: std::io::Error,
},
#[error(
"{path} is stale — rerun with {BLESS_ENV}=1 to regenerate it\n--- first difference at line {line} ---\ncommitted: {committed}\nlive: {live}"
)]
Stale {
path: String,
line: usize,
committed: String,
live: String,
},
}
pub fn check_committed_sdl(live: &str, path: impl AsRef<Path>) -> Result<(), SdlDrift> {
let path = path.as_ref();
let io = |source| SdlDrift::Io {
path: path.display().to_string(),
source,
};
if std::env::var(BLESS_ENV).is_ok_and(|v| v == "1" || v.eq_ignore_ascii_case("true")) {
return std::fs::write(path, format!("{}\n", live.trim())).map_err(io);
}
let committed = std::fs::read_to_string(path).map_err(io)?;
let committed = committed.replace("\r\n", "\n");
let (committed, live) = (committed.trim(), live.trim());
if committed == live {
return Ok(());
}
let mut left = committed.lines();
let mut right = live.lines();
let mut line_number = 1;
loop {
match (left.next(), right.next()) {
(Some(a), Some(b)) if a == b => line_number += 1,
(a, b) => {
return Err(SdlDrift::Stale {
path: path.display().to_string(),
line: line_number,
committed: a.unwrap_or("<end of file>").to_owned(),
live: b.unwrap_or("<end of schema>").to_owned(),
});
}
}
}
}
#[allow(clippy::panic)]
pub fn assert_committed_sdl(live: &str, path: impl AsRef<Path>) {
if let Err(drift) = check_committed_sdl(live, path) {
panic!("{drift}");
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
#[test]
fn detects_drift_and_reports_the_first_differing_line() {
let dir = std::env::temp_dir().join(format!("autumn-graphql-sdl-{}", std::process::id()));
std::fs::create_dir_all(&dir).unwrap();
let file = dir.join("schema.graphql");
std::fs::write(&file, "type Query {\r\n a: Int\r\n}\r\n").unwrap();
check_committed_sdl("type Query {\n a: Int\n}", &file).unwrap();
let drift = check_committed_sdl("type Query {\n b: Int\n}", &file).unwrap_err();
let SdlDrift::Stale {
line,
committed,
live,
..
} = &drift
else {
panic!("expected stale, got {drift}");
};
assert_eq!(
(*line, committed.as_str(), live.as_str()),
(2, " a: Int", " b: Int")
);
assert!(drift.to_string().contains(BLESS_ENV));
let drift = check_committed_sdl("type Query {\n a: Int\n}\ntype X", &file).unwrap_err();
assert!(matches!(drift, SdlDrift::Stale { line: 4, .. }));
assert!(check_committed_sdl("x", dir.join("missing.graphql")).is_err());
let _ = std::fs::remove_dir_all(dir);
}
}