ssg_a11y/types.rs
1// Copyright © 2023 - 2026 Static Site Generator (SSG). All rights reserved.
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Report and compliance-matrix types shared by every WCAG check.
5
6use serde::{Deserialize, Serialize};
7
8/// An individual accessibility issue found in a page.
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct AccessibilityIssue {
11 /// WCAG success criterion (e.g. "1.1.1").
12 pub criterion: String,
13 /// Severity: "error" or "warning".
14 pub severity: String,
15 /// Human-readable description.
16 pub message: String,
17}
18
19/// Accessibility report for a single page.
20#[derive(Debug, Clone, Serialize, Deserialize)]
21pub struct PageReport {
22 /// Relative path of the HTML file.
23 pub path: String,
24 /// Issues found.
25 pub issues: Vec<AccessibilityIssue>,
26}
27
28/// Full accessibility report.
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct AccessibilityReport {
31 /// Total pages scanned.
32 pub pages_scanned: usize,
33 /// Total issues found.
34 pub total_issues: usize,
35 /// WCAG version this report is asserted against.
36 #[serde(default = "default_wcag_version")]
37 pub wcag_version: String,
38 /// Per-page reports (only pages with issues).
39 pub pages: Vec<PageReport>,
40}
41
42/// Default value for [`AccessibilityReport::wcag_version`] when the field
43/// is absent from a serialised report (used by `#[serde(default = ...)]`).
44pub(crate) fn default_wcag_version() -> String {
45 "2.2".to_string()
46}
47
48/// How a single WCAG criterion is verified.
49#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
50#[serde(rename_all = "kebab-case")]
51#[non_exhaustive]
52pub enum CriterionStatus {
53 /// Verified at build time by this crate.
54 Automated,
55 /// Verified at runtime by a tool such as axe-core.
56 Runtime,
57 /// Requires human review (e.g. cognitive accessibility).
58 Manual,
59 /// Does not apply to static content (e.g. forms-only criteria).
60 NotApplicable,
61}
62
63/// One row of the WCAG 2.2 compliance matrix.
64#[derive(Debug, Clone, Serialize, Deserialize)]
65pub struct CriterionEntry {
66 /// SC identifier (e.g. "1.1.1", "2.5.8").
67 pub criterion: String,
68 /// Conformance level: A, AA, AAA.
69 pub level: String,
70 /// Short title of the criterion.
71 pub title: String,
72 /// Verification status.
73 pub status: CriterionStatus,
74 /// True if every scanned page passed (only meaningful for `Automated`).
75 pub all_pages_pass: bool,
76}
77
78/// WCAG 2.2 compliance matrix describing which criteria this crate can
79/// verify automatically, and whether every scanned page passed them.
80#[derive(Debug, Clone, Serialize, Deserialize)]
81pub struct WcagComplianceReport {
82 /// Spec version this matrix is asserted against.
83 pub wcag_version: String,
84 /// Total pages scanned.
85 pub pages_scanned: usize,
86 /// Per-criterion compliance entries.
87 pub criteria: Vec<CriterionEntry>,
88}
89
90#[cfg(test)]
91mod tests {
92 use super::*;
93
94 #[test]
95 fn default_wcag_version_is_22() {
96 // Covers default_wcag_version fn body. Used by serde when the
97 // wcag_version field is absent during deserialise.
98 assert_eq!(default_wcag_version(), "2.2");
99 }
100
101 #[test]
102 fn accessibility_report_deserialises_without_wcag_version() {
103 // Confirms the serde default integration: a JSON blob without
104 // wcag_version still parses and yields "2.2".
105 let json = r#"{"pages_scanned":0,"total_issues":0,"pages":[]}"#;
106 let r: AccessibilityReport = serde_json::from_str(json).unwrap();
107 assert_eq!(r.wcag_version, "2.2");
108 }
109}