sonos_api/services/group_rendering_control/
events.rs1use serde::{Deserialize, Serialize};
16
17use crate::{ApiError, Result};
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
21#[serde(rename = "propertyset")]
22pub struct GroupRenderingControlEvent {
23 #[serde(rename = "property", default)]
24 properties: Vec<GroupRenderingControlProperty>,
25}
26
27#[derive(Debug, Clone, Serialize, Deserialize)]
28struct GroupRenderingControlProperty {
29 #[serde(rename = "GroupVolume", default)]
30 group_volume: Option<String>,
31
32 #[serde(rename = "GroupMute", default)]
33 group_mute: Option<String>,
34
35 #[serde(rename = "GroupVolumeChangeable", default)]
36 group_volume_changeable: Option<String>,
37}
38
39impl GroupRenderingControlEvent {
40 pub fn group_volume(&self) -> Option<u16> {
42 self.properties
43 .iter()
44 .find_map(|p| p.group_volume.as_ref())
45 .and_then(|s| s.parse::<u16>().ok())
46 }
47
48 pub fn group_mute(&self) -> Option<bool> {
50 self.properties
51 .iter()
52 .find_map(|p| p.group_mute.as_ref())
53 .map(|s| s == "1" || s.to_lowercase() == "true")
54 }
55
56 pub fn group_volume_changeable(&self) -> Option<bool> {
58 self.properties
59 .iter()
60 .find_map(|p| p.group_volume_changeable.as_ref())
61 .map(|s| s == "1" || s.to_lowercase() == "true")
62 }
63
64 pub fn into_state(&self) -> super::state::GroupRenderingControlState {
66 super::state::GroupRenderingControlState {
67 group_volume: self.group_volume(),
68 group_mute: self.group_mute(),
69 group_volume_changeable: self.group_volume_changeable(),
70 }
71 }
72
73 pub fn from_xml(xml: &str) -> Result<Self> {
75 quick_xml::de::from_str(xml).map_err(|e| {
76 ApiError::ParseError(format!("Failed to parse GroupRenderingControl XML: {e}"))
77 })
78 }
79}
80
81#[cfg(test)]
82mod tests {
83 use super::*;
84
85 #[test]
86 fn test_parse_real_event_xml() {
87 let xml = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0"><e:property><GroupVolume>14</GroupVolume></e:property><e:property><GroupMute>0</GroupMute></e:property><e:property><GroupVolumeChangeable>1</GroupVolumeChangeable></e:property></e:propertyset>"#;
89
90 let event = GroupRenderingControlEvent::from_xml(xml).unwrap();
91 assert_eq!(event.group_volume(), Some(14));
92 assert_eq!(event.group_mute(), Some(false));
93 assert_eq!(event.group_volume_changeable(), Some(true));
94 }
95
96 #[test]
97 fn test_parse_formatted_xml() {
98 let xml = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
99 <e:property>
100 <GroupVolume>75</GroupVolume>
101 </e:property>
102 <e:property>
103 <GroupMute>1</GroupMute>
104 </e:property>
105 <e:property>
106 <GroupVolumeChangeable>0</GroupVolumeChangeable>
107 </e:property>
108 </e:propertyset>"#;
109
110 let event = GroupRenderingControlEvent::from_xml(xml).unwrap();
111 assert_eq!(event.group_volume(), Some(75));
112 assert_eq!(event.group_mute(), Some(true));
113 assert_eq!(event.group_volume_changeable(), Some(false));
114 }
115
116 #[test]
117 fn test_partial_properties() {
118 let xml = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
119 <e:property>
120 <GroupVolume>50</GroupVolume>
121 </e:property>
122 </e:propertyset>"#;
123
124 let event = GroupRenderingControlEvent::from_xml(xml).unwrap();
125 assert_eq!(event.group_volume(), Some(50));
126 assert_eq!(event.group_mute(), None);
127 assert_eq!(event.group_volume_changeable(), None);
128 }
129
130 #[test]
131 fn test_volume_boundary_values() {
132 let xml = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
133 <e:property><GroupVolume>0</GroupVolume></e:property>
134 </e:propertyset>"#;
135 let event = GroupRenderingControlEvent::from_xml(xml).unwrap();
136 assert_eq!(event.group_volume(), Some(0));
137
138 let xml = r#"<e:propertyset xmlns:e="urn:schemas-upnp-org:event-1-0">
139 <e:property><GroupVolume>100</GroupVolume></e:property>
140 </e:propertyset>"#;
141 let event = GroupRenderingControlEvent::from_xml(xml).unwrap();
142 assert_eq!(event.group_volume(), Some(100));
143 }
144
145 #[test]
146 fn test_boolean_parsing() {
147 let event = GroupRenderingControlEvent {
148 properties: vec![GroupRenderingControlProperty {
149 group_volume: None,
150 group_mute: Some("1".to_string()),
151 group_volume_changeable: Some("true".to_string()),
152 }],
153 };
154 assert_eq!(event.group_mute(), Some(true));
155 assert_eq!(event.group_volume_changeable(), Some(true));
156
157 let event = GroupRenderingControlEvent {
158 properties: vec![GroupRenderingControlProperty {
159 group_volume: None,
160 group_mute: Some("0".to_string()),
161 group_volume_changeable: Some("false".to_string()),
162 }],
163 };
164 assert_eq!(event.group_mute(), Some(false));
165 assert_eq!(event.group_volume_changeable(), Some(false));
166 }
167
168 #[test]
169 fn test_into_state_maps_all_fields() {
170 let event = GroupRenderingControlEvent {
171 properties: vec![GroupRenderingControlProperty {
172 group_volume: Some("42".to_string()),
173 group_mute: Some("0".to_string()),
174 group_volume_changeable: Some("true".to_string()),
175 }],
176 };
177
178 let state = event.into_state();
179
180 assert_eq!(state.group_volume, Some(42));
181 assert_eq!(state.group_mute, Some(false));
182 assert_eq!(state.group_volume_changeable, Some(true));
183 }
184}