use crate::cli::docs_url::rule_url;
use crate::rules::registry::DiagnosticSeverity;
use crate::rules::registry::SourceSpan;
use crate::rules::Diagnostic;
pub fn emit_human_to_string(
diagnostics: &[(std::path::PathBuf, Vec<Diagnostic>)],
total_errors: usize,
total_warnings: usize,
duration_ms: Option<u64>,
) -> String {
let mut out = String::new();
for (path, diags) in diagnostics {
for d in diags {
let severity_label = match d.severity {
DiagnosticSeverity::Error => "error",
DiagnosticSeverity::Warning => "warning",
};
out.push_str(&format!("{}[{}]: {}\n", severity_label, d.code, d.message));
out.push_str(&format!(
" --> {}\n",
format_source_location(path, &d.source)
));
out.push_str(" |\n");
out.push_str(&format!(" = profile: {}\n", d.profile));
out.push_str(&format!(" = schema path: {}\n", d.pointer));
if let Some(hint) = &d.hint {
out.push_str(&format!(" = hint: {}\n", hint));
}
out.push_str(&format!(" = see: {}\n", rule_url(&d.code)));
out.push('\n');
}
}
let total = total_errors + total_warnings;
let schema_count = diagnostics.len();
let duration_part = duration_ms.map_or(String::new(), |d| format!(" in {}ms", d));
out.push_str(&format!(
"{} issue{} found ({} error{}, {} warning{}) across {} schema{}{}\n",
total,
if total == 1 { "" } else { "s" },
total_errors,
if total_errors == 1 { "" } else { "s" },
total_warnings,
if total_warnings == 1 { "" } else { "s" },
schema_count,
if schema_count == 1 { "" } else { "s" },
duration_part
));
out
}
fn format_source_location(path: &std::path::Path, source: &Option<SourceSpan>) -> String {
if let Some(span) = source {
match (span.line, span.col) {
(Some(line), Some(col)) => format!("{}:{}:{}", span.file, line, col),
(Some(line), None) => format!("{}:{}", span.file, line),
(None, _) => span.file.clone(),
}
} else {
path.display().to_string()
}
}