sarif_rust 0.3.0

A comprehensive Rust library for parsing, generating, and manipulating SARIF (Static Analysis Results Interchange Format) v2.1.0 files
Documentation
//! Comprehensive demonstration of all SARIF conversion formats
//!
//! This example shows how to create a SARIF log and convert it to all available formats:
//! - Original SARIF JSON
//! - CSV format
//! - HTML report
//! - JSON Lines (JSONL)
//! - GitHub Security format

use sarif_rust::builder::*;
use sarif_rust::types::Level;
use sarif_rust::utils::conversion::*;
use std::fs;
use std::path::Path;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    println!("🔄 SARIF Conversion Demo - All Formats");
    println!("=====================================\n");

    // Create a comprehensive SARIF log with multiple findings
    let sarif_log = create_sample_sarif_log()?;

    println!("📊 Created SARIF log with:");
    println!("  - {} runs", sarif_log.runs.len());
    let total_results: usize = sarif_log
        .runs
        .iter()
        .map(|run| run.results.as_ref().map_or(0, |r| r.len()))
        .sum();
    println!("  - {} total results", total_results);
    println!();

    // Create output directory
    let output_dir = "sarif_outputs";
    fs::create_dir_all(output_dir)?;
    println!("📁 Created output directory: {}", output_dir);
    println!();

    // 1. Original SARIF JSON (pretty-printed)
    output_sarif_json(&sarif_log, output_dir)?;

    // 2. CSV conversion
    output_csv_format(&sarif_log, output_dir)?;

    // 3. HTML report
    output_html_format(&sarif_log, output_dir)?;

    // 4. JSON Lines format
    output_jsonl_format(&sarif_log, output_dir)?;

    // 5. GitHub Security format
    output_github_format(&sarif_log, output_dir)?;

    // 6. Demonstrate custom configuration
    output_with_custom_config(&sarif_log, output_dir)?;

    println!("\n✅ All conversions completed successfully!");
    println!("\nGenerated files in '{}':", output_dir);
    println!("  📄 results.sarif          - Original SARIF JSON");
    println!("  📊 results.csv            - CSV format");
    println!("  🌐 results.html           - HTML report");
    println!("  📋 results.jsonl          - JSON Lines format");
    println!("  🔒 results_github.json    - GitHub Security format");
    println!("  ⚙️  results_filtered.csv   - Custom filtered CSV");
    println!();
    println!("💡 Open results.html in a web browser to see the interactive report!");

    Ok(())
}

/// Create a sample SARIF log with various types of findings
fn create_sample_sarif_log() -> Result<sarif_rust::types::SarifLog, Box<dyn std::error::Error>> {
    let sarif_log = SarifLogBuilder::new()
        .with_schema("https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json")
        .add_run(
            RunBuilder::with_tool("security-scanner", Some("2.1.0"))
                .add_result(
                    ResultBuilder::with_text_message("SQL injection vulnerability detected in user input validation")
                        .with_rule_id("CWE-89")
                        .with_level(Level::Error)
                        .add_file_location("src/auth/login.rs", 42, 15)
                        .build()
                )
                .add_result(
                    ResultBuilder::with_text_message("Hardcoded password found in configuration")
                        .with_rule_id("CWE-798")
                        .with_level(Level::Error)
                        .add_file_location("src/config/database.rs", 28, 1)
                        .build()
                )
                .add_result(
                    ResultBuilder::with_text_message("Unescaped user input in HTML template")
                        .with_rule_id("CWE-79")
                        .with_level(Level::Warning)
                        .add_file_location("src/web/templates.rs", 156, 8)
                        .build()
                )
                .add_result(
                    ResultBuilder::with_text_message("Missing error handling for file operations")
                        .with_rule_id("ERR-001")
                        .with_level(Level::Warning)
                        .add_file_location("src/utils/file_manager.rs", 89, 12)
                        .build()
                )
                .build()
        )
        .add_run(
            RunBuilder::with_tool("style-checker", Some("1.5.2"))
                .add_result(
                    ResultBuilder::with_text_message("Consider using const instead of let for immutable values")
                        .with_rule_id("STYLE-001")
                        .with_level(Level::Note)
                        .add_file_location("src/main.rs", 15, 5)
                        .build()
                )
                .add_result(
                    ResultBuilder::with_text_message("Function exceeds recommended length (>50 lines)")
                        .with_rule_id("STYLE-002")
                        .with_level(Level::Note)
                        .add_file_location("src/business/calculator.rs", 78, 1)
                        .build()
                )
                .add_result(
                    ResultBuilder::with_text_message("Unused import detected")
                        .with_rule_id("STYLE-003")
                        .with_level(Level::Note)
                        .add_file_location("src/helpers/utils.rs", 3, 1)
                        .build()
                )
                .build()
        )
        .build()?;

    Ok(sarif_log)
}

