clnrm_core/formatting/
json.rs

1//! JSON Formatter
2//!
3//! Generates structured JSON output for test results.
4//! Useful for programmatic consumption and CI/CD integration.
5
6use crate::error::{CleanroomError, Result};
7use crate::formatting::formatter::{Formatter, FormatterType};
8use crate::formatting::test_result::{TestStatus, TestSuite};
9use serde::Serialize;
10
11/// JSON representation of a test result
12#[derive(Debug, Serialize)]
13struct JsonTestResult {
14    name: String,
15    status: String,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    duration_ms: Option<f64>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    error: Option<String>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    stdout: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    stderr: Option<String>,
24}
25
26/// JSON representation of a test suite
27#[derive(Debug, Serialize)]
28struct JsonTestSuite {
29    name: String,
30    success: bool,
31    total: usize,
32    passed: usize,
33    failed: usize,
34    skipped: usize,
35    #[serde(skip_serializing_if = "Option::is_none")]
36    duration_ms: Option<f64>,
37    results: Vec<JsonTestResult>,
38}
39
40/// JSON formatter for test results
41#[derive(Debug, Default)]
42pub struct JsonFormatter {
43    /// Whether to pretty-print JSON output
44    pretty: bool,
45}
46
47impl JsonFormatter {
48    /// Create a new JSON formatter with pretty printing
49    pub fn new() -> Self {
50        Self::with_pretty(true)
51    }
52
53    /// Create a new JSON formatter with optional pretty printing
54    pub fn with_pretty(pretty: bool) -> Self {
55        Self { pretty }
56    }
57
58    /// Convert TestStatus to string
59    fn status_to_string(status: &TestStatus) -> String {
60        match status {
61            TestStatus::Passed => "passed",
62            TestStatus::Failed => "failed",
63            TestStatus::Skipped => "skipped",
64            TestStatus::Unknown => "unknown",
65        }
66        .to_string()
67    }
68}
69
70impl Formatter for JsonFormatter {
71    fn format(&self, suite: &TestSuite) -> Result<String> {
72        let results: Vec<JsonTestResult> = suite
73            .results
74            .iter()
75            .map(|r| JsonTestResult {
76                name: r.name.clone(),
77                status: Self::status_to_string(&r.status),
78                duration_ms: r.duration.map(|d| d.as_secs_f64() * 1000.0),
79                error: r.error.clone(),
80                stdout: r.stdout.clone(),
81                stderr: r.stderr.clone(),
82            })
83            .collect();
84
85        let json_suite = JsonTestSuite {
86            name: suite.name.clone(),
87            success: suite.is_success(),
88            total: suite.total_count(),
89            passed: suite.passed_count(),
90            failed: suite.failed_count(),
91            skipped: suite.skipped_count(),
92            duration_ms: suite.duration.map(|d| d.as_secs_f64() * 1000.0),
93            results,
94        };
95
96        let json_str = if self.pretty {
97            serde_json::to_string_pretty(&json_suite)
98        } else {
99            serde_json::to_string(&json_suite)
100        }
101        .map_err(|e| {
102            CleanroomError::serialization_error(format!("JSON serialization failed: {}", e))
103        })?;
104
105        Ok(json_str)
106    }
107
108    fn name(&self) -> &'static str {
109        "json"
110    }
111
112    fn formatter_type(&self) -> FormatterType {
113        FormatterType::Json
114    }
115}