Skip to main content

sarif_to_md_core/
lib.rs

1//! Convert SARIF security reports to Markdown with customizable output formats.
2//!
3//! ```rust
4//! use sarif_to_md_core::{
5//!     ReportProcessorBuilder,
6//!     generators::SarifMarkdownGenerator,
7//!     markdown::MarkdownFormat,
8//! };
9//!
10//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
11//! let sarif_json = r#"{"version": "2.1.0", "runs": []}"#;
12//! let processor = ReportProcessorBuilder::new()
13//!     .generator(SarifMarkdownGenerator::new(MarkdownFormat::GitHubFlavored, true))
14//!     .content(sarif_json.to_string())
15//!     .build()?;
16//!
17//! let markdown = processor.generate()?;
18//! # Ok(())
19//! # }
20//! ```
21
22use crate::error::{BuilderError, Error};
23use crate::markdown::MarkdownGenerator;
24
25pub mod error;
26pub mod generators;
27pub mod markdown;
28
29/// Processes reports into Markdown using configurable generators.
30pub struct ReportProcessor<G: MarkdownGenerator> {
31    generator: G,
32    content: String,
33}
34
35impl<G: MarkdownGenerator> ReportProcessor<G> {
36    /// Create a processor with the specified generator and JSON content.
37    pub fn new(generator: G, content: String) -> Self {
38        Self { generator, content }
39    }
40
41    /// Parse content and generate Markdown output.
42    pub fn generate(self) -> Result<String, Error> {
43        let data: G::Input = serde_json::from_str(&self.content)?;
44        let markdown = self.generator.generate_markdown_template(&data)?;
45        Ok(markdown)
46    }
47}
48
49/// Builder for configuring [`ReportProcessor`] instances using type-state pattern.
50pub struct ReportProcessorBuilder<G = ()> {
51    generator: G,
52    content: Option<String>,
53}
54
55impl ReportProcessorBuilder<()> {
56    /// Create a new builder instance.
57    pub fn new() -> Self {
58        Self {
59            generator: (),
60            content: None,
61        }
62    }
63}
64
65impl Default for ReportProcessorBuilder<()> {
66    fn default() -> Self {
67        Self::new()
68    }
69}
70
71impl ReportProcessorBuilder<()> {
72    /// Set the Markdown generator to use.
73    pub fn generator<G: MarkdownGenerator>(self, generator: G) -> ReportProcessorBuilder<G> {
74        ReportProcessorBuilder {
75            generator,
76            content: self.content,
77        }
78    }
79}
80
81impl<G: MarkdownGenerator> ReportProcessorBuilder<G> {
82    /// Set the JSON report content to process.
83    pub fn content(mut self, content: String) -> Self {
84        self.content = Some(content);
85        self
86    }
87
88    /// Build the configured processor.
89    pub fn build(self) -> Result<ReportProcessor<G>, BuilderError> {
90        let content = self.content.ok_or(BuilderError::MissingContent)?;
91
92        Ok(ReportProcessor::new(self.generator, content))
93    }
94}
95
96#[cfg(test)]
97mod tests {
98    use rstest::*;
99
100    #[rstest]
101    fn public_api() -> Result<(), Box<dyn std::error::Error>> {
102        rustup_toolchain::install(public_api::MINIMUM_NIGHTLY_RUST_VERSION)?;
103
104        let rustdoc_json = rustdoc_json::Builder::default()
105            .toolchain(public_api::MINIMUM_NIGHTLY_RUST_VERSION)
106            .build()?;
107
108        let public_api = public_api::Builder::from_rustdoc_json(rustdoc_json).build()?;
109
110        insta::assert_snapshot!(public_api);
111        Ok(())
112    }
113}