use serde::{Deserialize, Serialize};
use crate::extra::Extra;
use crate::outcome::{Outcome, Severity, WcagLevel};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Location {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub file: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub selector: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub node: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_hint: Option<String>,
}
impl Location {
pub fn file(path: impl Into<String>) -> Self {
Location {
file: Some(path.into()),
..Default::default()
}
}
pub fn node(id: impl Into<String>) -> Self {
Location {
node: Some(id.into()),
..Default::default()
}
}
pub fn with_selector(mut self, s: impl Into<String>) -> Self {
self.selector = Some(s.into());
self
}
pub fn with_url(mut self, u: impl Into<String>) -> Self {
self.url = Some(u.into());
self
}
pub fn is_empty(&self) -> bool {
self == &Location::default()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Evidence {
pub source: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub field: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub value: Option<String>,
}
impl Evidence {
pub fn new(source: impl Into<String>) -> Self {
Evidence {
source: source.into(),
field: None,
value: None,
}
}
pub fn ax_tree(value: impl Into<String>) -> Self {
Evidence {
source: "ax_tree".into(),
field: None,
value: Some(value.into()),
}
}
pub fn dom_attribute(field: impl Into<String>, value: Option<String>) -> Self {
Evidence {
source: "dom_attribute".into(),
field: Some(field.into()),
value,
}
}
pub fn computed(field: impl Into<String>, value: impl Into<String>) -> Self {
Evidence {
source: "computed".into(),
field: Some(field.into()),
value: Some(value.into()),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
pub struct Finding {
pub rule_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub rule_name: Option<String>,
pub outcome: Outcome,
pub severity: Severity,
pub message: String,
#[serde(default, skip_serializing_if = "Location::is_empty")]
pub location: Location,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub role: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub name: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub wcag: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wcag_level: Option<WcagLevel>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub tags: Vec<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub help: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub help_url: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggestion: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggested_code: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub snippet: Option<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub evidence: Vec<Evidence>,
#[serde(skip)]
pub extra: Extra,
}
impl Finding {
fn new(outcome: Outcome, rule_id: impl Into<String>, message: impl Into<String>) -> Self {
Finding {
rule_id: rule_id.into(),
rule_name: None,
outcome,
severity: Severity::default(),
message: message.into(),
location: Location::default(),
role: None,
name: None,
wcag: Vec::new(),
wcag_level: None,
tags: Vec::new(),
help: None,
help_url: None,
suggestion: None,
suggested_code: None,
snippet: None,
evidence: Vec::new(),
extra: Extra::none(),
}
}
pub fn fail(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(Outcome::Fail, rule_id, message)
}
pub fn review(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(Outcome::Review, rule_id, message)
}
pub fn pass(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(Outcome::Pass, rule_id, message)
}
pub fn untested(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
Self::new(Outcome::Untested, rule_id, message)
}
pub fn with_severity(mut self, s: Severity) -> Self {
self.severity = s;
self
}
pub fn with_rule_name(mut self, n: impl Into<String>) -> Self {
self.rule_name = Some(n.into());
self
}
pub fn with_element(mut self, role: Option<String>, name: Option<String>) -> Self {
self.role = role;
self.name = name;
self
}
pub fn with_extra<T: std::any::Any + Send + Sync>(mut self, value: T) -> Self {
self.extra = Extra::new(value);
self
}
pub fn at(mut self, l: Location) -> Self {
self.location = l;
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_wcag_level(mut self, l: WcagLevel) -> Self {
self.wcag_level = Some(l);
self
}
pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
self.tags = tags.into_iter().map(Into::into).collect();
self
}
pub fn with_help(mut self, help: impl Into<String>) -> Self {
self.help = Some(help.into());
self
}
pub fn with_help_url(mut self, url: impl Into<String>) -> Self {
self.help_url = Some(url.into());
self
}
pub fn with_suggestion(mut self, s: impl Into<String>) -> Self {
self.suggestion = Some(s.into());
self
}
pub fn with_suggested_code(mut self, s: impl Into<String>) -> Self {
self.suggested_code = Some(s.into());
self
}
pub fn with_snippet(mut self, s: impl Into<String>) -> Self {
self.snippet = Some(s.into());
self
}
pub fn with_evidence(mut self, e: impl IntoIterator<Item = Evidence>) -> Self {
self.evidence = e.into_iter().collect();
self
}
pub fn add_evidence(mut self, e: Evidence) -> Self {
self.evidence.push(e);
self
}
}