Skip to main content

oparry_cli/
output.rs

1//! Output formatters for different formats
2
3use oparry_core::{Report, ValidationResult};
4use colored::Colorize;
5
6/// Output formatter trait
7pub trait OutputFormatter {
8    /// Format validation result
9    fn format_result(&self, result: &ValidationResult) -> String;
10    /// Format report
11    fn format_report(&self, report: &Report) -> String;
12}
13
14/// Human-readable output formatter
15pub struct HumanFormatter {
16    show_paths: bool,
17    use_colors: bool,
18}
19
20impl HumanFormatter {
21    /// Create new human formatter
22    pub fn new(show_paths: bool, use_colors: bool) -> Self {
23        Self {
24            show_paths,
25            use_colors,
26        }
27    }
28}
29
30impl OutputFormatter for HumanFormatter {
31    fn format_result(&self, result: &ValidationResult) -> String {
32        let mut output = String::new();
33
34        for issue in &result.issues {
35            let icon = match issue.level {
36                oparry_core::IssueLevel::Error => {
37                    if self.use_colors {
38                        "✗".red()
39                    } else {
40                        "✗".normal()
41                    }
42                }
43                oparry_core::IssueLevel::Warning => {
44                    if self.use_colors {
45                        "⚠".yellow()
46                    } else {
47                        "⚠".normal()
48                    }
49                }
50                oparry_core::IssueLevel::Note => {
51                    if self.use_colors {
52                        "ℹ".blue()
53                    } else {
54                        "ℹ".normal()
55                    }
56                }
57            };
58
59            let level_str = match issue.level {
60                oparry_core::IssueLevel::Error => "error",
61                oparry_core::IssueLevel::Warning => "warning",
62                oparry_core::IssueLevel::Note => "note",
63            };
64
65            output.push_str(&format!("{} ", icon));
66
67            if let Some(ref file) = issue.file {
68                output.push_str(&format!("{} ", file));
69            }
70
71            output.push_str(&format!("{}\n", level_str));
72
73            output.push_str(&format!("  --> {}\n", issue.message));
74
75            if let Some(ref suggestion) = issue.suggestion {
76                output.push_str(&format!("     suggestion: {}\n", suggestion));
77            }
78
79            output.push('\n');
80        }
81
82        // Summary
83        let errors = result.error_count();
84        let warnings = result.warning_count();
85
86        if errors > 0 || warnings > 0 {
87            output.push_str(&format!(
88                "{} {}, {} {}\n",
89                errors.to_string().red().bold(),
90                if errors == 1 { "error" } else { "errors" },
91                warnings.to_string().yellow().bold(),
92                if warnings == 1 { "warning" } else { "warnings" }
93            ));
94        } else {
95            output.push_str(&format!("{} No issues found\n", "✓".green().bold()));
96        }
97
98        output
99    }
100
101    fn format_report(&self, report: &Report) -> String {
102        self.format_result(&report.result)
103    }
104}
105
106/// JSON output formatter
107pub struct JsonFormatter;
108
109impl JsonFormatter {
110    /// Create new JSON formatter
111    pub fn new() -> Self {
112        Self
113    }
114}
115
116impl Default for JsonFormatter {
117    fn default() -> Self {
118        Self::new()
119    }
120}
121
122impl OutputFormatter for JsonFormatter {
123    fn format_result(&self, result: &ValidationResult) -> String {
124        serde_json::to_string_pretty(result).unwrap_or_else(|_| "{}".to_string())
125    }
126
127    fn format_report(&self, report: &Report) -> String {
128        serde_json::to_string_pretty(report).unwrap_or_else(|_| "{}".to_string())
129    }
130}
131
132/// SARIF output formatter
133pub struct SarifFormatter;
134
135impl SarifFormatter {
136    /// Create new SARIF formatter
137    pub fn new() -> Self {
138        Self
139    }
140}
141
142impl Default for SarifFormatter {
143    fn default() -> Self {
144        Self::new()
145    }
146}
147
148impl OutputFormatter for SarifFormatter {
149    fn format_result(&self, result: &ValidationResult) -> String {
150        let report = Report::new(result.clone());
151        self.format_report(&report)
152    }
153
154    fn format_report(&self, report: &Report) -> String {
155        match report.to_sarif() {
156            Ok(sarif) => serde_json::to_string_pretty(&sarif).unwrap_or_else(|_| "{}".to_string()),
157            Err(e) => format!(r#"{{"error": "{}"}}"#, e),
158        }
159    }
160}
161
162#[cfg(test)]
163mod tests {
164    use super::*;
165    use oparry_core::{Issue, IssueLevel};
166
167    #[test]
168    fn test_json_formatter() {
169        let formatter = JsonFormatter::new();
170        let mut result = ValidationResult::new();
171        result.add_issue(Issue::error("test", "test message"));
172
173        let output = formatter.format_result(&result);
174        assert!(output.contains("\"error\""));
175        assert!(output.contains("test message"));
176    }
177}