use std::collections::BTreeSet;
use std::path::PathBuf;
use crate::cli::OutputFormat;
use crate::error::CommandError;
use crate::release::{self, ReleaseError};
const CHANGES_DIR: &str = "changes";
pub(crate) fn run_changes_validate(format: OutputFormat) -> Result<(), CommandError> {
let document = release::load_metadata()?;
let crates = release::discover(&document);
release::validate(&crates)
.map_err(|diagnostics| CommandError::Release(ReleaseError::Validation(diagnostics)))?;
let known_units: BTreeSet<String> = crates
.iter()
.filter_map(|c| match &c.metadata {
release::CrateMetadata::Valid(md) if md.publish => md.release_unit.clone(),
_ => None,
})
.collect();
let changes_dir = PathBuf::from(CHANGES_DIR);
let (fragments, parse_errors) =
release::load_fragments_files(&changes_dir).map_err(ReleaseError::from)?;
if !parse_errors.is_empty() {
print_parse_errors(&parse_errors, format)?;
let diagnostics = parse_errors
.into_iter()
.map(|(name, msg)| release::Diagnostic {
crate_name: name,
message: msg,
})
.collect();
return Err(CommandError::Release(ReleaseError::Validation(diagnostics)));
}
match release::validate_fragments(&fragments, &known_units) {
Ok(change_report) => {
print_ok(&change_report, format)?;
Ok(())
}
Err(diagnostics) => {
print_fail(&diagnostics, format)?;
let wrapped: Vec<release::Diagnostic> = diagnostics
.into_iter()
.map(|msg| release::Diagnostic {
crate_name: "changes".to_string(),
message: msg,
})
.collect();
Err(CommandError::Release(ReleaseError::Validation(wrapped)))
}
}
}
fn print_ok(report: &release::ChangeReport, format: OutputFormat) -> Result<(), CommandError> {
match format {
OutputFormat::Human => {
if report.changed_units.is_empty() {
println!("no pending change fragments in changes/");
} else {
println!(
"change fragments OK: {} entries across {} unit(s)",
report.entry_count,
report.changed_units.len(),
);
for (unit, kind) in &report.changed_units {
println!(" {unit}: {kind:?}");
}
}
Ok(())
}
OutputFormat::Json => {
let payload = serde_json::json!({
"ok": true,
"entry_count": report.entry_count,
"changed_units": report.changed_units.iter()
.map(|(unit, kind)| (unit.clone(), format!("{kind:?}")))
.collect::<std::collections::BTreeMap<_, _>>(),
});
println!("{}", serde_json::to_string_pretty(&payload)?);
Ok(())
}
}
}
fn print_fail(diagnostics: &[String], format: OutputFormat) -> Result<(), CommandError> {
match format {
OutputFormat::Human => {
eprintln!("change fragment validation failed ({}):", diagnostics.len());
for diagnostic in diagnostics {
eprintln!(" {diagnostic}");
}
Ok(())
}
OutputFormat::Json => {
let payload = serde_json::json!({
"ok": false,
"diagnostics": diagnostics,
});
println!("{}", serde_json::to_string_pretty(&payload)?);
Ok(())
}
}
}
fn print_parse_errors(
errors: &[(String, String)],
format: OutputFormat,
) -> Result<(), CommandError> {
match format {
OutputFormat::Human => {
eprintln!("change fragment parse errors ({}):", errors.len());
for (name, msg) in errors {
eprintln!(" {name}: {msg}");
}
Ok(())
}
OutputFormat::Json => {
let payload = serde_json::json!({
"ok": false,
"parse_errors": errors.iter().map(|(n, m)| {
serde_json::json!({"file": n, "error": m})
}).collect::<Vec<_>>(),
});
println!("{}", serde_json::to_string_pretty(&payload)?);
Ok(())
}
}
}