[go: up one dir, main page]

sarif-to-md-core 0.1.0

Core library for converting SARIF security reports to Markdown.
sarif-to-md-core-0.1.0 has been yanked.

sarif-to-md-core

Core Rust library for parsing SARIF (Static Analysis Results Interchange Format) reports and converting them to Markdown.

Crates.io Documentation License

Features

  • SARIF Parsing - Robust parsing of SARIF v2.1.0 format
  • Markdown Generation - Flexible template-based Markdown generation
  • Multiple Formats - GitHub Flavored Markdown and CommonMark support
  • Extensible - Builder pattern for easy customization
  • Type-Safe - Strongly typed API with comprehensive error handling
  • Zero-Copy - Efficient parsing with minimal allocations

Installation

Add to your Cargo.toml:

[dependencies]
sarif-to-md-core = "0.1"

Quick Start

use sarif_to_md_core::{
    ReportProcessorBuilder,
    generators::SarifMarkdownGenerator,
    markdown::MarkdownFormat,
};
use std::fs;

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Read SARIF content
    let sarif_content = fs::read_to_string("security-report.sarif")?;
    
    // Build processor
    let processor = ReportProcessorBuilder::new()
        .generator(SarifMarkdownGenerator::new(
            MarkdownFormat::GitHubFlavored,
            true  // with emoji
        ))
        .content(sarif_content)
        .build()?;
    
    // Generate Markdown
    let markdown = processor.generate()?;
    
    println!("{}", markdown);
    Ok(())
}

API Overview

Core Types

ReportProcessor<G>

Main processor for generating Markdown from SARIF reports.

pub struct ReportProcessor<G: MarkdownGenerator> {
    generator: G,
    content: String,
}

impl<G: MarkdownGenerator> ReportProcessor<G> {
    pub fn new(generator: G, content: String) -> Self;
    pub fn generate(self) -> Result<String, Error>;
}

ReportProcessorBuilder<G>

Builder for constructing ReportProcessor instances.

impl ReportProcessorBuilder<()> {
    pub fn new() -> Self;
    pub fn generator<G: MarkdownGenerator>(self, generator: G) 
        -> ReportProcessorBuilder<G>;
}

impl<G: MarkdownGenerator> ReportProcessorBuilder<G> {
    pub fn content(self, content: String) -> Self;
    pub fn build(self) -> Result<ReportProcessor<G>, BuilderError>;
}

MarkdownGenerator Trait

Core trait for implementing custom Markdown generators.

pub trait MarkdownGenerator {
    type Input: DeserializeOwned;
    
    fn generate_markdown_template(&self, report: &Self::Input) 
        -> Result<String, GeneratorError>;
}

Generators

SarifMarkdownGenerator

Built-in generator for SARIF reports.

impl SarifMarkdownGenerator {
    pub fn new(
        markdown_format: MarkdownFormat, 
        with_emoji: bool
    ) -> Self;
}

Markdown Formats

pub enum MarkdownFormat {
    CommonMark,       // Standard Markdown
    GitHubFlavored,   // GitHub Flavored Markdown with HTML
}

Error Types

pub enum Error {
    JsonError(serde_json::Error),
    GeneratorError(GeneratorError),
    IoError(std::io::Error),
}

pub enum GeneratorError {
    TemplateError(askama::Error),
}

pub enum BuilderError {
    MissingContent,
}

Advanced Usage

Custom Generator Implementation

Create your own Markdown generator by implementing the MarkdownGenerator trait:

use sarif_to_md_core::{
    markdown::{MarkdownGenerator, GeneratorError},
    ReportProcessorBuilder,
};
use serde::Deserialize;

#[derive(Deserialize)]
struct CustomReport {
    findings: Vec<Finding>,
}

struct CustomMarkdownGenerator;

impl MarkdownGenerator for CustomMarkdownGenerator {
    type Input = CustomReport;
    
    fn generate_markdown_template(&self, report: &Self::Input) 
        -> Result<String, GeneratorError> 
    {
        let mut markdown = String::from("# Custom Report\n\n");
        for finding in &report.findings {
            markdown.push_str(&format!("- {}\n", finding.title));
        }
        Ok(markdown)
    }
}

