use serde::{Deserialize, Serialize};
use crate::error::DomainError;
use crate::value_objects::{Attributes, Score};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ValidatorReport {
kind: String,
passed: bool,
summary: String,
details: Attributes,
}
impl ValidatorReport {
pub fn new(
kind: impl Into<String>,
passed: bool,
summary: impl Into<String>,
details: Attributes,
) -> Result<Self, DomainError> {
let kind = kind.into();
if kind.trim().is_empty() {
return Err(DomainError::EmptyField {
field: "validator_report.kind",
});
}
Ok(Self {
kind,
passed,
summary: summary.into(),
details,
})
}
#[must_use]
pub fn kind(&self) -> &str {
&self.kind
}
#[must_use]
pub fn passed(&self) -> bool {
self.passed
}
#[must_use]
pub fn summary(&self) -> &str {
&self.summary
}
#[must_use]
pub fn details(&self) -> &Attributes {
&self.details
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct ValidationOutcome {
score: Score,
reports: Vec<ValidatorReport>,
}
impl ValidationOutcome {
#[must_use]
pub fn new(score: Score, reports: Vec<ValidatorReport>) -> Self {
Self { score, reports }
}
#[must_use]
pub fn score(&self) -> Score {
self.score
}
#[must_use]
pub fn reports(&self) -> &[ValidatorReport] {
&self.reports
}
#[must_use]
pub fn all_passed(&self) -> bool {
self.reports.iter().all(ValidatorReport::passed)
}
#[must_use]
pub fn failures(&self) -> usize {
self.reports.iter().filter(|r| !r.passed()).count()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn report(kind: &str, passed: bool) -> ValidatorReport {
ValidatorReport::new(kind, passed, "summary", Attributes::empty()).unwrap()
}
#[test]
fn empty_kind_is_rejected() {
let err = ValidatorReport::new(" ", true, "", Attributes::empty()).unwrap_err();
assert!(matches!(
err,
DomainError::EmptyField {
field: "validator_report.kind"
}
));
}
#[test]
fn arbitrary_kind_is_accepted() {
for kind in [
"lint",
"policy",
"dry-run",
"clinical-safety",
"sourcing-feasibility",
"fact-check",
] {
assert_eq!(report(kind, true).kind(), kind);
}
}
#[test]
fn outcome_aggregates_reports() {
let o = ValidationOutcome::new(
Score::new(0.75).unwrap(),
vec![report("lint", true), report("policy", false)],
);
assert_eq!(o.reports().len(), 2);
assert!(!o.all_passed());
assert_eq!(o.failures(), 1);
assert_eq!(o.score().get(), 0.75);
}
#[test]
fn outcome_without_failures_reports_all_passed() {
let o = ValidationOutcome::new(Score::MAX, vec![report("a", true), report("b", true)]);
assert!(o.all_passed());
assert_eq!(o.failures(), 0);
}
#[test]
fn outcome_with_no_reports_trivially_passes() {
let o = ValidationOutcome::new(Score::MIN, vec![]);
assert!(o.all_passed());
assert_eq!(o.failures(), 0);
}
}