1mod restricted;
2
3use core::{fmt, str::FromStr};
4
5use heapless::String;
6pub use restricted::RestrictedCapability;
7use thiserror::Error;
8
9#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
10pub enum Capability {
11 Activity,
12 AllJoyn,
13 Appointments,
14 BackgroundMediaPlayback,
15 BlockedChatMessages,
16 Bluetooth,
17 Chat,
18 CodeGeneration,
19 Contacts,
20 GazeInput,
21 GlobalMediaControl,
22 GraphicsCapture,
23 GraphicsCaptureProgrammatic,
24 GraphicsCaptureWithoutBorder,
25 HumanInterfaceDevice,
26 HumanPresence,
27 InternetClient,
28 InternetClientServer,
29 Location,
30 LowLevel,
31 LowLevelDevices,
32 Microphone,
33 MusicLibrary,
34 Objects3D,
35 Optical,
36 PhoneCall,
37 PhoneCallHistoryPublic,
38 PicturesLibrary,
39 PointOfService,
40 PrivateNetworkClientServer,
41 Proximity,
42 Radios,
43 RecordedCallsFolder,
44 RemoteSystem,
45 RemovableStorage,
46 SerialCommunication,
47 SpatialPerception,
48 SystemManagement,
49 Usb,
50 UserAccountInformation,
51 UserDataTasks,
52 UserNotificationListener,
53 VideosLibrary,
54 VoipCall,
55 Webcam,
56 WiFiControl,
57}
58
59impl Capability {
60 pub const MAX_LEN: usize = 40;
61
62 #[must_use]
63 pub const fn as_str(self) -> &'static str {
64 match self {
65 Self::Activity => "activity",
66 Self::AllJoyn => "allJoyn",
67 Self::Appointments => "appointments",
68 Self::BackgroundMediaPlayback => "backgroundMediaPlayback",
69 Self::BlockedChatMessages => "blockedChatMessages",
70 Self::Bluetooth => "bluetooth",
71 Self::Chat => "chat",
72 Self::CodeGeneration => "codeGeneration",
73 Self::Contacts => "contacts",
74 Self::GazeInput => "gazeInput",
75 Self::GlobalMediaControl => "globalMediaControl",
76 Self::GraphicsCapture => "graphicsCapture",
77 Self::GraphicsCaptureProgrammatic => "graphicsCaptureProgrammatic",
78 Self::GraphicsCaptureWithoutBorder => "graphicsCaptureWithoutBorder",
79 Self::HumanInterfaceDevice => "humaninterfacedevice",
80 Self::HumanPresence => "humanPresence",
81 Self::InternetClient => "internetClient",
82 Self::InternetClientServer => "internetClientServer",
83 Self::Location => "location",
84 Self::LowLevel => "lowLevel",
85 Self::LowLevelDevices => "lowLevelDevices",
86 Self::Microphone => "microphone",
87 Self::MusicLibrary => "musicLibrary",
88 Self::Objects3D => "objects3D",
89 Self::Optical => "optical",
90 Self::PhoneCall => "phoneCall",
91 Self::PhoneCallHistoryPublic => "phoneCallHistoryPublic",
92 Self::PicturesLibrary => "picturesLibrary",
93 Self::PointOfService => "pointOfService",
94 Self::PrivateNetworkClientServer => "privateNetworkClientServer",
95 Self::Proximity => "proximity",
96 Self::Radios => "radios",
97 Self::RecordedCallsFolder => "recordedCallsFolder",
98 Self::RemoteSystem => "remoteSystem",
99 Self::RemovableStorage => "removableStorage",
100 Self::SerialCommunication => "serialcommunication",
101 Self::SpatialPerception => "spatialPerception",
102 Self::SystemManagement => "systemManagement",
103 Self::Usb => "usb",
104 Self::UserAccountInformation => "userAccountInformation",
105 Self::UserDataTasks => "userDataTasks",
106 Self::UserNotificationListener => "userNotificationListener",
107 Self::VideosLibrary => "videosLibrary",
108 Self::VoipCall => "voipCall",
109 Self::Webcam => "webcam",
110 Self::WiFiControl => "wiFiControl",
111 }
112 }
113}
114
115impl AsRef<str> for Capability {
116 #[inline]
117 fn as_ref(&self) -> &str {
118 self.as_str()
119 }
120}
121
122impl fmt::Display for Capability {
123 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124 self.as_str().fmt(f)
125 }
126}
127
128#[derive(Debug, Error, Eq, PartialEq)]
129pub enum CapabilityError {
130 #[error("Capability must not be empty")]
131 Empty,
132 #[error(
133 "Capability must not have more than {} ASCII characters but has {_0}",
134 Capability::MAX_LEN
135 )]
136 TooLong(usize),
137 #[error(r#""{_0}" is not a known capability"#)]
138 Unknown(String<40>),
139}
140
141impl FromStr for Capability {
142 type Err = CapabilityError;
143
144 fn from_str(s: &str) -> Result<Self, Self::Err> {
145 if s.is_empty() {
146 return Err(Self::Err::Empty);
147 }
148
149 match s {
150 "activity" => Ok(Self::Activity),
151 "allJoyn" => Ok(Self::AllJoyn),
152 "appointments" => Ok(Self::Appointments),
153 "backgroundMediaPlayback" => Ok(Self::BackgroundMediaPlayback),
154 "blockedChatMessages" => Ok(Self::BlockedChatMessages),
155 "bluetooth" => Ok(Self::Bluetooth),
156 "chat" => Ok(Self::Chat),
157 "codeGeneration" => Ok(Self::CodeGeneration),
158 "contacts" => Ok(Self::Contacts),
159 "gazeInput" => Ok(Self::GazeInput),
160 "globalMediaControl" => Ok(Self::GlobalMediaControl),
161 "graphicsCapture" => Ok(Self::GraphicsCapture),
162 "graphicsCaptureProgrammatic" => Ok(Self::GraphicsCaptureProgrammatic),
163 "graphicsCaptureWithoutBorder" => Ok(Self::GraphicsCaptureWithoutBorder),
164 "humaninterfacedevice" => Ok(Self::HumanInterfaceDevice),
165 "humanPresence" => Ok(Self::HumanPresence),
166 "internetClient" => Ok(Self::InternetClient),
167 "internetClientServer" => Ok(Self::InternetClientServer),
168 "location" => Ok(Self::Location),
169 "lowLevel" => Ok(Self::LowLevel),
170 "lowLevelDevices" => Ok(Self::LowLevelDevices),
171 "microphone" => Ok(Self::Microphone),
172 "musicLibrary" => Ok(Self::MusicLibrary),
173 "objects3D" => Ok(Self::Objects3D),
174 "optical" => Ok(Self::Optical),
175 "phoneCall" => Ok(Self::PhoneCall),
176 "phoneCallHistoryPublic" => Ok(Self::PhoneCallHistoryPublic),
177 "picturesLibrary" => Ok(Self::PicturesLibrary),
178 "pointOfService" => Ok(Self::PointOfService),
179 "privateNetworkClientServer" => Ok(Self::PrivateNetworkClientServer),
180 "proximity" => Ok(Self::Proximity),
181 "radios" => Ok(Self::Radios),
182 "recordedCallsFolder" => Ok(Self::RecordedCallsFolder),
183 "remoteSystem" => Ok(Self::RemoteSystem),
184 "removableStorage" => Ok(Self::RemovableStorage),
185 "serialcommunication" => Ok(Self::SerialCommunication),
186 "spatialPerception" => Ok(Self::SpatialPerception),
187 "systemManagement" => Ok(Self::SystemManagement),
188 "usb" => Ok(Self::Usb),
189 "userAccountInformation" => Ok(Self::UserAccountInformation),
190 "userDataTasks" => Ok(Self::UserDataTasks),
191 "userNotificationListener" => Ok(Self::UserNotificationListener),
192 "videosLibrary" => Ok(Self::VideosLibrary),
193 "voipCall" => Ok(Self::VoipCall),
194 "webcam" => Ok(Self::Webcam),
195 "wiFiControl" => Ok(Self::WiFiControl),
196 _ => Err(Self::Err::Unknown(
197 s.parse::<String<{ Self::MAX_LEN }>>()
198 .map_err(|_| Self::Err::TooLong(s.len()))?,
199 )),
200 }
201 }
202}
203
204#[cfg(feature = "serde")]
205impl serde::Serialize for Capability {
206 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
207 where
208 S: serde::Serializer,
209 {
210 self.as_str().serialize(serializer)
211 }
212}
213
214#[cfg(feature = "serde")]
215impl<'de> serde::Deserialize<'de> for Capability {
216 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
217 where
218 D: serde::Deserializer<'de>,
219 {
220 struct CapabilityVisitor;
221
222 impl serde::de::Visitor<'_> for CapabilityVisitor {
223 type Value = Capability;
224
225 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
226 formatter.write_str("a capability string")
227 }
228
229 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
230 where
231 E: serde::de::Error,
232 {
233 value.parse::<Self::Value>().map_err(E::custom)
234 }
235
236 fn visit_bytes<E>(self, value: &[u8]) -> Result<Self::Value, E>
237 where
238 E: serde::de::Error,
239 {
240 let utf8 = core::str::from_utf8(value).map_err(E::custom)?;
241 self.visit_str(utf8)
242 }
243 }
244
245 deserializer.deserialize_str(CapabilityVisitor)
246 }
247}
248
249#[cfg(test)]
250mod tests {
251 use rstest::rstest;
252
253 use super::{Capability, CapabilityError};
254
255 #[rstest]
256 #[case("musicLibrary")]
257 #[case("picturesLibrary")]
258 #[case("videosLibrary")]
259 #[case("removableStorage")]
260 #[case("internetClient")]
261 #[case("internetClientServer")]
262 #[case("privateNetworkClientServer")]
263 #[case("contacts")]
264 #[case("codeGeneration")]
265 #[case("allJoyn")]
266 #[case("phoneCall")]
267 #[case("phoneCallHistoryPublic")]
268 #[case("recordedCallsFolder")]
269 #[case("userAccountInformation")]
270 #[case("objects3D")]
271 #[case("chat")]
272 #[case("blockedChatMessages")]
273 #[case("lowLevelDevices")]
274 #[case("systemManagement")]
275 #[case("backgroundMediaPlayback")]
276 #[case("remoteSystem")]
277 #[case("spatialPerception")]
278 #[case("globalMediaControl")]
279 #[case("graphicsCapture")]
280 #[case("graphicsCaptureWithoutBorder")]
281 #[case("graphicsCaptureProgrammatic")]
282 #[case("userDataTasks")]
283 #[case("userNotificationListener")]
284 fn valid_general_capability(#[case] capability: &str) {
285 assert!(capability.parse::<Capability>().is_ok());
286 }
287
288 #[rstest]
289 #[case("location")]
290 #[case("microphone")]
291 #[case("proximity")]
292 #[case("webcam")]
293 #[case("usb")]
294 #[case("humaninterfacedevice")]
295 #[case("pointOfService")]
296 #[case("bluetooth")]
297 #[case("wiFiControl")]
298 #[case("radios")]
299 #[case("optical")]
300 #[case("activity")]
301 #[case("humanPresence")]
302 #[case("serialcommunication")]
303 #[case("gazeInput")]
304 #[case("lowLevel")]
305 fn valid_device_capability(#[case] capability: &str) {
306 assert!(capability.parse::<Capability>().is_ok());
307 }
308
309 #[test]
310 fn invalid_capability() {
311 assert_eq!("".parse::<Capability>().err(), Some(CapabilityError::Empty));
312 }
313}