clnrm_core/reporting/
json.rs1use crate::error::{CleanroomError, Result};
6use crate::validation::ValidationReport;
7use serde::Serialize;
8use std::path::Path;
9
10#[derive(Debug, Serialize)]
12pub struct JsonReport {
13 pub passed: bool,
15 pub total_passes: usize,
17 pub total_failures: usize,
19 pub passes: Vec<String>,
21 pub failures: Vec<FailureDetail>,
23}
24
25#[derive(Debug, Serialize)]
27pub struct FailureDetail {
28 pub name: String,
30 pub error: String,
32}
33
34pub struct JsonReporter;
36
37impl JsonReporter {
38 pub fn write(path: &Path, report: &ValidationReport) -> Result<()> {
52 let json_report = Self::convert_report(report);
53 let json_str = Self::serialize(&json_report)?;
54 Self::write_file(path, &json_str)
55 }
56
57 fn convert_report(report: &ValidationReport) -> JsonReport {
59 JsonReport {
60 passed: report.is_success(),
61 total_passes: report.passes().len(),
62 total_failures: report.failures().len(),
63 passes: report.passes().to_vec(),
64 failures: report
65 .failures()
66 .iter()
67 .map(|(name, error)| FailureDetail {
68 name: name.clone(),
69 error: error.clone(),
70 })
71 .collect(),
72 }
73 }
74
75 fn serialize(json_report: &JsonReport) -> Result<String> {
77 serde_json::to_string_pretty(json_report).map_err(|e| {
78 CleanroomError::serialization_error(format!("JSON serialization failed: {}", e))
79 })
80 }
81
82 fn write_file(path: &Path, content: &str) -> Result<()> {
84 std::fs::write(path, content).map_err(|e| {
85 CleanroomError::report_error(format!("Failed to write JSON report: {}", e))
86 })
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93 use crate::validation::ValidationReport;
94 use tempfile::TempDir;
95
96 #[test]
97 fn test_json_reporter_all_pass() -> Result<()> {
98 let temp_dir = TempDir::new()
100 .map_err(|e| CleanroomError::io_error(format!("Failed to create temp dir: {}", e)))?;
101 let json_path = temp_dir.path().join("report.json");
102
103 let mut report = ValidationReport::new();
104 report.add_pass("test1");
105 report.add_pass("test2");
106
107 JsonReporter::write(&json_path, &report)?;
109
110 let content = std::fs::read_to_string(&json_path)
112 .map_err(|e| CleanroomError::io_error(format!("Failed to read file: {}", e)))?;
113
114 assert!(content.contains(r#""passed": true"#));
115 assert!(content.contains(r#""total_passes": 2"#));
116 assert!(content.contains(r#""total_failures": 0"#));
117 assert!(content.contains(r#""test1""#));
118 assert!(content.contains(r#""test2""#));
119
120 Ok(())
121 }
122
123 #[test]
124 fn test_json_reporter_with_failures() -> Result<()> {
125 let temp_dir = TempDir::new()
127 .map_err(|e| CleanroomError::io_error(format!("Failed to create temp dir: {}", e)))?;
128 let json_path = temp_dir.path().join("report.json");
129
130 let mut report = ValidationReport::new();
131 report.add_pass("test1");
132 report.add_fail("test2", "Expected 2 but got 1".to_string());
133 report.add_fail("test3", "Missing span".to_string());
134
135 JsonReporter::write(&json_path, &report)?;
137
138 let content = std::fs::read_to_string(&json_path)
140 .map_err(|e| CleanroomError::io_error(format!("Failed to read file: {}", e)))?;
141
142 assert!(content.contains(r#""passed": false"#));
143 assert!(content.contains(r#""total_passes": 1"#));
144 assert!(content.contains(r#""total_failures": 2"#));
145 assert!(content.contains(r#""test2""#));
146 assert!(content.contains(r#""Expected 2 but got 1""#));
147 assert!(content.contains(r#""test3""#));
148 assert!(content.contains(r#""Missing span""#));
149
150 Ok(())
151 }
152
153 #[test]
154 fn test_json_reporter_empty_report() -> Result<()> {
155 let temp_dir = TempDir::new()
157 .map_err(|e| CleanroomError::io_error(format!("Failed to create temp dir: {}", e)))?;
158 let json_path = temp_dir.path().join("report.json");
159
160 let report = ValidationReport::new();
161
162 JsonReporter::write(&json_path, &report)?;
164
165 let content = std::fs::read_to_string(&json_path)
167 .map_err(|e| CleanroomError::io_error(format!("Failed to read file: {}", e)))?;
168
169 assert!(content.contains(r#""passed": true"#));
170 assert!(content.contains(r#""total_passes": 0"#));
171 assert!(content.contains(r#""total_failures": 0"#));
172
173 Ok(())
174 }
175
176 #[test]
177 fn test_json_reporter_special_characters() -> Result<()> {
178 let temp_dir = TempDir::new()
180 .map_err(|e| CleanroomError::io_error(format!("Failed to create temp dir: {}", e)))?;
181 let json_path = temp_dir.path().join("report.json");
182
183 let mut report = ValidationReport::new();
184 report.add_fail(
185 "test_with_quotes",
186 r#"Error: "Expected value" not found"#.to_string(),
187 );
188 report.add_fail("test_with_newlines", "Error:\nLine 1\nLine 2".to_string());
189
190 JsonReporter::write(&json_path, &report)?;
192
193 let content = std::fs::read_to_string(&json_path)
195 .map_err(|e| CleanroomError::io_error(format!("Failed to read file: {}", e)))?;
196
197 let parsed: serde_json::Value = serde_json::from_str(&content).map_err(|e| {
199 CleanroomError::serialization_error(format!("Failed to parse JSON: {}", e))
200 })?;
201
202 assert!(parsed.is_object());
203
204 Ok(())
205 }
206}