Skip to main content

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}