use std::time::Duration;
use serde::Serialize;
use crate::error::ValidationError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Error,
Warning,
}
#[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,
#[serde(skip)]
pub duration: Duration,
}
impl ValidationReport {
pub fn new() -> Self {
Self {
issues: Vec::new(),
items_checked: 0,
relationships_checked: 0,
duration: Duration::ZERO,
}
}
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 add_error(&mut self, error: ValidationError) {
self.issues.push(ValidationIssue::error(error));
}
pub fn add_warning(&mut self, error: ValidationError) {
self.issues.push(ValidationIssue::warning(error));
}
pub fn add_errors(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
for error in errors {
self.add_error(error);
}
}
pub fn add_warnings(&mut self, errors: impl IntoIterator<Item = ValidationError>) {
for error in errors {
self.add_warning(error);
}
}
}
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 duration(mut self, duration: Duration) -> Self {
self.report.duration = duration;
self
}
pub fn error(mut self, error: ValidationError) -> Self {
self.report.add_error(error);
self
}
pub fn warning(mut self, error: ValidationError) -> Self {
self.report.add_warning(error);
self
}
pub fn errors(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
self.report.add_errors(errors);
self
}
pub fn warnings(mut self, errors: impl IntoIterator<Item = ValidationError>) -> Self {
self.report.add_warnings(errors);
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_error() {
let mut report = ValidationReport::new();
report.add_error(ValidationError::BrokenReference {
from: ItemId::new_unchecked("A"),
to: ItemId::new_unchecked("B"),
location: None,
});
assert!(!report.is_valid());
assert_eq!(report.error_count(), 1);
assert_eq!(report.warning_count(), 0);
}
#[test]
fn test_report_with_warning() {
let mut report = ValidationReport::new();
report.add_warning(ValidationError::OrphanItem {
id: ItemId::new_unchecked("A"),
item_type: crate::model::ItemType::UseCase,
location: None,
});
assert!(report.is_valid());
assert_eq!(report.error_count(), 0);
assert_eq!(report.warning_count(), 1);
}
#[test]
fn test_builder() {
let report = ValidationReportBuilder::new()
.items_checked(10)
.relationships_checked(15)
.error(ValidationError::BrokenReference {
from: ItemId::new_unchecked("A"),
to: ItemId::new_unchecked("B"),
location: None,
})
.build();
assert_eq!(report.items_checked, 10);
assert_eq!(report.relationships_checked, 15);
assert_eq!(report.error_count(), 1);
}
}