Skip to main content

a11y_rules/
lib.rs

1//! Accessibility-Regeln, generisch über das Dokumentmodell.
2//!
3//! Ein Regelbestand, drei Oberflächen: Build-Zeit, CI/Crawl und die laufende
4//! Seite. Welche Regeln laufen können, hängt davon ab, welche
5//! [`Tier`](a11y_dom::Tier)s der Host bedient.
6//!
7//! # Nicht gelaufen ist nicht bestanden
8//!
9//! [`run`] läuft mit dem, was da ist, und hält für jede Regel fest, ob sie
10//! laufen konnte. Eine Tier-2-Regel auf einem Host ohne
11//! [`Semantics`](a11y_dom::Semantics) erzeugt keinen stillen Nicht-Befund,
12//! sondern einen Vermerk mit `NotRun::CapabilityMissing`.
13//!
14//! ```
15//! use a11y_dom::Arena;
16//! use a11y_rules::run;
17//!
18//! let doc = Arena::builder()
19//!     .open("html")
20//!         .open("body")
21//!             .open("img").attr("src", "logo.png").close()
22//!         .close()
23//!     .close()
24//!     .build();
25//!
26//! let report = run(&doc);
27//!
28//! // Gefunden: kein lang, kein title, kein alt.
29//! assert!(report.findings.iter().any(|f| f.rule_id == "images/alt-missing"));
30//! assert!(report.findings.iter().any(|f| f.rule_id == "document/lang-missing"));
31//!
32//! // Nicht beurteilt: die Tier-2- und Tier-3-Regeln, weil dieser Host weder
33//! // Semantik noch Darstellung liefert. Sie fehlen nicht im Bericht, sie
34//! // stehen mit `NotRun::CapabilityMissing` darin.
35//! assert_eq!(report.summary.rules_not_run, 6);
36//! ```
37//!
38//! Mit einem Host, der [`Semantics`](a11y_dom::Semantics) erfüllt, laufen die
39//! über [`run_with_semantics`] mit.
40
41#![forbid(unsafe_code)]
42
43mod registry;
44mod rendering;
45mod semantics;
46mod structure;
47
48pub use registry::{Meta, RenderingRule, SemanticsRule, StructureRule};
49
50use a11y_dom::{Document, Rendering, Semantics};
51use a11y_report::{Finding, NotRun, Report, RuleRun};
52
53/// Alle Tier-1-Regeln.
54pub fn structure_rules<D: Document>() -> Vec<StructureRule<D>> {
55    structure::rules()
56}
57
58/// Alle Tier-2-Regeln.
59pub fn semantics_rules<D: Semantics>() -> Vec<SemanticsRule<D>> {
60    semantics::rules()
61}
62
63/// Alle Tier-3-Regeln.
64pub fn rendering_rules<D: Rendering>() -> Vec<RenderingRule<D>> {
65    rendering::rules()
66}
67
68/// Die Deklarationen ohne Bindung an einen Host — für Werkzeuge, die den
69/// Regelbestand auflisten oder Kennungen benennen müssen, ohne ihn auszuführen.
70pub fn structure_metas() -> &'static [Meta] {
71    structure::METAS
72}
73
74/// Siehe [`structure_metas`].
75pub fn semantics_metas() -> &'static [Meta] {
76    semantics::METAS
77}
78
79/// Siehe [`structure_metas`].
80pub fn rendering_metas() -> &'static [Meta] {
81    rendering::METAS
82}
83
84/// Vermerkt je deklarierter Kennung, wie viele Befunde darauf entfallen.
85///
86/// Der Vermerk läuft über die **Befund**-Kennungen, nicht über eine
87/// übergeordnete Regelkennung. Nur so benutzen `rule_runs` und `findings`
88/// dieselbe Namensmenge und lassen sich verbinden.
89fn vermerke(meta: &Meta, gefunden: &[Finding], report: &mut Report) {
90    for id in meta.ids {
91        let anzahl = gefunden.iter().filter(|f| f.rule_id == *id).count();
92        report.record(RuleRun::ran(*id, anzahl));
93    }
94}
95
96/// Vermerkt jede Kennung der übergebenen Deklarationen als nicht gelaufen.
97///
98/// Das ist die Umsetzung von „nicht geprüft ist nicht bestanden": Eine Regel,
99/// deren Tier dieser Host nicht bedient, verschwindet nicht aus dem Bericht,
100/// sondern steht mit `NotRun::CapabilityMissing` darin.
101fn nicht_gelaufen(metas: &'static [Meta], grund: &'static str, report: &mut Report) {
102    for meta in metas {
103        for id in meta.ids {
104            report.record(RuleRun::not_run(*id, NotRun::CapabilityMissing).with_reason(grund));
105        }
106    }
107}
108
109const OHNE_SEMANTIK: &str = "Host liefert keine Rolle und keinen Accessible Name";
110const OHNE_DARSTELLUNG: &str = "Host liefert keine berechneten Stile und keine Geometrie";
111
112fn run_structure<D: Document>(doc: &D, report: &mut Report) {
113    for rule in structure_rules::<D>() {
114        let mut out: Vec<Finding> = Vec::new();
115        (rule.run)(doc, &mut out);
116        vermerke(&rule.meta, &out, report);
117        report.extend(out);
118    }
119}
120
121/// Prüft ein Dokument, das nur Struktur liefert.
122///
123/// Tier-2-Regeln werden mit `NotRun::CapabilityMissing` vermerkt, nicht
124/// übergangen — der Bericht sagt damit aus, was er *nicht* geprüft hat.
125pub fn run<D: Document>(doc: &D) -> Report {
126    let mut report = Report::new();
127    run_structure(doc, &mut report);
128    nicht_gelaufen(semantics_metas(), OHNE_SEMANTIK, &mut report);
129    nicht_gelaufen(rendering_metas(), OHNE_DARSTELLUNG, &mut report);
130    report.finish()
131}
132
133/// Prüft ein Dokument, das zusätzlich Rolle und Accessible Name liefert.
134pub fn run_with_semantics<D: Semantics>(doc: &D) -> Report {
135    let mut report = Report::new();
136    run_structure(doc, &mut report);
137    run_semantics(doc, &mut report);
138    nicht_gelaufen(rendering_metas(), OHNE_DARSTELLUNG, &mut report);
139    report.finish()
140}
141
142fn run_semantics<D: Semantics>(doc: &D, report: &mut Report) {
143    for rule in semantics_rules::<D>() {
144        let mut out: Vec<Finding> = Vec::new();
145        (rule.run)(doc, &mut out);
146        vermerke(&rule.meta, &out, report);
147        report.extend(out);
148    }
149}
150
151fn run_rendering<D: Rendering>(doc: &D, report: &mut Report) {
152    for rule in rendering_rules::<D>() {
153        let mut out: Vec<Finding> = Vec::new();
154        (rule.run)(doc, &mut out);
155        vermerke(&rule.meta, &out, report);
156        report.extend(out);
157    }
158}
159
160/// Prüft ein Dokument, das Struktur, Semantik **und** Darstellung liefert.
161///
162/// Das ist der Fall der laufenden Seite und der von Chrome getriebenen
163/// Prüfung. Alle Regeln laufen; der Bericht enthält keinen
164/// `CapabilityMissing`-Vermerk mehr.
165pub fn run_full<D: Semantics + Rendering>(doc: &D) -> Report {
166    let mut report = Report::new();
167    run_structure(doc, &mut report);
168    run_semantics(doc, &mut report);
169    run_rendering(doc, &mut report);
170    report.finish()
171}
172
173/// Prüft ein Dokument, das Struktur und Darstellung liefert, aber keine
174/// Semantik. Selten — aufgeführt, damit die Tier-Kombination nicht durch das
175/// Raster fällt.
176pub fn run_with_rendering<D: Rendering>(doc: &D) -> Report {
177    let mut report = Report::new();
178    run_structure(doc, &mut report);
179    nicht_gelaufen(semantics_metas(), OHNE_SEMANTIK, &mut report);
180    run_rendering(doc, &mut report);
181    report.finish()
182}