#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use crate::schema::Ns;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ResultKind {
FailedAssert,
SuccessfulReport,
}
impl ResultKind {
#[must_use]
pub const fn as_str(self) -> &'static str {
match self {
ResultKind::FailedAssert => "failed-assert",
ResultKind::SuccessfulReport => "successful-report",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DiagnosticResult {
pub id: String,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct PropertyResult {
pub id: String,
pub role: Option<String>,
pub scheme: Option<String>,
pub text: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct AssertionResult {
pub kind: ResultKind,
pub test: String,
pub location: String,
pub text: String,
pub id: Option<String>,
pub role: Option<String>,
pub flag: Option<String>,
pub see: Option<String>,
pub icon: Option<String>,
pub fpi: Option<String>,
pub diagnostics: Vec<DiagnosticResult>,
pub properties: Vec<PropertyResult>,
}
impl AssertionResult {
#[must_use]
pub fn is_failure(&self) -> bool {
self.kind == ResultKind::FailedAssert
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct FiredRule {
pub id: Option<String>,
pub context: String,
pub role: Option<String>,
pub flag: Option<String>,
pub location: String,
pub assertions: Vec<AssertionResult>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct ActivePattern {
pub id: Option<String>,
pub name: Option<String>,
pub documents: Option<String>,
pub rules: Vec<FiredRule>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Report {
pub title: Option<String>,
pub phase: Option<String>,
pub schema_version: Option<String>,
pub namespaces: Vec<Ns>,
pub patterns: Vec<ActivePattern>,
}
impl Report {
#[must_use]
pub fn is_valid(&self) -> bool {
self.count_failures() == 0
}
pub fn assertions(&self) -> impl Iterator<Item = &AssertionResult> {
self.patterns
.iter()
.flat_map(|pattern| pattern.rules.iter())
.flat_map(|rule| rule.assertions.iter())
}
pub fn failures(&self) -> impl Iterator<Item = &AssertionResult> {
self.assertions()
.filter(|a| a.kind == ResultKind::FailedAssert)
}
pub fn reports(&self) -> impl Iterator<Item = &AssertionResult> {
self.assertions()
.filter(|a| a.kind == ResultKind::SuccessfulReport)
}
#[must_use]
pub fn count_failures(&self) -> usize {
self.failures().count()
}
pub fn with_flag<'a>(&'a self, flag: &'a str) -> impl Iterator<Item = &'a AssertionResult> {
self.assertions()
.filter(move |a| a.flag.as_deref() == Some(flag))
}
pub fn fired_rules(&self) -> impl Iterator<Item = &FiredRule> {
self.patterns.iter().flat_map(|pattern| pattern.rules.iter())
}
#[must_use]
pub fn count_fired_rules(&self) -> usize {
self.fired_rules().count()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn finding(kind: ResultKind, flag: Option<&str>) -> AssertionResult {
AssertionResult {
kind,
test: "t".into(),
location: "/".into(),
text: "m".into(),
id: None,
role: None,
flag: flag.map(ToString::to_string),
see: None,
icon: None,
fpi: None,
diagnostics: vec![],
properties: vec![],
}
}
fn report_with(assertions: Vec<AssertionResult>) -> Report {
Report {
patterns: vec![ActivePattern {
id: None,
name: None,
documents: None,
rules: vec![FiredRule {
id: None,
context: "a".into(),
role: None,
flag: None,
location: "/".into(),
assertions,
}],
}],
..Report::default()
}
}
#[test]
fn an_empty_report_is_valid() {
assert!(Report::default().is_valid());
assert_eq!(Report::default().count_failures(), 0);
}
#[test]
fn successful_reports_do_not_make_a_document_invalid() {
let report = report_with(vec![finding(ResultKind::SuccessfulReport, None)]);
assert!(report.is_valid());
assert_eq!(report.reports().count(), 1);
assert_eq!(report.failures().count(), 0);
}
#[test]
fn failed_asserts_do() {
let report = report_with(vec![finding(ResultKind::FailedAssert, None)]);
assert!(!report.is_valid());
assert_eq!(report.count_failures(), 1);
}
#[test]
fn findings_can_be_filtered_by_flag() {
let report = report_with(vec![
finding(ResultKind::FailedAssert, Some("error")),
finding(ResultKind::FailedAssert, Some("warning")),
]);
assert_eq!(report.with_flag("error").count(), 1);
assert_eq!(report.with_flag("warning").count(), 1);
assert_eq!(report.with_flag("info").count(), 0);
}
#[test]
fn fired_rules_are_counted_separately_from_findings() {
let report = report_with(vec![]);
assert_eq!(report.count_fired_rules(), 1);
assert_eq!(report.assertions().count(), 0);
}
}