Skip to main content

ito_core/validate/
report.rs

1//! Report builder for validation runs.
2//!
3//! This module provides the [`ReportBuilder`] to accumulate issues found during
4//! a validation pass and compile them into a final [`ValidationReport`].
5//!
6//! It handles the logic of aggregating issues and determining the overall
7//! success/failure status based on the "strict" mode setting.
8
9use super::{ValidationIssue, ValidationReport};
10
11#[derive(Debug, Default)]
12/// A stateful builder for collecting validation issues.
13///
14/// Use this during a validation pass to accumulate issues as they are found.
15/// Call `finish()` at the end to generate the final report.
16pub struct ReportBuilder {
17    strict: bool,
18    issues: Vec<ValidationIssue>,
19}
20
21impl ReportBuilder {
22    /// Create a new builder with the given strictness setting.
23    ///
24    /// If `strict` is `true`, the presence of any `WARNING` issues will cause
25    /// the final report to be marked as failed. If `false`, only `ERROR` issues
26    /// cause failure.
27    pub fn new(strict: bool) -> Self {
28        Self {
29            strict,
30            issues: Vec::new(),
31        }
32    }
33
34    /// Add a single issue to the report.
35    pub fn push(&mut self, issue: ValidationIssue) {
36        self.issues.push(issue);
37    }
38
39    /// Extend this builder with multiple issues.
40    ///
41    /// Useful when merging results from sub-validations.
42    pub fn extend<I>(&mut self, issues: I)
43    where
44        I: IntoIterator<Item = ValidationIssue>,
45    {
46        self.issues.extend(issues);
47    }
48
49    /// Finish building and compute the final [`ValidationReport`].
50    ///
51    /// This consumes the builder, calculating summary statistics and the
52    /// final `valid` boolean based on the accumulated issues and strictness.
53    pub fn finish(self) -> ValidationReport {
54        ValidationReport::new(self.issues, self.strict)
55    }
56}
57
58/// Convenience constructor for a [`ReportBuilder`].
59///
60/// Equivalent to `ReportBuilder::new(strict)`.
61pub fn report(strict: bool) -> ReportBuilder {
62    ReportBuilder::new(strict)
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    fn issue(level: &str, path: &str, message: &str) -> ValidationIssue {
70        ValidationIssue {
71            level: level.to_string(),
72            path: path.to_string(),
73            message: message.to_string(),
74            line: None,
75            column: None,
76            rule_id: None,
77            metadata: None,
78        }
79    }
80
81    #[test]
82    fn finish_non_strict_only_fails_on_errors() {
83        let mut builder = ReportBuilder::new(false);
84        builder.push(issue("WARNING", "spec.md", "brief purpose"));
85
86        let report = builder.finish();
87        assert!(report.valid);
88        assert_eq!(report.summary.errors, 0);
89        assert_eq!(report.summary.warnings, 1);
90    }
91
92    #[test]
93    fn finish_strict_fails_on_warnings() {
94        let mut builder = report(true);
95        builder.push(issue("WARNING", "spec.md", "brief purpose"));
96
97        let result = builder.finish();
98        assert!(!result.valid);
99        assert_eq!(result.summary.errors, 0);
100        assert_eq!(result.summary.warnings, 1);
101    }
102
103    #[test]
104    fn extend_collects_multiple_issues() {
105        let mut builder = report(false);
106        builder.extend(vec![
107            issue("ERROR", "a.md", "a"),
108            issue("INFO", "b.md", "b"),
109            issue("WARNING", "c.md", "c"),
110        ]);
111
112        let result = builder.finish();
113        assert!(!result.valid);
114        assert_eq!(result.issues.len(), 3);
115        assert_eq!(result.summary.errors, 1);
116        assert_eq!(result.summary.warnings, 1);
117        assert_eq!(result.summary.info, 1);
118    }
119}