term_guard/core/
result.rs1use super::Level;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ValidationMetrics {
10 pub total_checks: usize,
12 pub passed_checks: usize,
14 pub failed_checks: usize,
16 pub skipped_checks: usize,
18 pub execution_time_ms: u64,
20 #[serde(skip_serializing_if = "HashMap::is_empty")]
22 pub custom_metrics: HashMap<String, f64>,
23}
24
25impl ValidationMetrics {
26 pub fn new() -> Self {
28 Self {
29 total_checks: 0,
30 passed_checks: 0,
31 failed_checks: 0,
32 skipped_checks: 0,
33 execution_time_ms: 0,
34 custom_metrics: HashMap::new(),
35 }
36 }
37
38 pub fn success_rate(&self) -> f64 {
40 if self.total_checks == 0 {
41 100.0
42 } else {
43 (self.passed_checks as f64 / self.total_checks as f64) * 100.0
44 }
45 }
46}
47
48impl Default for ValidationMetrics {
49 fn default() -> Self {
50 Self::new()
51 }
52}
53
54#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ValidationIssue {
57 pub check_name: String,
59 pub constraint_name: String,
61 pub level: Level,
63 pub message: String,
65 #[serde(skip_serializing_if = "Option::is_none")]
67 pub metric: Option<f64>,
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ValidationReport {
73 pub suite_name: String,
75 pub timestamp: String,
77 pub metrics: ValidationMetrics,
79 pub issues: Vec<ValidationIssue>,
81}
82
83impl ValidationReport {
84 pub fn new(suite_name: impl Into<String>) -> Self {
86 Self {
87 suite_name: suite_name.into(),
88 timestamp: chrono::Utc::now().to_rfc3339(),
89 metrics: ValidationMetrics::new(),
90 issues: Vec::new(),
91 }
92 }
93
94 pub fn add_issue(&mut self, issue: ValidationIssue) {
96 self.issues.push(issue);
97 }
98
99 pub fn has_errors(&self) -> bool {
101 self.issues.iter().any(|issue| issue.level == Level::Error)
102 }
103
104 pub fn has_warnings(&self) -> bool {
106 self.issues
107 .iter()
108 .any(|issue| issue.level == Level::Warning)
109 }
110
111 pub fn issues_by_level(&self, level: Level) -> Vec<&ValidationIssue> {
113 self.issues
114 .iter()
115 .filter(|issue| issue.level == level)
116 .collect()
117 }
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(tag = "status", rename_all = "lowercase")]
123pub enum ValidationResult {
124 Success {
126 metrics: ValidationMetrics,
128 report: ValidationReport,
130 },
131 Failure {
133 report: ValidationReport,
135 },
136}
137
138impl ValidationResult {
139 pub fn success(metrics: ValidationMetrics, report: ValidationReport) -> Self {
141 ValidationResult::Success { metrics, report }
142 }
143
144 pub fn failure(report: ValidationReport) -> Self {
146 ValidationResult::Failure { report }
147 }
148
149 pub fn is_success(&self) -> bool {
151 matches!(self, ValidationResult::Success { .. })
152 }
153
154 pub fn is_failure(&self) -> bool {
156 matches!(self, ValidationResult::Failure { .. })
157 }
158
159 pub fn report(&self) -> &ValidationReport {
161 match self {
162 ValidationResult::Success { report, .. } => report,
163 ValidationResult::Failure { report } => report,
164 }
165 }
166
167 pub fn metrics(&self) -> Option<&ValidationMetrics> {
169 match self {
170 ValidationResult::Success { metrics, .. } => Some(metrics),
171 ValidationResult::Failure { .. } => None,
172 }
173 }
174
175 pub fn to_json(&self) -> crate::prelude::Result<String> {
191 use crate::formatters::{JsonFormatter, ResultFormatter};
192 JsonFormatter::new().format(self)
193 }
194
195 pub fn to_json_pretty(&self) -> crate::prelude::Result<String> {
208 use crate::formatters::{JsonFormatter, ResultFormatter};
209 JsonFormatter::new().with_pretty(true).format(self)
210 }
211
212 pub fn to_human(&self) -> crate::prelude::Result<String> {
228 use crate::formatters::{HumanFormatter, ResultFormatter};
229 HumanFormatter::new().format(self)
230 }
231
232 pub fn to_markdown(&self) -> crate::prelude::Result<String> {
248 use crate::formatters::{MarkdownFormatter, ResultFormatter};
249 MarkdownFormatter::new().format(self)
250 }
251
252 pub fn format_with<F: crate::formatters::ResultFormatter>(
273 &self,
274 formatter: &F,
275 ) -> crate::prelude::Result<String> {
276 formatter.format(self)
277 }
278}
279
280#[cfg(test)]
281mod tests {
282 use super::*;
283
284 #[test]
285 fn test_validation_metrics_success_rate() {
286 let mut metrics = ValidationMetrics::new();
287 assert_eq!(metrics.success_rate(), 100.0);
288
289 metrics.total_checks = 10;
290 metrics.passed_checks = 8;
291 assert_eq!(metrics.success_rate(), 80.0);
292 }
293
294 #[test]
295 fn test_validation_report() {
296 let mut report = ValidationReport::new("test_suite");
297 assert!(!report.has_errors());
298 assert!(!report.has_warnings());
299
300 report.add_issue(ValidationIssue {
301 check_name: "test_check".to_string(),
302 constraint_name: "test_constraint".to_string(),
303 level: Level::Error,
304 message: "Test error".to_string(),
305 metric: Some(0.5),
306 });
307
308 assert!(report.has_errors());
309 assert_eq!(report.issues_by_level(Level::Error).len(), 1);
310 }
311
312 #[test]
313 fn test_validation_result() {
314 let metrics = ValidationMetrics::new();
315 let report = ValidationReport::new("test_suite");
316
317 let success_result = ValidationResult::success(metrics, report.clone());
318 assert!(success_result.is_success());
319 assert!(!success_result.is_failure());
320 assert!(success_result.metrics().is_some());
321
322 let failure_result = ValidationResult::failure(report);
323 assert!(!failure_result.is_success());
324 assert!(failure_result.is_failure());
325 assert!(failure_result.metrics().is_none());
326 }
327
328 #[test]
329 fn test_validation_result_formatting() {
330 let metrics = ValidationMetrics::new();
331 let mut report = ValidationReport::new("test_suite");
332
333 report.add_issue(ValidationIssue {
335 check_name: "test_check".to_string(),
336 constraint_name: "test_constraint".to_string(),
337 level: Level::Warning,
338 message: "Test warning message".to_string(),
339 metric: Some(0.8),
340 });
341
342 let result = ValidationResult::success(metrics, report);
343
344 let json_output = result.to_json().unwrap();
346 assert!(json_output.contains("\"status\": \"success\""));
347 assert!(json_output.contains("test_suite"));
348
349 let pretty_json = result.to_json_pretty().unwrap();
351 assert!(pretty_json.contains("\"status\": \"success\""));
352 assert!(pretty_json.contains("test_suite"));
354
355 let human_output = result.to_human().unwrap();
357 assert!(human_output.contains("Validation PASSED"));
358 assert!(human_output.contains("test_suite"));
359
360 let markdown_output = result.to_markdown().unwrap();
362 assert!(markdown_output.contains("## ✅ Validation Report - PASSED"));
363 assert!(markdown_output.contains("test_suite"));
364 }
365}