1use serde::{Deserialize, Serialize};
4
5use crate::extra::Extra;
6use crate::outcome::{Outcome, Severity, WcagLevel};
7
8#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
13pub struct Location {
14 #[serde(default, skip_serializing_if = "Option::is_none")]
16 pub file: Option<String>,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
19 pub url: Option<String>,
20 #[serde(default, skip_serializing_if = "Option::is_none")]
22 pub selector: Option<String>,
23 #[serde(default, skip_serializing_if = "Option::is_none")]
25 pub node: Option<String>,
26 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub source_hint: Option<String>,
29}
30
31impl Location {
32 pub fn file(path: impl Into<String>) -> Self {
33 Location {
34 file: Some(path.into()),
35 ..Default::default()
36 }
37 }
38
39 pub fn node(id: impl Into<String>) -> Self {
40 Location {
41 node: Some(id.into()),
42 ..Default::default()
43 }
44 }
45
46 pub fn with_selector(mut self, s: impl Into<String>) -> Self {
47 self.selector = Some(s.into());
48 self
49 }
50
51 pub fn with_url(mut self, u: impl Into<String>) -> Self {
52 self.url = Some(u.into());
53 self
54 }
55
56 pub fn is_empty(&self) -> bool {
58 self == &Location::default()
59 }
60}
61
62#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
66pub struct Evidence {
67 pub source: String,
69 #[serde(default, skip_serializing_if = "Option::is_none")]
70 pub field: Option<String>,
71 #[serde(default, skip_serializing_if = "Option::is_none")]
72 pub value: Option<String>,
73}
74
75impl Evidence {
76 pub fn new(source: impl Into<String>) -> Self {
77 Evidence {
78 source: source.into(),
79 field: None,
80 value: None,
81 }
82 }
83
84 pub fn ax_tree(value: impl Into<String>) -> Self {
85 Evidence {
86 source: "ax_tree".into(),
87 field: None,
88 value: Some(value.into()),
89 }
90 }
91
92 pub fn dom_attribute(field: impl Into<String>, value: Option<String>) -> Self {
93 Evidence {
94 source: "dom_attribute".into(),
95 field: Some(field.into()),
96 value,
97 }
98 }
99
100 pub fn computed(field: impl Into<String>, value: impl Into<String>) -> Self {
102 Evidence {
103 source: "computed".into(),
104 field: Some(field.into()),
105 value: Some(value.into()),
106 }
107 }
108}
109
110#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116pub struct Finding {
117 pub rule_id: String,
120 #[serde(default, skip_serializing_if = "Option::is_none")]
123 pub rule_name: Option<String>,
124 pub outcome: Outcome,
125 pub severity: Severity,
126 pub message: String,
128
129 #[serde(default, skip_serializing_if = "Location::is_empty")]
130 pub location: Location,
131
132 #[serde(default, skip_serializing_if = "Option::is_none")]
138 pub role: Option<String>,
139 #[serde(default, skip_serializing_if = "Option::is_none")]
141 pub name: Option<String>,
142
143 #[serde(default, skip_serializing_if = "Vec::is_empty")]
145 pub wcag: Vec<String>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub wcag_level: Option<WcagLevel>,
148 #[serde(default, skip_serializing_if = "Vec::is_empty")]
150 pub tags: Vec<String>,
151
152 #[serde(default, skip_serializing_if = "Option::is_none")]
154 pub help: Option<String>,
155 #[serde(default, skip_serializing_if = "Option::is_none")]
156 pub help_url: Option<String>,
157 #[serde(default, skip_serializing_if = "Option::is_none")]
159 pub suggestion: Option<String>,
160 #[serde(default, skip_serializing_if = "Option::is_none")]
162 pub suggested_code: Option<String>,
163
164 #[serde(default, skip_serializing_if = "Option::is_none")]
166 pub snippet: Option<String>,
167 #[serde(default, skip_serializing_if = "Vec::is_empty")]
168 pub evidence: Vec<Evidence>,
169
170 #[serde(skip)]
173 pub extra: Extra,
174}
175
176impl Finding {
177 fn new(outcome: Outcome, rule_id: impl Into<String>, message: impl Into<String>) -> Self {
178 Finding {
179 rule_id: rule_id.into(),
180 rule_name: None,
181 outcome,
182 severity: Severity::default(),
183 message: message.into(),
184 location: Location::default(),
185 role: None,
186 name: None,
187 wcag: Vec::new(),
188 wcag_level: None,
189 tags: Vec::new(),
190 help: None,
191 help_url: None,
192 suggestion: None,
193 suggested_code: None,
194 snippet: None,
195 evidence: Vec::new(),
196 extra: Extra::none(),
197 }
198 }
199
200 pub fn fail(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
202 Self::new(Outcome::Fail, rule_id, message)
203 }
204
205 pub fn review(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
207 Self::new(Outcome::Review, rule_id, message)
208 }
209
210 pub fn pass(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
212 Self::new(Outcome::Pass, rule_id, message)
213 }
214
215 pub fn untested(rule_id: impl Into<String>, message: impl Into<String>) -> Self {
218 Self::new(Outcome::Untested, rule_id, message)
219 }
220
221 pub fn with_severity(mut self, s: Severity) -> Self {
222 self.severity = s;
223 self
224 }
225
226 pub fn with_rule_name(mut self, n: impl Into<String>) -> Self {
227 self.rule_name = Some(n.into());
228 self
229 }
230
231 pub fn with_element(mut self, role: Option<String>, name: Option<String>) -> Self {
233 self.role = role;
234 self.name = name;
235 self
236 }
237
238 pub fn with_extra<T: std::any::Any + Send + Sync>(mut self, value: T) -> Self {
240 self.extra = Extra::new(value);
241 self
242 }
243
244 pub fn at(mut self, l: Location) -> Self {
245 self.location = l;
246 self
247 }
248
249 pub fn with_wcag(mut self, criteria: impl IntoIterator<Item = impl Into<String>>) -> Self {
250 self.wcag = criteria.into_iter().map(Into::into).collect();
251 self
252 }
253
254 pub fn with_wcag_level(mut self, l: WcagLevel) -> Self {
255 self.wcag_level = Some(l);
256 self
257 }
258
259 pub fn with_tags(mut self, tags: impl IntoIterator<Item = impl Into<String>>) -> Self {
260 self.tags = tags.into_iter().map(Into::into).collect();
261 self
262 }
263
264 pub fn with_help(mut self, help: impl Into<String>) -> Self {
265 self.help = Some(help.into());
266 self
267 }
268
269 pub fn with_help_url(mut self, url: impl Into<String>) -> Self {
270 self.help_url = Some(url.into());
271 self
272 }
273
274 pub fn with_suggestion(mut self, s: impl Into<String>) -> Self {
275 self.suggestion = Some(s.into());
276 self
277 }
278
279 pub fn with_suggested_code(mut self, s: impl Into<String>) -> Self {
280 self.suggested_code = Some(s.into());
281 self
282 }
283
284 pub fn with_snippet(mut self, s: impl Into<String>) -> Self {
285 self.snippet = Some(s.into());
286 self
287 }
288
289 pub fn with_evidence(mut self, e: impl IntoIterator<Item = Evidence>) -> Self {
290 self.evidence = e.into_iter().collect();
291 self
292 }
293
294 pub fn add_evidence(mut self, e: Evidence) -> Self {
295 self.evidence.push(e);
296 self
297 }
298}