sonos_api/services/group_rendering_control/
operations.rs1use crate::operation::parse_sonos_bool;
15use crate::{define_operation_with_response, define_upnp_operation, Validate};
16use paste::paste;
17
18define_operation_with_response! {
23 operation: GetGroupVolumeOperation,
24 action: "GetGroupVolume",
25 service: GroupRenderingControl,
26 request: {},
27 response: GetGroupVolumeResponse {
28 current_volume: u16,
29 },
30 xml_mapping: {
31 current_volume: "CurrentVolume",
32 },
33}
34
35impl Validate for GetGroupVolumeOperationRequest {}
36
37pub use get_group_volume_operation as get_group_volume;
38
39define_upnp_operation! {
44 operation: SetGroupVolumeOperation,
45 action: "SetGroupVolume",
46 service: GroupRenderingControl,
47 request: {
48 desired_volume: u16,
49 },
50 response: (),
51 payload: |req| {
52 format!(
53 "<InstanceID>{}</InstanceID><DesiredVolume>{}</DesiredVolume>",
54 req.instance_id, req.desired_volume
55 )
56 },
57 parse: |_xml| Ok(()),
58}
59
60impl Validate for SetGroupVolumeOperationRequest {
61 fn validate_basic(&self) -> Result<(), crate::operation::ValidationError> {
62 if self.desired_volume > 100 {
63 return Err(crate::operation::ValidationError::range_error(
64 "desired_volume",
65 0,
66 100,
67 self.desired_volume,
68 ));
69 }
70 Ok(())
71 }
72}
73
74pub use set_group_volume_operation as set_group_volume;
75
76define_operation_with_response! {
81 operation: SetRelativeGroupVolumeOperation,
82 action: "SetRelativeGroupVolume",
83 service: GroupRenderingControl,
84 request: {
85 adjustment: i16,
86 },
87 response: SetRelativeGroupVolumeResponse {
88 new_volume: u16,
89 },
90 xml_mapping: {
91 new_volume: "NewVolume",
92 },
93}
94
95impl Validate for SetRelativeGroupVolumeOperationRequest {
96 fn validate_basic(&self) -> Result<(), crate::operation::ValidationError> {
97 if self.adjustment < -100 || self.adjustment > 100 {
98 return Err(crate::operation::ValidationError::range_error(
99 "adjustment",
100 -100,
101 100,
102 self.adjustment,
103 ));
104 }
105 Ok(())
106 }
107}
108
109pub use set_relative_group_volume_operation as set_relative_group_volume;
110
111#[derive(serde::Serialize, Clone, Debug, PartialEq)]
118pub struct GetGroupMuteOperationRequest {
119 pub instance_id: u32,
120}
121
122#[derive(serde::Deserialize, Debug, Clone, PartialEq)]
123pub struct GetGroupMuteResponse {
124 pub current_mute: bool,
125}
126
127pub struct GetGroupMuteOperation;
128
129impl crate::operation::UPnPOperation for GetGroupMuteOperation {
130 type Request = GetGroupMuteOperationRequest;
131 type Response = GetGroupMuteResponse;
132
133 const SERVICE: crate::service::Service = crate::service::Service::GroupRenderingControl;
134 const ACTION: &'static str = "GetGroupMute";
135
136 fn build_payload(request: &Self::Request) -> Result<String, crate::operation::ValidationError> {
137 request.validate(crate::operation::ValidationLevel::Basic)?;
138 Ok(format!("<InstanceID>{}</InstanceID>", request.instance_id))
139 }
140
141 fn parse_response(xml: &xmltree::Element) -> Result<Self::Response, crate::error::ApiError> {
142 Ok(GetGroupMuteResponse {
143 current_mute: parse_sonos_bool(xml, "CurrentMute"),
144 })
145 }
146}
147
148pub fn get_group_mute_operation() -> crate::operation::OperationBuilder<GetGroupMuteOperation> {
149 let request = GetGroupMuteOperationRequest { instance_id: 0 };
150 crate::operation::OperationBuilder::new(request)
151}
152
153impl Validate for GetGroupMuteOperationRequest {}
154
155pub use get_group_mute_operation as get_group_mute;
156
157define_upnp_operation! {
162 operation: SetGroupMuteOperation,
163 action: "SetGroupMute",
164 service: GroupRenderingControl,
165 request: {
166 desired_mute: bool,
167 },
168 response: (),
169 payload: |req| {
170 format!(
171 "<InstanceID>{}</InstanceID><DesiredMute>{}</DesiredMute>",
172 req.instance_id,
173 if req.desired_mute { "1" } else { "0" }
174 )
175 },
176 parse: |_xml| Ok(()),
177}
178
179impl Validate for SetGroupMuteOperationRequest {}
180
181pub use set_group_mute_operation as set_group_mute;
182
183define_upnp_operation! {
188 operation: SnapshotGroupVolumeOperation,
189 action: "SnapshotGroupVolume",
190 service: GroupRenderingControl,
191 request: {},
192 response: (),
193 payload: |req| {
194 format!("<InstanceID>{}</InstanceID>", req.instance_id)
195 },
196 parse: |_xml| Ok(()),
197}
198
199impl Validate for SnapshotGroupVolumeOperationRequest {}
200
201pub use snapshot_group_volume_operation as snapshot_group_volume;
202
203pub const SERVICE: crate::Service = crate::Service::GroupRenderingControl;
209
210pub fn subscribe(
212 client: &crate::SonosClient,
213 ip: &str,
214 callback_url: &str,
215) -> crate::Result<crate::ManagedSubscription> {
216 client.subscribe(ip, SERVICE, callback_url)
217}
218
219pub fn subscribe_with_timeout(
221 client: &crate::SonosClient,
222 ip: &str,
223 callback_url: &str,
224 timeout_seconds: u32,
225) -> crate::Result<crate::ManagedSubscription> {
226 client.subscribe_with_timeout(ip, SERVICE, callback_url, timeout_seconds)
227}
228
229#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::operation::UPnPOperation;
237
238 #[test]
239 fn test_get_group_volume_builder() {
240 let op = get_group_volume().build().unwrap();
241 assert_eq!(op.metadata().action, "GetGroupVolume");
242 assert_eq!(op.request().instance_id, 0);
243 }
244
245 #[test]
246 fn test_set_group_volume_builder() {
247 let op = set_group_volume(75).build().unwrap();
248 assert_eq!(op.request().desired_volume, 75);
249 assert_eq!(op.request().instance_id, 0);
250 assert_eq!(op.metadata().action, "SetGroupVolume");
251 }
252
253 #[test]
254 fn test_set_relative_group_volume_builder() {
255 let op = set_relative_group_volume(10).build().unwrap();
256 assert_eq!(op.request().adjustment, 10);
257 assert_eq!(op.request().instance_id, 0);
258 assert_eq!(op.metadata().action, "SetRelativeGroupVolume");
259 }
260
261 #[test]
262 fn test_get_group_mute_builder() {
263 let op = get_group_mute().build().unwrap();
264 assert_eq!(op.metadata().action, "GetGroupMute");
265 assert_eq!(op.request().instance_id, 0);
266 }
267
268 #[test]
269 fn test_set_group_mute_builder() {
270 let op = set_group_mute(true).build().unwrap();
271 assert!(op.request().desired_mute);
272 assert_eq!(op.request().instance_id, 0);
273 assert_eq!(op.metadata().action, "SetGroupMute");
274 }
275
276 #[test]
277 fn test_snapshot_group_volume_builder() {
278 let op = snapshot_group_volume().build().unwrap();
279 assert_eq!(op.metadata().action, "SnapshotGroupVolume");
280 assert_eq!(op.request().instance_id, 0);
281 }
282
283 #[test]
284 fn test_get_group_volume_payload() {
285 let request = GetGroupVolumeOperationRequest { instance_id: 0 };
286 let payload = GetGroupVolumeOperation::build_payload(&request).unwrap();
287 assert_eq!(payload, "<InstanceID>0</InstanceID>");
288 }
289
290 #[test]
291 fn test_set_group_volume_payload() {
292 let request = SetGroupVolumeOperationRequest {
293 instance_id: 0,
294 desired_volume: 75,
295 };
296 let payload = SetGroupVolumeOperation::build_payload(&request).unwrap();
297 assert!(payload.contains("<InstanceID>0</InstanceID>"));
298 assert!(payload.contains("<DesiredVolume>75</DesiredVolume>"));
299 }
300
301 #[test]
302 fn test_set_relative_group_volume_payload() {
303 let request = SetRelativeGroupVolumeOperationRequest {
304 instance_id: 0,
305 adjustment: -25,
306 };
307 let payload = SetRelativeGroupVolumeOperation::build_payload(&request).unwrap();
308 assert!(payload.contains("<InstanceID>0</InstanceID>"));
309 assert!(payload.contains("<Adjustment>-25</Adjustment>"));
310 }
311
312 #[test]
313 fn test_get_group_mute_payload() {
314 let request = GetGroupMuteOperationRequest { instance_id: 0 };
315 let payload = GetGroupMuteOperation::build_payload(&request).unwrap();
316 assert_eq!(payload, "<InstanceID>0</InstanceID>");
317 }
318
319 #[test]
320 fn test_get_group_mute_parse_response_true() {
321 let xml_str =
322 r#"<GetGroupMuteResponse><CurrentMute>1</CurrentMute></GetGroupMuteResponse>"#;
323 let xml = xmltree::Element::parse(xml_str.as_bytes()).unwrap();
324 let response = GetGroupMuteOperation::parse_response(&xml).unwrap();
325 assert!(response.current_mute);
326 }
327
328 #[test]
329 fn test_get_group_mute_parse_response_false() {
330 let xml_str =
331 r#"<GetGroupMuteResponse><CurrentMute>0</CurrentMute></GetGroupMuteResponse>"#;
332 let xml = xmltree::Element::parse(xml_str.as_bytes()).unwrap();
333 let response = GetGroupMuteOperation::parse_response(&xml).unwrap();
334 assert!(!response.current_mute);
335 }
336
337 #[test]
338 fn test_set_group_mute_payload_true() {
339 let request = SetGroupMuteOperationRequest {
340 instance_id: 0,
341 desired_mute: true,
342 };
343 let payload = SetGroupMuteOperation::build_payload(&request).unwrap();
344 assert!(payload.contains("<InstanceID>0</InstanceID>"));
345 assert!(payload.contains("<DesiredMute>1</DesiredMute>"));
346 }
347
348 #[test]
349 fn test_set_group_mute_payload_false() {
350 let request = SetGroupMuteOperationRequest {
351 instance_id: 0,
352 desired_mute: false,
353 };
354 let payload = SetGroupMuteOperation::build_payload(&request).unwrap();
355 assert!(payload.contains("<InstanceID>0</InstanceID>"));
356 assert!(payload.contains("<DesiredMute>0</DesiredMute>"));
357 }
358
359 #[test]
360 fn test_snapshot_group_volume_payload() {
361 let request = SnapshotGroupVolumeOperationRequest { instance_id: 0 };
362 let payload = SnapshotGroupVolumeOperation::build_payload(&request).unwrap();
363 assert_eq!(payload, "<InstanceID>0</InstanceID>");
364 }
365
366 #[test]
367 fn test_set_group_volume_rejects_over_100() {
368 let request = SetGroupVolumeOperationRequest {
369 instance_id: 0,
370 desired_volume: 101,
371 };
372 assert!(request.validate_basic().is_err());
373 }
374
375 #[test]
376 fn test_set_group_volume_accepts_boundary_values() {
377 let request = SetGroupVolumeOperationRequest {
379 instance_id: 0,
380 desired_volume: 0,
381 };
382 assert!(request.validate_basic().is_ok());
383
384 let request = SetGroupVolumeOperationRequest {
386 instance_id: 0,
387 desired_volume: 100,
388 };
389 assert!(request.validate_basic().is_ok());
390 }
391
392 #[test]
393 fn test_set_relative_group_volume_rejects_under_minus_100() {
394 let request = SetRelativeGroupVolumeOperationRequest {
395 instance_id: 0,
396 adjustment: -101,
397 };
398 assert!(request.validate_basic().is_err());
399 }
400
401 #[test]
402 fn test_set_relative_group_volume_rejects_over_100() {
403 let request = SetRelativeGroupVolumeOperationRequest {
404 instance_id: 0,
405 adjustment: 101,
406 };
407 assert!(request.validate_basic().is_err());
408 }
409
410 #[test]
411 fn test_set_relative_group_volume_accepts_boundary_values() {
412 let request = SetRelativeGroupVolumeOperationRequest {
414 instance_id: 0,
415 adjustment: -100,
416 };
417 assert!(request.validate_basic().is_ok());
418
419 let request = SetRelativeGroupVolumeOperationRequest {
421 instance_id: 0,
422 adjustment: 0,
423 };
424 assert!(request.validate_basic().is_ok());
425
426 let request = SetRelativeGroupVolumeOperationRequest {
428 instance_id: 0,
429 adjustment: 100,
430 };
431 assert!(request.validate_basic().is_ok());
432 }
433
434 #[test]
435 fn test_service_constant() {
436 assert_eq!(SERVICE, crate::Service::GroupRenderingControl);
437 }
438
439 #[test]
440 fn test_subscribe_function_signature() {
441 let client = crate::SonosClient::new();
442 let _subscribe_fn = || subscribe(&client, "192.168.1.100", "http://callback.url");
444 }
445
446 #[test]
447 fn test_subscribe_with_timeout_function_signature() {
448 let client = crate::SonosClient::new();
449 let _subscribe_fn =
451 || subscribe_with_timeout(&client, "192.168.1.100", "http://callback.url", 3600);
452 }
453}