mod html;
mod json;
mod markdown;
mod sarif;
mod text;
pub mod narrative;
pub mod report_context;
pub mod svg;
use crate::models::HealthReport;
use anyhow::{anyhow, Result};
use std::str::FromStr;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OutputFormat {
Text,
Json,
Sarif,
Html,
Markdown,
}
impl FromStr for OutputFormat {
type Err = anyhow::Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_lowercase().as_str() {
"text" | "txt" | "terminal" => Ok(OutputFormat::Text),
"json" => Ok(OutputFormat::Json),
"sarif" => Ok(OutputFormat::Sarif),
"html" => Ok(OutputFormat::Html),
"markdown" | "md" => Ok(OutputFormat::Markdown),
_ => Err(anyhow!(
"Unknown format '{}'. Valid formats: text, json, sarif, html, markdown",
s
)),
}
}
}
impl std::fmt::Display for OutputFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
OutputFormat::Text => write!(f, "text"),
OutputFormat::Json => write!(f, "json"),
OutputFormat::Sarif => write!(f, "sarif"),
OutputFormat::Html => write!(f, "html"),
OutputFormat::Markdown => write!(f, "markdown"),
}
}
}
pub fn report(report: &HealthReport, format: &str) -> Result<String> {
let fmt = OutputFormat::from_str(format)?;
report_with_format(report, fmt)
}
pub fn report_with_format(report: &HealthReport, format: OutputFormat) -> Result<String> {
match format {
OutputFormat::Text => text::render(report),
OutputFormat::Json => json::render(report),
OutputFormat::Sarif => sarif::render(report),
OutputFormat::Html => html::render(report),
OutputFormat::Markdown => markdown::render(report),
}
}
pub fn report_with_context(
ctx: &report_context::ReportContext,
format: OutputFormat,
) -> Result<String> {
match format {
OutputFormat::Text => text::render_with_context(ctx),
OutputFormat::Html => html::render_with_context(ctx),
OutputFormat::Json => json::render(&ctx.health),
OutputFormat::Sarif => sarif::render(&ctx.health),
OutputFormat::Markdown => markdown::render(&ctx.health),
}
}
#[allow(dead_code)] pub fn file_extension(format: OutputFormat) -> &'static str {
match format {
OutputFormat::Text => "txt",
OutputFormat::Json => "json",
OutputFormat::Sarif => "sarif.json",
OutputFormat::Html => "html",
OutputFormat::Markdown => "md",
}
}
#[cfg(test)]
pub(crate) mod tests {
use super::*;
pub(crate) fn test_report() -> HealthReport {
use crate::models::{Finding, FindingsSummary, Severity};
let findings = vec![Finding {
id: "f1".into(),
detector: "TestDetector".into(),
severity: Severity::High,
title: "Test finding".into(),
description: "A test issue".into(),
affected_files: vec!["src/main.rs".into()],
line_start: Some(10),
suggested_fix: Some("Fix it".into()),
..Default::default()
}];
HealthReport {
overall_score: 85.0,
grade: "B".into(),
structure_score: 90.0,
quality_score: 80.0,
architecture_score: Some(85.0),
findings_summary: FindingsSummary::from_findings(&findings),
findings,
total_files: 100,
total_functions: 500,
total_classes: 50,
total_loc: 10000,
}
}
#[test]
fn test_report_with_context_text() {
let report = test_report();
let ctx = report_context::ReportContext {
health: report,
graph_data: None,
git_data: None,
source_snippets: vec![],
previous_health: None,
style_profile: None,
};
let output = report_with_context(&ctx, OutputFormat::Text).unwrap();
assert!(output.contains("Score:") || output.contains("score") || output.contains("Repotoire"));
}
#[test]
fn test_format_parsing() {
assert_eq!(OutputFormat::from_str("text").unwrap(), OutputFormat::Text);
assert_eq!(OutputFormat::from_str("JSON").unwrap(), OutputFormat::Json);
assert_eq!(
OutputFormat::from_str("sarif").unwrap(),
OutputFormat::Sarif
);
assert_eq!(OutputFormat::from_str("html").unwrap(), OutputFormat::Html);
assert_eq!(
OutputFormat::from_str("md").unwrap(),
OutputFormat::Markdown
);
assert!(OutputFormat::from_str("invalid").is_err());
}
}