use serde::Serialize;
use std::fmt;
#[derive(Clone, Debug, Default, Serialize)]
pub struct ValidationReport {
pub issues: Vec<ValidationIssue>,
}
impl ValidationReport {
pub fn new() -> Self {
Self { issues: Vec::new() }
}
pub fn add(&mut self, issue: ValidationIssue) {
self.issues.push(issue);
}
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 is_ok(&self) -> bool {
self.error_count() == 0
}
pub fn is_clean(&self) -> bool {
self.issues.is_empty()
}
pub fn is_ok_strict(&self) -> bool {
self.issues.is_empty()
}
pub fn as_json(&self) -> impl Serialize + '_ {
ValidationReportJson {
error_count: self.error_count(),
warning_count: self.warning_count(),
report: self,
}
}
}
#[derive(Serialize)]
struct ValidationReportJson<'a> {
error_count: usize,
warning_count: usize,
#[serde(flatten)]
report: &'a ValidationReport,
}
impl fmt::Display for ValidationReport {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.issues.is_empty() {
return writeln!(f, "Validation passed: no issues found");
}
writeln!(
f,
"Validation completed with {} error(s) and {} warning(s):",
self.error_count(),
self.warning_count()
)?;
writeln!(f)?;
for issue in &self.issues {
writeln!(f, " {}", issue)?;
}
Ok(())
}
}
#[derive(Clone, Debug, Serialize)]
pub struct ValidationIssue {
pub severity: Severity,
pub code: IssueCode,
pub message: String,
pub context: IssueContext,
}
impl ValidationIssue {
pub fn new(
severity: Severity,
code: IssueCode,
message: impl Into<String>,
context: IssueContext,
) -> Self {
Self {
severity,
code,
message: message.into(),
context,
}
}
pub fn error(code: IssueCode, message: impl Into<String>, context: IssueContext) -> Self {
Self::new(Severity::Error, code, message, context)
}
pub fn warning(code: IssueCode, message: impl Into<String>, context: IssueContext) -> Self {
Self::new(Severity::Warning, code, message, context)
}
}
impl fmt::Display for ValidationIssue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let severity = match self.severity {
Severity::Error => "ERROR",
Severity::Warning => "WARN ",
};
write!(
f,
"[{}] {:?} in {}: {}",
severity, self.code, self.context, self.message
)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum Severity {
Warning,
Error,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize)]
#[serde(rename_all = "snake_case")]
pub enum IssueCode {
DuplicateImageId,
DuplicateAnnotationId,
DuplicateCategoryId,
MissingImageRef,
MissingCategoryRef,
InvalidImageDimensions,
EmptyFileName,
EmptyCategoryName,
DuplicateCategoryName,
BBoxNotFinite,
InvalidBBoxOrdering,
BBoxOutOfBounds,
InvalidBBoxArea,
}
#[derive(Clone, Debug, Serialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum IssueContext {
Dataset,
Image { id: u64 },
Annotation { id: u64 },
Category { id: u64 },
}
impl fmt::Display for IssueContext {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
IssueContext::Dataset => write!(f, "dataset"),
IssueContext::Image { id } => write!(f, "image {}", id),
IssueContext::Annotation { id } => write!(f, "annotation {}", id),
IssueContext::Category { id } => write!(f, "category {}", id),
}
}
}