1pub mod json;
2pub mod matrix;
3pub mod sarif;
4pub mod vanta;
5
6pub use sarif::utc_now_rfc3339;
7
8use anyhow::Result;
9use libverify_core::assessment::{BatchReport, VerificationResult};
10
11#[derive(Debug, Clone, Copy)]
12pub enum Format {
13 Json,
14 Matrix,
15 Sarif,
16 Vanta,
17}
18
19pub struct OutputOptions {
20 pub format: Format,
21 pub only_failures: bool,
22 pub tool_name: String,
24 pub tool_version: String,
26}
27
28pub fn parse_format(s: &str) -> Result<Format> {
29 match s {
30 "json" => Ok(Format::Json),
31 "matrix" => Ok(Format::Matrix),
32 "sarif" => Ok(Format::Sarif),
33 "vanta" => Ok(Format::Vanta),
34 _ => anyhow::bail!("invalid format: {s} (use 'json', 'matrix', 'sarif', or 'vanta')"),
35 }
36}
37
38pub fn render(opts: &OutputOptions, result: &VerificationResult) -> Result<String> {
39 match opts.format {
40 Format::Json => json::render(result, opts.only_failures),
41 Format::Matrix => matrix::render(result, opts.only_failures),
42 Format::Sarif => sarif::render(
43 result,
44 opts.only_failures,
45 &opts.tool_name,
46 &opts.tool_version,
47 ),
48 Format::Vanta => vanta::render(result, opts.only_failures),
49 }
50}
51
52pub fn render_batch(opts: &OutputOptions, batch: &BatchReport) -> Result<String> {
53 match opts.format {
54 Format::Json => json::render_batch(batch, opts.only_failures),
55 Format::Matrix => matrix::render_batch(batch, opts.only_failures),
56 Format::Sarif => sarif::render_batch(
57 batch,
58 opts.only_failures,
59 &opts.tool_name,
60 &opts.tool_version,
61 ),
62 Format::Vanta => vanta::render_batch(batch, opts.only_failures),
63 }
64}
65
66#[cfg(test)]
67mod tests {
68 use super::*;
69 use libverify_core::assessment::AssessmentReport;
70 use libverify_core::control::{ControlFinding, builtin};
71 use libverify_core::profile::{FindingSeverity, GateDecision, ProfileOutcome};
72
73 fn opts(format: Format) -> OutputOptions {
74 OutputOptions {
75 format,
76 only_failures: false,
77 tool_name: "test".to_string(),
78 tool_version: "0.1".to_string(),
79 }
80 }
81
82 fn sample_result() -> VerificationResult {
83 VerificationResult {
84 report: AssessmentReport {
85 profile_name: "test".to_string(),
86 findings: vec![ControlFinding::satisfied(
87 builtin::id(builtin::REVIEW_INDEPENDENCE),
88 "ok",
89 vec![],
90 )],
91 outcomes: vec![ProfileOutcome {
92 control_id: builtin::id(builtin::REVIEW_INDEPENDENCE),
93 severity: FindingSeverity::Info,
94 decision: GateDecision::Pass,
95 rationale: "ok".to_string(),
96 annotations: Default::default(),
97 }],
98 severity_labels: Default::default(),
99 },
100 evidence: None,
101 }
102 }
103
104 #[test]
105 fn parse_format_valid() {
106 assert!(matches!(parse_format("json").unwrap(), Format::Json));
107 assert!(matches!(parse_format("matrix").unwrap(), Format::Matrix));
108 assert!(matches!(parse_format("sarif").unwrap(), Format::Sarif));
109 assert!(matches!(parse_format("vanta").unwrap(), Format::Vanta));
110 }
111
112 #[test]
113 fn parse_format_invalid() {
114 assert!(parse_format("xml").is_err());
115 assert!(parse_format("").is_err());
116 }
117
118 #[test]
119 fn render_json_format() {
120 let output = render(&opts(Format::Json), &sample_result()).unwrap();
121 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
122 assert_eq!(parsed["profile_name"], "test");
123 }
124
125 #[test]
126 fn render_sarif_format() {
127 let output = render(&opts(Format::Sarif), &sample_result()).unwrap();
128 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
129 assert_eq!(parsed["version"], "2.1.0");
130 }
131
132 #[test]
133 fn render_batch_json_format() {
134 let batch = BatchReport {
135 reports: vec![],
136 total_pass: 0,
137 total_review: 0,
138 total_fail: 0,
139 skipped: vec![],
140 };
141 let output = render_batch(&opts(Format::Json), &batch).unwrap();
142 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
143 assert!(parsed["reports"].as_array().unwrap().is_empty());
144 }
145
146 #[test]
147 fn render_batch_sarif_format() {
148 let batch = BatchReport {
149 reports: vec![],
150 total_pass: 0,
151 total_review: 0,
152 total_fail: 0,
153 skipped: vec![],
154 };
155 let output = render_batch(&opts(Format::Sarif), &batch).unwrap();
156 let parsed: serde_json::Value = serde_json::from_str(&output).unwrap();
157 assert_eq!(parsed["version"], "2.1.0");
158 }
159}