use std::collections::{BTreeMap, BTreeSet};
use std::fmt;
use super::{ConformanceLevel, ConformanceReport, ConformanceSuite};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ConformanceCheckResult {
Passed,
Unsupported(String),
Failed(String),
}
impl ConformanceCheckResult {
pub const fn passed() -> Self {
Self::Passed
}
pub fn unsupported(reason: impl Into<String>) -> Self {
Self::Unsupported(reason.into())
}
pub fn failed(detail: impl Into<String>) -> Self {
Self::Failed(detail.into())
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConformanceCheckReport {
pub level: ConformanceLevel,
pub invariant: String,
pub result: ConformanceCheckResult,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ConformanceFailure {
pub level: ConformanceLevel,
pub invariant: String,
pub detail: String,
}
impl fmt::Display for ConformanceFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:?} conformance failed for {}: {}",
self.level, self.invariant, self.detail
)
}
}
impl std::error::Error for ConformanceFailure {}
pub fn conformance() -> ConformanceRunner {
ConformanceSuite::new().runner()
}
pub struct ConformanceRunner {
suite: ConformanceSuite,
checks: Vec<ConformanceCheck>,
unsupported: BTreeMap<ConformanceLevel, Vec<String>>,
}
impl ConformanceRunner {
pub fn new(suite: ConformanceSuite) -> Self {
Self {
suite,
checks: Vec::new(),
unsupported: BTreeMap::new(),
}
}
pub fn require(mut self, level: ConformanceLevel) -> Self {
self.suite = self.suite.require(level);
self
}
pub fn check(
mut self,
level: ConformanceLevel,
invariant: impl Into<String>,
run: impl FnMut() -> ConformanceCheckResult + 'static,
) -> Self {
self.suite = self.suite.require(level);
self.checks.push(ConformanceCheck {
level,
invariant: invariant.into(),
run: Box::new(run),
});
self
}
pub fn unsupported(mut self, level: ConformanceLevel, reason: impl Into<String>) -> Self {
self.suite = self.suite.require(level);
self.unsupported
.entry(level)
.or_default()
.push(reason.into());
self
}
pub fn run(mut self) -> Result<ConformanceReport, ConformanceFailure> {
let mut report = ConformanceReport::new();
let mut seen = BTreeSet::new();
let mut unsupported = self.unsupported;
for check in &mut self.checks {
seen.insert(check.level);
let result = (check.run)();
match &result {
ConformanceCheckResult::Passed => {}
ConformanceCheckResult::Unsupported(reason) => {
unsupported
.entry(check.level)
.or_default()
.push(format!("{}: {reason}", check.invariant));
}
ConformanceCheckResult::Failed(detail) => {
return Err(ConformanceFailure {
level: check.level,
invariant: check.invariant.clone(),
detail: detail.clone(),
});
}
}
report = report.record_check(ConformanceCheckReport {
level: check.level,
invariant: check.invariant.clone(),
result,
});
}
for level in self.suite.required_levels() {
if let Some(reasons) = unsupported.remove(level) {
for reason in reasons {
report = report.unsupported_with_reason(*level, reason);
}
} else if seen.contains(level) {
report = report.support(*level);
} else {
report = report.unsupported_with_reason(
*level,
"no conformance check registered for required level",
);
}
}
Ok(report)
}
}
struct ConformanceCheck {
level: ConformanceLevel,
invariant: String,
run: Box<dyn FnMut() -> ConformanceCheckResult>,
}