use std::collections::HashMap;
use serde::Serialize;
use crate::error::ValidationError;
use crate::model::ItemType;
use super::rule::Severity;
#[derive(Debug, Clone, Serialize)]
pub struct ValidationIssue {
pub severity: Severity,
pub error: ValidationError,
}
impl ValidationIssue {
pub fn error(error: ValidationError) -> Self {
Self {
severity: Severity::Error,
error,
}
}
pub fn warning(error: ValidationError) -> Self {
Self {
severity: Severity::Warning,
error,
}
}
}
#[derive(Debug, Clone, Serialize)]
pub struct ValidationReport {
pub issues: Vec<ValidationIssue>,
pub items_checked: usize,
pub relationships_checked: usize,
pub items_by_type: HashMap<ItemType, usize>,
}
impl ValidationReport {
pub fn new() -> Self {
Self {
issues: Vec::new(),
items_checked: 0,
relationships_checked: 0,
items_by_type: HashMap::new(),
}
}
pub fn is_valid(&self) -> bool {
!self.issues.iter().any(|i| i.severity == Severity::Error)
}
pub fn error_count(&self) -> usize {
self.issues
.iter()
.filter(|i| i.severity == Severity::Error)
.count()
}
pub fn warning_count(&self) -> usize {
self.issues
.iter()
.filter(|i| i.severity == Severity::Warning)
.count()
}
pub fn errors(&self) -> Vec<&ValidationError> {
self.issues
.iter()
.filter(|i| i.severity == Severity::Error)
.map(|i| &i.error)
.collect()
}
pub fn warnings(&self) -> Vec<&ValidationError> {
self.issues
.iter()
.filter(|i| i.severity == Severity::Warning)
.map(|i| &i.error)
.collect()
}
pub fn merge(&mut self, other: ValidationReport) {
let mut merged_issues = other.issues;
merged_issues.append(&mut self.issues);
self.issues = merged_issues;
self.items_checked += other.items_checked;
self.relationships_checked += other.relationships_checked;
if self.items_by_type.is_empty() && !other.items_by_type.is_empty() {
self.items_by_type = other.items_by_type;
}
}
}
impl Default for ValidationReport {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Default)]
pub struct ValidationReportBuilder {
report: ValidationReport,
}
impl ValidationReportBuilder {
pub fn new() -> Self {
Self::default()
}
pub fn items_checked(mut self, count: usize) -> Self {
self.report.items_checked = count;
self
}
pub fn relationships_checked(mut self, count: usize) -> Self {
self.report.relationships_checked = count;
self
}
pub fn items_by_type(mut self, counts: HashMap<ItemType, usize>) -> Self {
self.report.items_by_type = counts;
self
}
pub fn errors(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
for error in errors {
self.report.issues.push(ValidationIssue::error(error));
}
self
}
pub fn warnings(mut self, warnings: impl IntoIterator<Item = ValidationError>) -> Self {
for warning in warnings {
self.report.issues.push(ValidationIssue::warning(warning));
}
self
}
pub fn build(self) -> ValidationReport {
self.report
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::ItemId;
#[test]
fn test_empty_report_is_valid() {
let report = ValidationReport::new();
assert!(report.is_valid());
assert_eq!(report.error_count(), 0);
assert_eq!(report.warning_count(), 0);
}
#[test]
fn test_report_with_errors() {
let report = ValidationReportBuilder::new()
.errors([
ValidationError::BrokenReference {
from: ItemId::new_unchecked("A"),
to: ItemId::new_unchecked("B"),
},
ValidationError::BrokenReference {
from: ItemId::new_unchecked("C"),
to: ItemId::new_unchecked("D"),
},
])
.build();
assert!(!report.is_valid());
assert_eq!(report.error_count(), 2);
assert_eq!(report.warning_count(), 0);
}
#[test]
fn test_report_with_warnings() {
let report = ValidationReportBuilder::new()
.warnings([
ValidationError::BrokenReference {
from: ItemId::new_unchecked("A"),
to: ItemId::new_unchecked("B"),
},
ValidationError::BrokenReference {
from: ItemId::new_unchecked("C"),
to: ItemId::new_unchecked("D"),
},
])
.build();
assert!(report.is_valid());
assert_eq!(report.error_count(), 0);
assert_eq!(report.warning_count(), 2);
}
#[test]
fn test_builder() {
let report = ValidationReportBuilder::new()
.items_checked(10)
.relationships_checked(15)
.errors([ValidationError::BrokenReference {
from: ItemId::new_unchecked("A"),
to: ItemId::new_unchecked("B"),
}])
.build();
assert_eq!(report.items_checked, 10);
assert_eq!(report.relationships_checked, 15);
assert_eq!(report.error_count(), 1);
}
}