Skip to main content

hap_model/
perms.rs

1//! Characteristic permissions.
2//!
3//! On the wire, perms are an array of short codes, e.g. `["pr","pw","ev"]`.
4
5use serde::de::{SeqAccess, Visitor};
6use serde::ser::SerializeSeq;
7use serde::{Deserialize, Deserializer, Serialize, Serializer};
8use std::fmt;
9
10/// The set of HAP characteristic permissions.
11///
12/// HAP defines seven independent permission flags; modeling them as a flat set
13/// of booleans mirrors the wire format directly, so the `struct_excessive_bools`
14/// lint does not apply here.
15#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
16#[allow(clippy::struct_excessive_bools)]
17pub struct Perms {
18    /// `pr` — paired read.
19    pub read: bool,
20    /// `pw` — paired write.
21    pub write: bool,
22    /// `ev` — events / notifications.
23    pub events: bool,
24    /// `aa` — additional authorization.
25    pub additional_authorization: bool,
26    /// `tw` — timed write.
27    pub timed_write: bool,
28    /// `hd` — hidden.
29    pub hidden: bool,
30    /// `wr` — write response.
31    pub write_response: bool,
32}
33
34impl Perms {
35    fn codes(self) -> Vec<&'static str> {
36        let mut v = Vec::new();
37        if self.read {
38            v.push("pr");
39        }
40        if self.write {
41            v.push("pw");
42        }
43        if self.events {
44            v.push("ev");
45        }
46        if self.additional_authorization {
47            v.push("aa");
48        }
49        if self.timed_write {
50            v.push("tw");
51        }
52        if self.hidden {
53            v.push("hd");
54        }
55        if self.write_response {
56            v.push("wr");
57        }
58        v
59    }
60}
61
62impl Serialize for Perms {
63    fn serialize<S: Serializer>(&self, s: S) -> Result<S::Ok, S::Error> {
64        let codes = self.codes();
65        let mut seq = s.serialize_seq(Some(codes.len()))?;
66        for c in codes {
67            seq.serialize_element(c)?;
68        }
69        seq.end()
70    }
71}
72
73impl<'de> Deserialize<'de> for Perms {
74    fn deserialize<D: Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
75        struct PermsVisitor;
76        impl<'de> Visitor<'de> for PermsVisitor {
77            type Value = Perms;
78            fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
79                f.write_str("an array of HAP permission codes")
80            }
81            fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Perms, A::Error> {
82                let mut p = Perms::default();
83                while let Some(code) = seq.next_element::<String>()? {
84                    match code.as_str() {
85                        "pr" => p.read = true,
86                        "pw" => p.write = true,
87                        "ev" => p.events = true,
88                        "aa" => p.additional_authorization = true,
89                        "tw" => p.timed_write = true,
90                        "hd" => p.hidden = true,
91                        "wr" => p.write_response = true,
92                        // Unknown codes are ignored for forward-compat, not errored.
93                        _ => {}
94                    }
95                }
96                Ok(p)
97            }
98        }
99        d.deserialize_seq(PermsVisitor)
100    }
101}
102
103#[cfg(test)]
104// Test-code carve-out: unwrap allowed with this documented justification.
105#[allow(clippy::unwrap_used)]
106mod tests {
107    use super::*;
108
109    #[test]
110    fn parses_pr_pw_ev() {
111        let p: Perms = serde_json::from_str(r#"["pr","pw","ev"]"#).unwrap();
112        assert!(p.read && p.write && p.events);
113        assert!(!p.hidden);
114    }
115
116    #[test]
117    fn serializes_in_canonical_order() {
118        let p = Perms {
119            read: true,
120            events: true,
121            ..Perms::default()
122        };
123        assert_eq!(serde_json::to_string(&p).unwrap(), r#"["pr","ev"]"#);
124    }
125
126    #[test]
127    fn ignores_unknown_codes() {
128        let p: Perms = serde_json::from_str(r#"["pr","xx"]"#).unwrap();
129        assert!(p.read);
130    }
131}