// Use it
let processor = ReportProcessorBuilder::new()
    .generator(CustomMarkdownGenerator)
    .content(json_content)
    .build()?;

Customizing Output Format

use sarif_to_md_core::{
    generators::SarifMarkdownGenerator,
    markdown::MarkdownFormat,
};

// GitHub Flavored with collapsible sections
let gfm_generator = SarifMarkdownGenerator::new(
    MarkdownFormat::GitHubFlavored,
    true  // emoji enabled
);

// CommonMark for maximum compatibility
let cm_generator = SarifMarkdownGenerator::new(
    MarkdownFormat::CommonMark,
    false  // no emoji
);

Error Handling

use sarif_to_md_core::{Error, BuilderError};

match processor.generate() {
    Ok(markdown) => println!("{}", markdown),
    Err(Error::JsonError(e)) => eprintln!("Invalid JSON: {}", e),
    Err(Error::GeneratorError(e)) => eprintln!("Template error: {}", e),
    Err(Error::IoError(e)) => eprintln!("IO error: {}", e),
}

Processing Multiple Files

use std::path::Path;
use sarif_to_md_core::{
    ReportProcessorBuilder,
    generators::SarifMarkdownGenerator,
    markdown::MarkdownFormat,
};

fn process_directory(dir: &Path) -> Result<Vec<String>, Box<dyn std::error::Error>> {
    let mut reports = Vec::new();
    
    for entry in std::fs::read_dir(dir)? {
        let entry = entry?;
        if entry.path().extension().and_then(|s| s.to_str()) == Some("sarif") {
            let content = std::fs::read_to_string(entry.path())?;
            
            let processor = ReportProcessorBuilder::new()
                .generator(SarifMarkdownGenerator::new(
                    MarkdownFormat::CommonMark,
                    false
                ))
                .content(content)
                .build()?;
            
            reports.push(processor.generate()?);
        }
    }
    
    Ok(reports)
}

Template System

The library uses Askama for template rendering. Templates are located in templates/sarif/:

  • report.md - Main report template
  • macros.md - Reusable template macros

Template Variables

Templates have access to:

struct SarifReportTemplate {
    runs: Vec<SarifRun>,
    timestamp: String,
    with_emoji: bool,
    is_gfm: bool,
}

struct SarifRun {
    tool_name: String,
    tool_version: Option<String>,
    total_results: usize,
    severity_counts: Vec<SeverityCount>,
    results: Vec<SarifResultView>,
}

Testing

# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Test specific module
cargo test markdown::sarif

Benchmarking

# Run benchmarks (requires nightly)
cargo +nightly bench

Documentation

Generate and open the full API documentation:

cargo doc --no-deps --open

MSRV (Minimum Supported Rust Version)

This crate requires Rust 1.90 or later.

Dependencies

  • serde - Serialization framework
  • serde_json - JSON parsing
  • serde-sarif - SARIF format support
  • askama - Template engine
  • chrono - Date/time handling
  • thiserror - Error handling

Performance Considerations

  • Parsing: O(n) complexity for SARIF parsing
  • Template Rendering: O(m) where m is the number of findings
  • Memory: Minimal allocations, most data is borrowed
  • Throughput: Can process 1000+ findings per second on modern hardware

Architecture

sarif-to-md-core/
├── src/
│   ├── lib.rs                    # Public API
│   ├── error.rs                  # Error types
│   ├── generators.rs             # Re-exports
│   └── markdown/
│       ├── mod.rs                # Core traits
│       └── sarif/
│           ├── generator.rs      # SARIF generator
│           └── types.rs          # View models
└── templates/
    └── sarif/
        ├── report.md             # Main template
        └── macros.md             # Helper macros

Comparison with Alternatives

Feature sarif-to-md-core Other Tools
Language Rust JavaScript/Python
Performance High Medium
Memory Usage Low Medium-High
Type Safety Strong Weak
Dependencies Minimal Many

Contributing

See the parent repository's CONTRIBUTING.md for guidelines.

Changelog

See CHANGELOG.md for version history.

License

This project is dual-licensed under:

Choose the license that best suits your needs.

Resources

Support