Skip to main content

ipp_printer_app/
flags.rs

1// The flag constants below have self-descriptive names (`COVER_OPEN`,
2// `MEDIA_JAM`, …). `bitflags!` expands the constants into a separate impl
3// block that attributes attached to the struct don't reach, so we allow at
4// module scope rather than walking 17 consts.
5#![allow(missing_docs)]
6
7//! IPP `printer-state-reasons` bit flags (PWG 5101.1 keywords).
8
9use bitflags::bitflags;
10
11/// Underlying integer representation for [`PrinterReason`].
12pub type PrinterReasonRaw = u32;
13
14bitflags! {
15    /// `printer-state-reasons` flags. Use [`PrinterReason::empty`] for
16    /// "no reasons"; do NOT define a `NONE = 0` constant — bitflags
17    /// `.contains(zero)` is always `true`, making it a footgun.
18    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
19    #[allow(missing_docs)]
20    pub struct PrinterReason: PrinterReasonRaw {
21        const OTHER = 0x0001;
22        const COVER_OPEN = 0x0002;
23        const INPUT_TRAY_MISSING = 0x0004;
24        const MARKER_SUPPLY_EMPTY = 0x0008;
25        const MARKER_SUPPLY_LOW = 0x0010;
26        const MARKER_WASTE_ALMOST_FULL = 0x0020;
27        const MARKER_WASTE_FULL = 0x0040;
28        const MEDIA_EMPTY = 0x0080;
29        const MEDIA_JAM = 0x0100;
30        const MEDIA_LOW = 0x0200;
31        const MEDIA_NEEDED = 0x0400;
32        const OFFLINE = 0x0800;
33        const SPOOL_AREA_FULL = 0x1000;
34        const TONER_EMPTY = 0x2000;
35        const TONER_LOW = 0x4000;
36        const DOOR_OPEN = 0x8000;
37        const IDENTIFY_PRINTER_REQUESTED = 0x10000;
38    }
39}
40
41impl PrinterReason {
42    /// PWG keyword tokens for this flag set, in the order CUPS expects.
43    /// An empty set yields `["none"]`.
44    pub fn ipp_keywords(&self) -> Vec<&'static str> {
45        if self.is_empty() {
46            return vec!["none"];
47        }
48        let table = [
49            (Self::OTHER, "other"),
50            (Self::COVER_OPEN, "cover-open"),
51            (Self::DOOR_OPEN, "door-open"),
52            (Self::INPUT_TRAY_MISSING, "input-tray-missing"),
53            (Self::MARKER_SUPPLY_EMPTY, "marker-supply-empty"),
54            (Self::MARKER_SUPPLY_LOW, "marker-supply-low"),
55            (Self::MARKER_WASTE_ALMOST_FULL, "marker-waste-almost-full"),
56            (Self::MARKER_WASTE_FULL, "marker-waste-full"),
57            (Self::MEDIA_EMPTY, "media-empty"),
58            (Self::MEDIA_JAM, "media-jam"),
59            (Self::MEDIA_LOW, "media-low"),
60            (Self::MEDIA_NEEDED, "media-needed"),
61            (Self::OFFLINE, "offline-report"),
62            (Self::SPOOL_AREA_FULL, "spool-area-full"),
63            (Self::TONER_EMPTY, "toner-empty"),
64            (Self::TONER_LOW, "toner-low"),
65            (Self::IDENTIFY_PRINTER_REQUESTED, "identify-printer-requested"),
66        ];
67        table
68            .into_iter()
69            .filter(|(bit, _)| self.contains(*bit))
70            .map(|(_, kw)| kw)
71            .collect()
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78
79    #[test]
80    fn empty_set_is_none() {
81        assert_eq!(PrinterReason::empty().ipp_keywords(), vec!["none"]);
82    }
83
84    #[test]
85    fn single_flag_surfaces() {
86        assert_eq!(PrinterReason::COVER_OPEN.ipp_keywords(), vec!["cover-open"]);
87    }
88
89    #[test]
90    fn multi_flag_surfaces_all() {
91        let r = PrinterReason::COVER_OPEN | PrinterReason::MEDIA_EMPTY;
92        let kws = r.ipp_keywords();
93        assert!(kws.contains(&"cover-open"));
94        assert!(kws.contains(&"media-empty"));
95        assert!(!kws.contains(&"none"));
96    }
97}