use std::io::Read;
use arcature_data::LintReport;
use crate::cli::OutputFormat;
use crate::error::CommandError;
pub(crate) fn run_lint(format: OutputFormat) -> Result<(), CommandError> {
let mut sql = String::new();
std::io::stdin()
.read_to_string(&mut sql)
.map_err(|source| CommandError::Db(crate::error::DbCommandError::ReadStdin(source)))?;
let report: LintReport = arcature_data::classify(&sql);
print_report(&report, format)?;
if report.has_critical() {
return Err(CommandError::Db(crate::error::DbCommandError::LintCritical));
}
Ok(())
}
fn print_report(report: &LintReport, format: OutputFormat) -> Result<(), CommandError> {
match format {
OutputFormat::Human => println!("{report}"),
OutputFormat::Json => {
let json = serde_json::to_string_pretty(report)?;
println!("{json}");
}
}
Ok(())
}
#[cfg(test)]
mod tests {
use arcature_data::{LintCategory, LintSeverity, classify};
#[test]
fn classify_flags_drop_table_critical() {
let report = classify("DROP TABLE users;");
assert!(report.has_critical());
assert_eq!(report.findings[0].category, LintCategory::DestructiveDrop);
assert_eq!(report.findings[0].severity, LintSeverity::Critical);
}
#[test]
fn classify_clean_migration_is_clean() {
let report = classify("CREATE TABLE t (id int);\nCREATE INDEX CONCURRENTLY i ON t (c);");
assert!(report.is_clean());
}
#[test]
fn report_serializes_to_json() {
let report = classify("DROP TABLE x;");
let json = serde_json::to_string(&report).expect("serialize");
assert!(json.contains("destructive-drop"));
assert!(json.contains("critical"));
}
}