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