use std::fmt;
use crate::Resource;
use crate::validation::blocker::Blocker;
use crate::validation::context::ExternalValidationMode;
use crate::validation::options::TopologyCompleteness;
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Severity {
Warning,
Error,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct ValidationIssue {
pub severity: Severity,
pub rule: &'static str,
pub subject: Resource,
pub property: Option<&'static str>,
pub message: String,
pub hint: Option<Hint>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Hint {
SuggestedTerm {
table: &'static str,
iri: &'static str,
label: &'static str,
},
UrlPattern {
expected: &'static str,
},
PreferredAlias {
canonical: &'static str,
},
Note(&'static str),
}
impl ValidationIssue {
pub(crate) fn error(
rule: &'static str,
subject: Resource,
property: Option<&'static str>,
message: impl Into<String>,
) -> Self {
Self {
severity: Severity::Error,
rule,
subject,
property,
message: message.into(),
hint: None,
}
}
pub(crate) fn warning(
rule: &'static str,
subject: Resource,
property: Option<&'static str>,
message: impl Into<String>,
) -> Self {
Self {
severity: Severity::Warning,
rule,
subject,
property,
message: message.into(),
hint: None,
}
}
#[allow(dead_code)]
pub(crate) fn with_hint(mut self, hint: Hint) -> Self {
self.hint = Some(hint);
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CoverageKind {
OntologyKnownTermsOnly,
LocalReferencesOnly,
LexicalShapeOnly,
PolicyDefaultUndecided,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum NotAppliedReason {
MachineUncheckable,
Deferred(Blocker),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct PartialApplication {
pub rule: &'static str,
pub blocker: Blocker,
pub coverage_kind: CoverageKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct NotApplied {
pub rule: &'static str,
pub reason: NotAppliedReason,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct RuleCoverage {
pub fully_applied: Vec<&'static str>,
pub partially_applied: Vec<PartialApplication>,
pub not_applied: Vec<NotApplied>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct AppliedOptions {
pub topology_completeness: TopologyCompleteness,
pub external_mode: ExternalValidationMode,
pub document_resolvers: usize,
pub content_resolvers: usize,
pub overridden_rules: Vec<(&'static str, crate::validation::options::RuleOverride)>,
pub severity_floor: Option<Severity>,
pub severity_ceiling: Option<Severity>,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct ValidationReport {
pub(crate) issues: Vec<ValidationIssue>,
pub(crate) coverage: RuleCoverage,
pub(crate) options_summary: AppliedOptions,
}
impl ValidationReport {
pub fn issues(&self) -> &[ValidationIssue] {
&self.issues
}
pub fn coverage(&self) -> &RuleCoverage {
&self.coverage
}
pub fn options_summary(&self) -> &AppliedOptions {
&self.options_summary
}
pub fn has_errors(&self) -> bool {
self.issues
.iter()
.any(|issue| issue.severity == Severity::Error)
}
pub fn is_valid(&self) -> bool {
!self.has_errors()
}
pub fn errors(&self) -> impl Iterator<Item = &ValidationIssue> {
self.issues
.iter()
.filter(|issue| issue.severity == Severity::Error)
}
pub fn warnings(&self) -> impl Iterator<Item = &ValidationIssue> {
self.issues
.iter()
.filter(|issue| issue.severity == Severity::Warning)
}
}
impl fmt::Display for ValidationReport {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
let error_count = self.errors().count();
let warning_count = self.warnings().count();
write!(
formatter,
"SBOL validation failed with {error_count} errors and {warning_count} warnings"
)?;
for issue in self.errors().take(5) {
write!(formatter, "\n{}: {}", issue.rule, issue.message)?;
}
Ok(())
}
}
impl std::error::Error for ValidationReport {}