syncable_cli/analyzer/hadolint/formatter/
json.rs

1//! JSON formatter for hadolint-rs.
2//!
3//! Outputs lint results in JSON format for CI/CD pipeline integration.
4//! Compatible with the original hadolint JSON output.
5
6use crate::analyzer::hadolint::formatter::Formatter;
7use crate::analyzer::hadolint::lint::LintResult;
8use crate::analyzer::hadolint::types::Severity;
9use serde::Serialize;
10use std::io::Write;
11
12/// JSON output formatter.
13#[derive(Debug, Clone, Default)]
14pub struct JsonFormatter {
15    /// Pretty-print the JSON output.
16    pub pretty: bool,
17}
18
19impl JsonFormatter {
20    /// Create a new JSON formatter.
21    pub fn new() -> Self {
22        Self::default()
23    }
24
25    /// Create a JSON formatter with pretty-printing enabled.
26    pub fn pretty() -> Self {
27        Self { pretty: true }
28    }
29}
30
31/// JSON representation of a lint failure.
32#[derive(Debug, Serialize)]
33struct JsonFailure {
34    line: u32,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    column: Option<u32>,
37    code: String,
38    message: String,
39    level: String,
40    file: String,
41}
42
43impl Formatter for JsonFormatter {
44    fn format<W: Write>(
45        &self,
46        result: &LintResult,
47        filename: &str,
48        writer: &mut W,
49    ) -> std::io::Result<()> {
50        let failures: Vec<JsonFailure> = result
51            .failures
52            .iter()
53            .map(|f| JsonFailure {
54                line: f.line,
55                column: f.column,
56                code: f.code.to_string(),
57                message: f.message.clone(),
58                level: match f.severity {
59                    Severity::Error => "error",
60                    Severity::Warning => "warning",
61                    Severity::Info => "info",
62                    Severity::Style => "style",
63                    Severity::Ignore => "ignore",
64                }
65                .to_string(),
66                file: filename.to_string(),
67            })
68            .collect();
69
70        let json = if self.pretty {
71            serde_json::to_string_pretty(&failures)
72        } else {
73            serde_json::to_string(&failures)
74        }
75        .map_err(std::io::Error::other)?;
76
77        writeln!(writer, "{}", json)
78    }
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84    use crate::analyzer::hadolint::types::CheckFailure;
85
86    #[test]
87    fn test_json_output() {
88        let mut result = LintResult::new();
89        result.failures.push(CheckFailure::new(
90            "DL3008",
91            Severity::Warning,
92            "Pin versions in apt get install",
93            5,
94        ));
95
96        let formatter = JsonFormatter::new();
97        let output = formatter.format_to_string(&result, "Dockerfile");
98
99        assert!(output.contains("DL3008"));
100        assert!(output.contains("warning"));
101        assert!(output.contains("Pin versions"));
102    }
103}