use serde::{Deserialize, Serialize};
use crate::finding::Finding;
use crate::outcome::{Outcome, Severity};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum NotRun {
CapabilityMissing,
Disabled,
NotApplicable,
Errored,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct RuleRun {
pub rule_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub viewport: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub wcag: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub not_run: Option<NotRun>,
#[serde(default)]
pub findings: usize,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
impl RuleRun {
pub fn ran(rule_id: impl Into<String>, findings: usize) -> Self {
RuleRun {
rule_id: rule_id.into(),
viewport: None,
wcag: Vec::new(),
not_run: None,
findings,
reason: None,
}
}
pub fn not_run(rule_id: impl Into<String>, why: NotRun) -> Self {
RuleRun {
rule_id: rule_id.into(),
viewport: None,
wcag: Vec::new(),
not_run: Some(why),
findings: 0,
reason: None,
}
}
pub fn in_viewport(mut self, v: impl Into<String>) -> Self {
self.viewport = Some(v.into());
self
}
pub fn with_wcag(mut self, criteria: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.wcag = criteria.into_iter().map(Into::into).collect();
self
}
pub fn with_reason(mut self, r: impl Into<String>) -> Self {
self.reason = Some(r.into());
self
}
pub fn did_run(&self) -> bool {
self.not_run.is_none()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Summary {
pub fail: usize,
pub review: usize,
pub pass: usize,
pub untested: usize,
pub critical: usize,
pub high: usize,
pub medium: usize,
pub low: usize,
pub rules_not_run: usize,
}
impl Summary {
pub fn problems(&self) -> usize {
self.fail + self.review
}
}
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Report {
pub findings: Vec<Finding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub rule_runs: Vec<RuleRun>,
pub summary: Summary,
}
impl Report {
pub fn new() -> Self {
Self::default()
}
pub fn push(&mut self, f: Finding) {
self.findings.push(f);
}
pub fn extend(&mut self, fs: impl IntoIterator<Item = Finding>) {
self.findings.extend(fs);
}
pub fn record(&mut self, r: RuleRun) {
self.rule_runs.push(r);
}
pub fn by_outcome(&self, o: Outcome) -> impl Iterator<Item = &Finding> {
self.findings.iter().filter(move |f| f.outcome == o)
}
pub fn visible(&self) -> impl Iterator<Item = &Finding> {
self.findings
.iter()
.filter(|f| f.outcome.is_visible_by_default())
}
pub fn finish(mut self) -> Self {
let mut s = Summary::default();
for f in &self.findings {
match f.outcome {
Outcome::Fail => s.fail += 1,
Outcome::Review => s.review += 1,
Outcome::Pass => s.pass += 1,
Outcome::Untested => s.untested += 1,
}
if f.outcome.is_problem() {
match f.severity {
Severity::Critical => s.critical += 1,
Severity::High => s.high += 1,
Severity::Medium => s.medium += 1,
Severity::Low => s.low += 1,
}
}
}
s.rules_not_run = self.rule_runs.iter().filter(|r| !r.did_run()).count();
self.summary = s;
self
}
}