Skip to main content

made_core/entities/
validation.rs

1//! Validation outcome of a proposal — generic replacement for the
2//! SWE-specific `CheckSuite` / `PolicyResult` / `LintResult` / `DryRunResult`
3//! of the original service.
4//!
5//! MADE does not know what a validator checks. A validator
6//! is any adapter that, given a proposal, returns a [`ValidatorReport`]
7//! (pass/fail + opaque details). A [`ValidationOutcome`] aggregates all
8//! reports for a single proposal and carries the final [`Score`].
9
10use serde::{Deserialize, Serialize};
11
12use crate::entities::ValidatorReport;
13use crate::value_objects::Score;
14
15/// Aggregate validation outcome for one proposal.
16#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
17pub struct ValidationOutcome {
18    score: Score,
19    reports: Vec<ValidatorReport>,
20}
21
22impl ValidationOutcome {
23    #[must_use]
24    pub fn new(score: Score, reports: Vec<ValidatorReport>) -> Self {
25        Self { score, reports }
26    }
27
28    #[must_use]
29    pub fn score(&self) -> Score {
30        self.score
31    }
32    #[must_use]
33    pub fn reports(&self) -> &[ValidatorReport] {
34        &self.reports
35    }
36
37    /// Convenience: true iff all reports passed. Independent of score.
38    #[must_use]
39    pub fn all_passed(&self) -> bool {
40        self.reports.iter().all(ValidatorReport::passed)
41    }
42
43    /// Convenience: number of failing reports.
44    #[must_use]
45    pub fn failures(&self) -> usize {
46        self.reports.iter().filter(|r| !r.passed()).count()
47    }
48}
49
50#[cfg(test)]
51mod tests {
52    use super::*;
53    use crate::value_objects::Attributes;
54
55    fn report(kind: &str, passed: bool) -> ValidatorReport {
56        ValidatorReport::new(kind, passed, "summary", Attributes::empty()).unwrap()
57    }
58
59    #[test]
60    fn outcome_aggregates_reports() {
61        let o = ValidationOutcome::new(
62            Score::new(0.75).unwrap(),
63            vec![report("lint", true), report("policy", false)],
64        );
65        assert_eq!(o.reports().len(), 2);
66        assert!(!o.all_passed());
67        assert_eq!(o.failures(), 1);
68        assert_eq!(o.score().get(), 0.75);
69    }
70
71    #[test]
72    fn outcome_without_failures_reports_all_passed() {
73        let o = ValidationOutcome::new(Score::MAX, vec![report("a", true), report("b", true)]);
74        assert!(o.all_passed());
75        assert_eq!(o.failures(), 0);
76    }
77
78    #[test]
79    fn outcome_with_no_reports_trivially_passes() {
80        let o = ValidationOutcome::new(Score::MIN, vec![]);
81        assert!(o.all_passed());
82        assert_eq!(o.failures(), 0);
83    }
84}