rust_diff_analyzer/output/
json.rs

1// SPDX-FileCopyrightText: 2025 RAprogramm <andrey.rozanov.vl@gmail.com>
2// SPDX-License-Identifier: MIT
3
4use masterror::AppError;
5use serde::Serialize;
6
7use super::formatter::Formatter;
8use crate::{config::Config, error::OutputError, types::AnalysisResult};
9
10/// Formatter for JSON output
11pub struct JsonFormatter;
12
13#[derive(Serialize)]
14struct JsonOutput<'a> {
15    summary: &'a crate::types::Summary,
16    changes: Vec<JsonChange<'a>>,
17}
18
19#[derive(Serialize)]
20struct JsonChange<'a> {
21    file: String,
22    unit: &'a str,
23    kind: &'a str,
24    visibility: &'a str,
25    classification: &'a str,
26    lines_added: usize,
27    lines_removed: usize,
28}
29
30impl Formatter for JsonFormatter {
31    fn format(&self, result: &AnalysisResult, config: &Config) -> Result<String, AppError> {
32        let changes: Vec<JsonChange> = if config.output.include_details {
33            result
34                .changes
35                .iter()
36                .map(|c| JsonChange {
37                    file: c.file_path.to_string_lossy().to_string(),
38                    unit: &c.unit.name,
39                    kind: c.unit.kind.as_str(),
40                    visibility: c.unit.visibility.as_str(),
41                    classification: c.classification.as_str(),
42                    lines_added: c.lines_added,
43                    lines_removed: c.lines_removed,
44                })
45                .collect()
46        } else {
47            vec![]
48        };
49
50        let output = JsonOutput {
51            summary: &result.summary,
52            changes,
53        };
54
55        serde_json::to_string_pretty(&output).map_err(|e| {
56            AppError::from(OutputError {
57                format: "json".to_string(),
58                message: e.to_string(),
59            })
60        })
61    }
62}
63
64#[cfg(test)]
65mod tests {
66    use super::*;
67    use crate::types::{AnalysisScope, Summary};
68
69    #[test]
70    fn test_json_format() {
71        let result = AnalysisResult::new(
72            vec![],
73            Summary {
74                prod_functions: 3,
75                prod_structs: 1,
76                prod_other: 0,
77                test_units: 5,
78                prod_lines_added: 30,
79                prod_lines_removed: 10,
80                test_lines_added: 50,
81                test_lines_removed: 20,
82                weighted_score: 15,
83                exceeds_limit: false,
84            },
85            AnalysisScope::new(),
86        );
87
88        let config = Config::default();
89        let output = JsonFormatter
90            .format(&result, &config)
91            .expect("format should succeed");
92
93        assert!(output.contains("\"prod_functions\": 3"));
94        assert!(output.contains("\"weighted_score\": 15"));
95    }
96}