Skip to main content

a11y_report/
extra.rs

1//! Der Erweiterungsslot.
2//!
3//! Der gemeinsame Vertrag kann nicht jedes Feld jedes Werkzeugs aufnehmen —
4//! auditmysite hängt etwa einen Element-Screenshot an einen Befund, den es für
5//! seinen PDF-Bericht zuschneidet. Solche Daten gehören nicht in den
6//! JSON-Vertrag: Sie sind werkzeugspezifisch, oft nicht serialisierbar und für
7//! einen fremden Auswerter bedeutungslos.
8//!
9//! Die naheliegende Alternative — eine Seitentabelle, die Befunde über ihren
10//! Index adressiert — trägt nicht: Befunde werden gefiltert, sortiert und aus
11//! mehreren Läufen zusammengeführt, und dabei verschieben sich Indizes.
12//!
13//! Deshalb ein undurchsichtiger Slot am Befund selbst: Er reist mit, wird nie
14//! serialisiert und ist **nicht Teil der Identität** eines Befunds. Zwei
15//! Befunde mit gleichem Inhalt und verschiedenen Beigaben gelten als gleich.
16
17use std::any::Any;
18use std::sync::Arc;
19
20/// Werkzeugspezifische Beigabe an einem [`Finding`](crate::Finding) oder
21/// [`RuleRun`](crate::RuleRun). Nie serialisiert.
22///
23/// ```
24/// use a11y_report::{Extra, Finding};
25///
26/// struct Screenshot(Vec<u8>);
27///
28/// let f = Finding::fail("images/alt-missing", "kein alt")
29///     .with_extra(Screenshot(vec![0x89, 0x50, 0x4e, 0x47]));
30///
31/// assert_eq!(f.extra.get::<Screenshot>().map(|s| s.0.len()), Some(4));
32/// // Eine andere Sorte liegt nicht darin.
33/// assert!(f.extra.get::<String>().is_none());
34/// ```
35#[derive(Clone, Default)]
36pub struct Extra(Option<Arc<dyn Any + Send + Sync>>);
37
38impl Extra {
39    /// Leer — der Normalfall.
40    pub fn none() -> Self {
41        Extra(None)
42    }
43
44    /// Legt einen Wert ab. Ein bereits vorhandener wird ersetzt; der Slot hält
45    /// genau eine Sorte. Wer mehreres anhängen will, legt eine eigene Struktur
46    /// hinein.
47    pub fn new<T: Any + Send + Sync>(value: T) -> Self {
48        Extra(Some(Arc::new(value)))
49    }
50
51    /// Holt den Wert zurück, wenn er von der erwarteten Sorte ist.
52    pub fn get<T: Any + Send + Sync>(&self) -> Option<&T> {
53        self.0.as_ref()?.downcast_ref::<T>()
54    }
55
56    pub fn is_empty(&self) -> bool {
57        self.0.is_none()
58    }
59}
60
61impl std::fmt::Debug for Extra {
62    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63        match self.0 {
64            Some(_) => f.write_str("Extra(…)"),
65            None => f.write_str("Extra(leer)"),
66        }
67    }
68}
69
70/// Beigaben zählen nicht zur Identität eines Befunds.
71///
72/// Sonst wären zwei inhaltlich gleiche Befunde ungleich, nur weil an einem ein
73/// Screenshot hängt — und Vergleiche in Tests und beim Zusammenführen von
74/// Läufen würden an etwas scheitern, das gar nicht Teil der Aussage ist.
75impl PartialEq for Extra {
76    fn eq(&self, _other: &Self) -> bool {
77        true
78    }
79}
80
81impl Eq for Extra {}