fn format_qg_as_json(
results: &QualityGateResults,
violations: &[QualityViolation],
) -> Result<String> {
Ok(serde_json::to_string_pretty(&serde_json::json!({
"results": results,
"violations": violations,
}))?)
}
fn format_qg_as_human(
results: &QualityGateResults,
violations: &[QualityViolation],
) -> Result<String> {
use std::fmt::Write;
let mut output = String::new();
write_qg_human_header(&mut output, results)?;
write_qg_violation_counts(&mut output, results)?;
if let Some(score) = results.provability_score {
writeln!(&mut output, "\nProvability score: {score:.2}")?;
}
if !violations.is_empty() {
write_qg_violations_list(&mut output, violations)?;
}
Ok(output)
}
fn write_qg_human_header(output: &mut String, results: &QualityGateResults) -> Result<()> {
use std::fmt::Write;
writeln!(output, "# Quality Gate Report\n")?;
writeln!(
output,
"Status: {}",
if results.passed {
"\u{2705} PASSED"
} else {
"\u{274c} FAILED"
}
)?;
writeln!(output, "Total violations: {}\n", results.total_violations)?;
Ok(())
}
fn write_qg_violation_counts(output: &mut String, results: &QualityGateResults) -> Result<()> {
use std::fmt::Write;
let counts = [
("Complexity", results.complexity_violations),
("Dead code", results.dead_code_violations),
("Technical debt", results.satd_violations),
("Entropy", results.entropy_violations),
("Security", results.security_violations),
("Duplicate code", results.duplicate_violations),
];
for (name, count) in counts {
if count > 0 {
writeln!(output, "## {name} violations: {count}")?;
}
}
Ok(())
}
fn write_qg_violations_list(output: &mut String, violations: &[QualityViolation]) -> Result<()> {
use std::fmt::Write;
writeln!(output, "\n## Violations:\n")?;
for v in violations {
writeln!(
output,
"- [{}] {} - {}",
v.severity, v.check_type, v.message
)?;
if let Some(line) = v.line {
writeln!(output, " File: {}:{}", v.file, line)?;
} else {
writeln!(output, " File: {}", v.file)?;
}
write_violation_details(output, v)?;
}
Ok(())
}
fn write_violation_details(output: &mut String, v: &QualityViolation) -> Result<()> {
use std::fmt::Write;
let Some(details) = &v.details else {
return Ok(());
};
if !details.score_factors.is_empty() {
writeln!(output, " Factors: {}", details.score_factors.join(", "))?;
}
if let Some(code) = &details.example_code {
let trimmed = code.trim();
if !trimmed.is_empty() {
writeln!(output, " Example: {}", truncate_line(trimmed, 100))?;
}
}
if details.affected_files.len() > 1 {
writeln!(output, " Files: {}", details.affected_files.join(", "))?;
}
Ok(())
}
fn truncate_line(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
format!("{}...", &s[..max_len])
}
}
fn format_qg_as_junit(violations: &[QualityViolation]) -> Result<String> {
let mut output = String::new();
write_junit_header(&mut output)?;
write_junit_testsuite_start(&mut output, violations.len())?;
write_junit_testcases(&mut output, violations)?;
write_junit_footer(&mut output)?;
Ok(output)
}
fn write_junit_header(output: &mut String) -> Result<()> {
use std::fmt::Write;
writeln!(output, r#"<?xml version="1.0" encoding="UTF-8"?>"#)?;
writeln!(output, r#"<testsuites name="Quality Gate">"#)?;
Ok(())
}
fn write_junit_testsuite_start(output: &mut String, count: usize) -> Result<()> {
use std::fmt::Write;
writeln!(
output,
r#" <testsuite name="Quality Checks" tests="{count}" failures="{count}">"#
)?;
Ok(())
}
fn write_junit_testcases(output: &mut String, violations: &[QualityViolation]) -> Result<()> {
for v in violations {
write_single_junit_testcase(output, v)?;
}
Ok(())
}
fn write_single_junit_testcase(output: &mut String, v: &QualityViolation) -> Result<()> {
use std::fmt::Write;
writeln!(
output,
r#" <testcase name="{}" classname="{}">"#,
v.message, v.check_type
)?;
writeln!(
output,
r#" <failure message="{}" type="{}"/>"#,
v.message, v.severity
)?;
writeln!(output, r" </testcase>")?;
Ok(())
}
fn write_junit_footer(output: &mut String) -> Result<()> {
use std::fmt::Write;
writeln!(output, r" </testsuite>")?;
writeln!(output, r"</testsuites>")?;
Ok(())
}