/// Output original SARIF JSON format
fn output_sarif_json(
    sarif: &sarif_rust::types::SarifLog,
    output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("1. 📄 Converting to SARIF JSON");

    let json = sarif_rust::to_string_pretty(sarif)?;
    let file_path = Path::new(output_dir).join("results.sarif");
    fs::write(&file_path, &json)?;

    println!(
        "   ✓ Generated: {} ({} bytes)",
        file_path.display(),
        json.len()
    );
    println!("   📋 Pretty-printed SARIF v2.1.0 JSON format");
    println!();

    Ok(())
}

/// Output CSV format
fn output_csv_format(
    sarif: &sarif_rust::types::SarifLog,
    output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("2. 📊 Converting to CSV");

    let csv_converter = CsvConverter::new();
    let csv_output = csv_converter.convert_to_csv(sarif)?;
    let file_path = Path::new(output_dir).join("results.csv");
    fs::write(&file_path, &csv_output)?;

    println!(
        "   ✓ Generated: {} ({} bytes)",
        file_path.display(),
        csv_output.len()
    );
    println!(
        "   📋 Comma-separated values with columns: tool, rule_id, level, message, file, line"
    );

    // Show a preview of the CSV content
    let lines: Vec<&str> = csv_output.lines().take(3).collect();
    if lines.len() > 1 {
        println!("   📄 Preview:");
        for (i, line) in lines.iter().enumerate() {
            if i == 0 {
                println!("      Header: {}", line);
            } else {
                println!("      Row {}: {}", i, line);
            }
        }
    }
    println!();

    Ok(())
}

/// Output HTML report format
fn output_html_format(
    sarif: &sarif_rust::types::SarifLog,
    output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("3. 🌐 Converting to HTML");

    let html_converter = HtmlConverter::new();
    let html_output = html_converter.convert_to_html(sarif)?;
    let file_path = Path::new(output_dir).join("results.html");
    fs::write(&file_path, &html_output)?;

    println!(
        "   ✓ Generated: {} ({} bytes)",
        file_path.display(),
        html_output.len()
    );
    println!("   📋 Interactive HTML report with styling and navigation");
    println!("   🌐 Open in web browser for best viewing experience");
    println!();

    Ok(())
}

/// Output JSON Lines format
fn output_jsonl_format(
    sarif: &sarif_rust::types::SarifLog,
    output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("4. 📋 Converting to JSON Lines (JSONL)");

    let jsonl_converter = JsonLinesConverter::new();
    let jsonl_output = jsonl_converter.convert_to_jsonl(sarif)?;
    let file_path = Path::new(output_dir).join("results.jsonl");
    fs::write(&file_path, &jsonl_output)?;

    println!(
        "   ✓ Generated: {} ({} bytes)",
        file_path.display(),
        jsonl_output.len()
    );
    println!("   📋 One JSON object per line, ideal for streaming and log processing");

    // Show line count
    let line_count = jsonl_output.lines().count();
    println!("   📊 Contains {} lines (one per result)", line_count);
    println!();

    Ok(())
}

/// Output GitHub Security format
fn output_github_format(
    sarif: &sarif_rust::types::SarifLog,
    output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("5. 🔒 Converting to GitHub Security format");

    let github_converter = GitHubSecurityConverter::new();
    let github_output = github_converter.convert_to_github_format(sarif)?;
    let file_path = Path::new(output_dir).join("results_github.json");
    fs::write(&file_path, &github_output)?;

    println!(
        "   ✓ Generated: {} ({} bytes)",
        file_path.display(),
        github_output.len()
    );
    println!("   📋 GitHub-compatible security alert format");
    println!("   🔒 Ready for GitHub Security Advisory integration");
    println!();

    Ok(())
}

/// Demonstrate conversion with custom configuration
fn output_with_custom_config(
    sarif: &sarif_rust::types::SarifLog,
    output_dir: &str,
) -> Result<(), Box<dyn std::error::Error>> {
    println!("6. ⚙️  Converting with custom configuration");

    // Create a custom configuration that filters for warnings and errors only
    let config = ConversionConfig {
        include_full_paths: true,
        max_message_length: Some(100),   // Truncate long messages
        min_level: Some(Level::Warning), // Only warnings and errors
        include_snippets: false,
        field_mappings: std::collections::HashMap::new(),
    };

    let csv_converter = CsvConverter::with_config(config);
    let csv_output = csv_converter.convert_to_csv(sarif)?;
    let file_path = Path::new(output_dir).join("results_filtered.csv");
    fs::write(&file_path, &csv_output)?;

    println!(
        "   ✓ Generated: {} ({} bytes)",
        file_path.display(),
        csv_output.len()
    );
    println!("   📋 Filtered CSV (warnings/errors only, max 100 char messages)");

    // Count lines to show filtering effect
    let filtered_lines = csv_output.lines().count();
    println!(
        "   📊 Filtered to {} results (excluding notes)",
        filtered_lines.saturating_sub(1)
    ); // -1 for header
    println!();

    Ok(())
}