term_guard/core/
result.rs

1//! Validation result types.
2
3use super::Level;
4use serde::{Deserialize, Serialize};
5use std::collections::HashMap;
6
7/// Metrics collected during validation.
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct ValidationMetrics {
10    /// Total number of checks executed
11    pub total_checks: usize,
12    /// Number of checks that passed
13    pub passed_checks: usize,
14    /// Number of checks that failed
15    pub failed_checks: usize,
16    /// Number of checks that were skipped
17    pub skipped_checks: usize,
18    /// Total execution time in milliseconds
19    pub execution_time_ms: u64,
20    /// Custom metrics collected during validation
21    #[serde(skip_serializing_if = "HashMap::is_empty")]
22    pub custom_metrics: HashMap<String, f64>,
23}
24
25impl ValidationMetrics {
26    /// Creates new validation metrics with all counts set to zero.
27    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    /// Returns the success rate as a percentage (0.0 to 100.0).
39    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/// A detailed validation issue found during checks.
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct ValidationIssue {
57    /// The name of the check that found the issue
58    pub check_name: String,
59    /// The name of the constraint that failed
60    pub constraint_name: String,
61    /// The severity level of the issue
62    pub level: Level,
63    /// A description of the issue
64    pub message: String,
65    /// Optional metric value associated with the issue
66    #[serde(skip_serializing_if = "Option::is_none")]
67    pub metric: Option<f64>,
68}
69
70/// A validation report containing all issues found.
71#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct ValidationReport {
73    /// The name of the validation suite that was run
74    pub suite_name: String,
75    /// Timestamp when the validation was run (ISO 8601 format)
76    pub timestamp: String,
77    /// Overall validation metrics
78    pub metrics: ValidationMetrics,
79    /// List of issues found during validation
80    pub issues: Vec<ValidationIssue>,
81}
82
83impl ValidationReport {
84    /// Creates a new validation report.
85    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    /// Adds an issue to the report.
95    pub fn add_issue(&mut self, issue: ValidationIssue) {
96        self.issues.push(issue);
97    }
98
99    /// Returns true if there are any error-level issues.
100    pub fn has_errors(&self) -> bool {
101        self.issues.iter().any(|issue| issue.level == Level::Error)
102    }
103
104    /// Returns true if there are any warning-level issues.
105    pub fn has_warnings(&self) -> bool {
106        self.issues
107            .iter()
108            .any(|issue| issue.level == Level::Warning)
109    }
110
111    /// Gets all issues of a specific level.
112    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/// The result of running a validation suite.
121#[derive(Debug, Clone, Serialize, Deserialize)]
122#[serde(tag = "status", rename_all = "lowercase")]
123pub enum ValidationResult {
124    /// Validation completed successfully with no critical issues
125    Success {
126        /// Validation metrics
127        metrics: ValidationMetrics,
128        /// Detailed report (may contain warnings or info)
129        report: ValidationReport,
130    },
131    /// Validation failed due to one or more critical issues
132    Failure {
133        /// Detailed report containing the issues
134        report: ValidationReport,
135    },
136}
137
138impl ValidationResult {
139    /// Creates a successful validation result.
140    pub fn success(metrics: ValidationMetrics, report: ValidationReport) -> Self {
141        ValidationResult::Success { metrics, report }
142    }
143
144    /// Creates a failed validation result.
145    pub fn failure(report: ValidationReport) -> Self {
146        ValidationResult::Failure { report }
147    }
148
149    /// Returns true if the validation succeeded.
150    pub fn is_success(&self) -> bool {
151        matches!(self, ValidationResult::Success { .. })
152    }
153
154    /// Returns true if the validation failed.
155    pub fn is_failure(&self) -> bool {
156        matches!(self, ValidationResult::Failure { .. })
157    }
158
159    /// Returns the validation report.
160    pub fn report(&self) -> &ValidationReport {
161        match self {
162            ValidationResult::Success { report, .. } => report,
163            ValidationResult::Failure { report } => report,
164        }
165    }
166
167    /// Returns the validation metrics if available (only for success).
168    pub fn metrics(&self) -> Option<&ValidationMetrics> {
169        match self {
170            ValidationResult::Success { metrics, .. } => Some(metrics),
171            ValidationResult::Failure { .. } => None,
172        }
173    }
174
175    /// Formats the validation result as JSON.
176    ///
177    /// This is a convenience method that uses the `JsonFormatter` to output
178    /// the result as structured JSON.
179    ///
180    /// # Examples
181    ///
182    /// ```rust
183    /// # use term_guard::core::{ValidationResult, ValidationReport, ValidationMetrics};
184    /// # let metrics = ValidationMetrics::new();
185    /// # let report = ValidationReport::new("test");
186    /// # let result = ValidationResult::success(metrics, report);
187    /// let json_output = result.to_json().unwrap();
188    /// println!("{}", json_output);
189    /// ```
190    pub fn to_json(&self) -> crate::prelude::Result<String> {
191        use crate::formatters::{JsonFormatter, ResultFormatter};
192        JsonFormatter::new().format(self)
193    }
194
195    /// Formats the validation result as JSON with pretty printing.
196    ///
197    /// # Examples
198    ///
199    /// ```rust
200    /// # use term_guard::core::{ValidationResult, ValidationReport, ValidationMetrics};
201    /// # let metrics = ValidationMetrics::new();
202    /// # let report = ValidationReport::new("test");
203    /// # let result = ValidationResult::success(metrics, report);
204    /// let pretty_json = result.to_json_pretty().unwrap();
205    /// println!("{}", pretty_json);
206    /// ```
207    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    /// Formats the validation result in a human-readable format.
213    ///
214    /// This is a convenience method that uses the `HumanFormatter` to output
215    /// the result in a format suitable for console display.
216    ///
217    /// # Examples
218    ///
219    /// ```rust
220    /// # use term_guard::core::{ValidationResult, ValidationReport, ValidationMetrics};
221    /// # let metrics = ValidationMetrics::new();
222    /// # let report = ValidationReport::new("test");
223    /// # let result = ValidationResult::success(metrics, report);
224    /// let human_output = result.to_human().unwrap();
225    /// println!("{}", human_output);
226    /// ```
227    pub fn to_human(&self) -> crate::prelude::Result<String> {
228        use crate::formatters::{HumanFormatter, ResultFormatter};
229        HumanFormatter::new().format(self)
230    }
231
232    /// Formats the validation result as Markdown.
233    ///
234    /// This is a convenience method that uses the `MarkdownFormatter` to output
235    /// the result in Markdown format suitable for documentation.
236    ///
237    /// # Examples
238    ///
239    /// ```rust
240    /// # use term_guard::core::{ValidationResult, ValidationReport, ValidationMetrics};
241    /// # let metrics = ValidationMetrics::new();
242    /// # let report = ValidationReport::new("test");
243    /// # let result = ValidationResult::success(metrics, report);
244    /// let markdown_output = result.to_markdown().unwrap();
245    /// println!("{}", markdown_output);
246    /// ```
247    pub fn to_markdown(&self) -> crate::prelude::Result<String> {
248        use crate::formatters::{MarkdownFormatter, ResultFormatter};
249        MarkdownFormatter::new().format(self)
250    }
251
252    /// Formats the validation result using a custom formatter.
253    ///
254    /// # Arguments
255    ///
256    /// * `formatter` - The formatter to use
257    ///
258    /// # Examples
259    ///
260    /// ```rust
261    /// # use term_guard::core::{ValidationResult, ValidationReport, ValidationMetrics};
262    /// use term_guard::formatters::{ResultFormatter, HumanFormatter, FormatterConfig};
263    /// # let metrics = ValidationMetrics::new();
264    /// # let report = ValidationReport::new("test");
265    /// # let result = ValidationResult::success(metrics, report);
266    ///
267    /// let config = FormatterConfig::minimal();
268    /// let formatter = HumanFormatter::with_config(config);
269    /// let output = result.format_with(&formatter).unwrap();
270    /// println!("{}", output);
271    /// ```
272    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        // Add a test issue
334        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        // Test JSON formatting
345        let json_output = result.to_json().unwrap();
346        assert!(json_output.contains("\"status\": \"success\""));
347        assert!(json_output.contains("test_suite"));
348
349        // Test pretty JSON formatting
350        let pretty_json = result.to_json_pretty().unwrap();
351        assert!(pretty_json.contains("\"status\": \"success\""));
352        // Pretty JSON should contain the same content
353        assert!(pretty_json.contains("test_suite"));
354
355        // Test human formatting
356        let human_output = result.to_human().unwrap();
357        assert!(human_output.contains("Validation PASSED"));
358        assert!(human_output.contains("test_suite"));
359
360        // Test markdown formatting
361        let markdown_output = result.to_markdown().unwrap();
362        assert!(markdown_output.contains("## ✅ Validation Report - PASSED"));
363        assert!(markdown_output.contains("test_suite"));
364    }
365}