clnrm_core/formatting/
json.rs1use crate::error::{CleanroomError, Result};
7use crate::formatting::formatter::{Formatter, FormatterType};
8use crate::formatting::test_result::{TestStatus, TestSuite};
9use serde::Serialize;
10
11#[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#[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#[derive(Debug, Default)]
42pub struct JsonFormatter {
43 pretty: bool,
45}
46
47impl JsonFormatter {
48 pub fn new() -> Self {
50 Self::with_pretty(true)
51 }
52
53 pub fn with_pretty(pretty: bool) -> Self {
55 Self { pretty }
56 }
57
58 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}