secreport/render.rs
1use crate::format::Format;
2use crate::models::GenericFinding;
3use secfinding::{Finding, Reportable};
4use std::io::Write;
5
6pub mod json;
7pub mod markdown;
8pub mod summary;
9
10/// Render ANY type that implements [`Reportable`] into the given format.
11///
12/// This is the most flexible rendering function - it works with any type that
13/// implements the `Reportable` trait from the `secfinding` crate. For rendering
14/// native [`Finding`] types, see [`render`].
15///
16/// # Parameters
17///
18/// - `findings`: A slice of findings that implement `Reportable`
19/// - `format`: The output format to use
20/// - `tool_name`: Name of the tool generating the report (used in SARIF/Markdown headers)
21///
22/// # Returns
23///
24/// - `Ok(String)` containing the rendered output
25/// - `Err(serde_json::Error)` if conversion or serialization fails
26///
27/// # Example
28///
29/// ```
30/// use secfinding::{Finding, Severity};
31/// use secreport::format::Format;
32/// use secreport::render::render_any;
33///
34/// let finding = Finding::builder("my-scanner", "example.com", Severity::High)
35/// .title("Test Finding")
36/// .build()
37/// .unwrap();
38///
39/// let output = render_any(&[finding], Format::Json, "my-scanner").unwrap();
40/// assert!(output.contains("Test Finding"));
41/// ```
42pub fn render_any<R: Reportable>(
43 findings: &[R],
44 format: Format,
45 tool_name: &str,
46) -> Result<String, serde_json::Error> {
47 let generic: Vec<GenericFinding> = findings
48 .iter()
49 .map(GenericFinding::try_from_reportable)
50 .collect::<Result<_, _>>()?;
51
52 match format {
53 Format::Text => Ok(summary::render_text_generic(&generic)),
54 Format::Json => json::render_json_generic(&generic),
55 Format::Jsonl => json::render_jsonl_generic(&generic),
56 Format::Sarif => json::render_sarif_generic(&generic, tool_name),
57 Format::Markdown => Ok(markdown::render_markdown_generic(&generic, tool_name)),
58 }
59}
60
61/// Render native [`Finding`] types in the given format.
62///
63/// This is a convenience wrapper around [`render_any`] specifically for
64/// the native `Finding` type from the `secfinding` crate.
65///
66/// # Parameters
67///
68/// - `findings`: A slice of [`Finding`] objects
69/// - `format`: The output format to use
70/// - `tool_name`: Name of the tool generating the report
71///
72/// # Returns
73///
74/// - `Ok(String)` containing the rendered output
75/// - `Err(serde_json::Error)` if serialization fails
76///
77/// # Example
78///
79/// ```
80/// use secfinding::{Finding, Severity};
81/// use secreport::format::Format;
82/// use secreport::render::render;
83///
84/// let findings = vec![
85/// Finding::builder("scanner", "target.com", Severity::Critical)
86/// .title("Critical Vulnerability")
87/// .build()
88/// .unwrap(),
89/// ];
90///
91/// let markdown = render(&findings, Format::Markdown, "security-scanner").unwrap();
92/// assert!(markdown.contains("Critical Vulnerability"));
93/// ```
94pub fn render(
95 findings: &[Finding],
96 format: Format,
97 tool_name: &str,
98) -> Result<String, serde_json::Error> {
99 render_any(findings, format, tool_name)
100}
101
102/// Write rendered output to a writer.
103///
104/// This function writes the rendered content to any type implementing
105/// `std::io::Write`, such as files or stdout.
106///
107/// # Parameters
108///
109/// - `content`: The rendered content to write
110/// - `writer`: Any type implementing `Write`
111///
112/// # Returns
113///
114/// - `Ok(())` on success
115/// - `Err(std::io::Error)` if writing fails
116///
117/// # Example
118///
119/// ```
120/// use secreport::render::emit;
121///
122/// let content = "Security Report\n===============";
123/// let mut output = Vec::new();
124///
125/// emit(content, &mut output).unwrap();
126/// assert_eq!(String::from_utf8(output).unwrap(), content);
127/// ```
128///
129/// Writing to stdout:
130///
131/// ```
132/// use secreport::render::emit;
133/// use std::io;
134///
135/// # fn example() -> io::Result<()> {
136/// emit("Report complete!\n", io::stdout())?;
137/// # Ok(())
138/// # }
139/// ```
140pub fn emit(content: &str, mut writer: impl Write) -> std::io::Result<()> {
141 write!(writer, "{}", content)
142}