#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn format_qg_as_junit(violations: &[QualityViolation]) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
writeln!(&mut output, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
writeln!(&mut output, r#"<testsuites name="Quality Gate">"#)?;
writeln!(
&mut output,
r#" <testsuite name="Quality Checks" tests="{}" failures="{}">"#,
violations.len(),
violations.len()
)?;
for violation in violations {
write_junit_test_case(&mut output, violation)?;
}
writeln!(&mut output, r" </testsuite>")?;
writeln!(&mut output, r"</testsuites>")?;
Ok(output)
}
fn write_junit_test_case(
output: &mut String,
violation: &QualityViolation,
) -> Result<(), std::fmt::Error> {
use std::fmt::Write;
writeln!(
output,
r#" <testcase name="{}" classname="{}">"#,
violation.message, violation.check_type
)?;
writeln!(
output,
r#" <failure message="{}" type="{}"/>"#,
violation.message, violation.severity
)?;
writeln!(output, r" </testcase>")?;
Ok(())
}
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn format_project_output(
results: &QualityGateResults,
violations: &[QualityViolation],
format: QualityGateOutputFormat,
) -> Result<String> {
match format {
QualityGateOutputFormat::Json => Ok(serde_json::to_string_pretty(&json!({
"passed": results.passed,
"results": results,
"violations": violations,
}))?),
QualityGateOutputFormat::Summary
| QualityGateOutputFormat::Markdown
| QualityGateOutputFormat::Detailed
| QualityGateOutputFormat::Human => Ok(format_project_summary(results, violations)),
QualityGateOutputFormat::Junit => format_qg_as_junit(violations),
}
}
fn format_project_summary(results: &QualityGateResults, violations: &[QualityViolation]) -> String {
let mut output = String::new();
output.push_str("# Quality Gate Results\n\n");
if results.passed {
output.push_str("## ✅ PASSED\n\n");
} else {
output.push_str("## ❌ FAILED\n\n");
}
output.push_str(&format!(
"Total violations: {}\n\n",
results.total_violations
));
if violations.is_empty() {
output.push_str("No violations found.\n");
} else {
output.push_str("## Violations by Type\n\n");
let grouped = group_violations_by_type(violations);
output.push_str(&format_violations_markdown(&grouped));
}
output
}
fn group_violations_by_type(
violations: &[QualityViolation],
) -> HashMap<String, Vec<&QualityViolation>> {
let mut grouped = HashMap::new();
for violation in violations {
grouped
.entry(violation.check_type.clone())
.or_insert_with(Vec::new)
.push(violation);
}
grouped
}
fn format_violations_markdown(grouped: &HashMap<String, Vec<&QualityViolation>>) -> String {
let mut output = String::new();
for (check_type, violations) in grouped {
output.push_str(&format!(
"### {} ({} violation{})\n\n",
check_type,
violations.len(),
if violations.len() == 1 { "" } else { "s" }
));
for violation in violations {
if let Some(line) = violation.line {
output.push_str(&format!(
"- **{}:{}**: {}\n",
violation.file, line, violation.message
));
} else {
output.push_str(&format!(
"- **{}**: {}\n",
violation.file, violation.message
));
}
}
output.push('\n');
}
output
}