[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

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

[![Crates.io](https://img.shields.io/crates/v/sarif-to-md-core.svg)](https://crates.io/crates/sarif-to-md-core)
[![Documentation](https://docs.rs/sarif-to-md-core/badge.svg)](https://docs.rs/sarif-to-md-core)
[![License](https://img.shields.io/badge/license-MIT%2FApache--2.0-blue.svg)](../LICENSE-MIT)

## 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`:

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

## Quick Start

```rust
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.

```rust
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.

```rust
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.

```rust
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.

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

### Markdown Formats

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

### Error Types

```rust
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:

```rust
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

```rust
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

```rust
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

```rust
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](https://crates.io/crates/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:

```rust
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

```bash
# Run all tests
cargo test

# Run with output
cargo test -- --nocapture

# Test specific module
cargo test markdown::sarif
```

## Benchmarking

```bash
# Run benchmarks (requires nightly)
cargo +nightly bench
```

## Documentation

Generate and open the full API documentation:

```bash
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](../../CONTRIBUTING.md) for guidelines.

## Changelog

See [CHANGELOG.md](../../CHANGELOG.md) for version history.

## License

This project is dual-licensed under:

- MIT License ([LICENSE-MIT]../../LICENSE-MIT.md)
- Apache License 2.0 ([LICENSE-APACHE]../../LICENSE-APACHE.md)

Choose the license that best suits your needs.

## Resources

- [SARIF Specification]https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html
- [SARIF Tutorials]https://github.com/microsoft/sarif-tutorials
- [GitHub SARIF Support]https://docs.github.com/en/code-security/code-scanning/integrating-with-code-scanning/sarif-support-for-code-scanning

## Support

- [Documentation]https://docs.rs/sarif-to-md-core
- [Issue Tracker]https://github.com/fulgas/sarif-to-md-rs/issues
- [Discussions]https://github.com/fulgas/sarif-to-md-rs/discussions