use crate::operation::parse_sonos_bool;
use crate::{define_operation_with_response, define_upnp_operation, Validate};
use paste::paste;
define_operation_with_response! {
operation: GetGroupVolumeOperation,
action: "GetGroupVolume",
service: GroupRenderingControl,
request: {},
response: GetGroupVolumeResponse {
current_volume: u16,
},
xml_mapping: {
current_volume: "CurrentVolume",
},
}
impl Validate for GetGroupVolumeOperationRequest {}
pub use get_group_volume_operation as get_group_volume;
define_upnp_operation! {
operation: SetGroupVolumeOperation,
action: "SetGroupVolume",
service: GroupRenderingControl,
request: {
desired_volume: u16,
},
response: (),
payload: |req| {
format!(
"<InstanceID>{}</InstanceID><DesiredVolume>{}</DesiredVolume>",
req.instance_id, req.desired_volume
)
},
parse: |_xml| Ok(()),
}
impl Validate for SetGroupVolumeOperationRequest {
fn validate_basic(&self) -> Result<(), crate::operation::ValidationError> {
if self.desired_volume > 100 {
return Err(crate::operation::ValidationError::range_error(
"desired_volume",
0,
100,
self.desired_volume,
));
}
Ok(())
}
}
pub use set_group_volume_operation as set_group_volume;
define_operation_with_response! {
operation: SetRelativeGroupVolumeOperation,
action: "SetRelativeGroupVolume",
service: GroupRenderingControl,
request: {
adjustment: i16,
},
response: SetRelativeGroupVolumeResponse {
new_volume: u16,
},
xml_mapping: {
new_volume: "NewVolume",
},
}
impl Validate for SetRelativeGroupVolumeOperationRequest {
fn validate_basic(&self) -> Result<(), crate::operation::ValidationError> {
if self.adjustment < -100 || self.adjustment > 100 {
return Err(crate::operation::ValidationError::range_error(
"adjustment",
-100,
100,
self.adjustment,
));
}
Ok(())
}
}
pub use set_relative_group_volume_operation as set_relative_group_volume;
#[derive(serde::Serialize, Clone, Debug, PartialEq)]
pub struct GetGroupMuteOperationRequest {
pub instance_id: u32,
}
#[derive(serde::Deserialize, Debug, Clone, PartialEq)]
pub struct GetGroupMuteResponse {
pub current_mute: bool,
}
pub struct GetGroupMuteOperation;
impl crate::operation::UPnPOperation for GetGroupMuteOperation {
type Request = GetGroupMuteOperationRequest;
type Response = GetGroupMuteResponse;
const SERVICE: crate::service::Service = crate::service::Service::GroupRenderingControl;
const ACTION: &'static str = "GetGroupMute";
fn build_payload(request: &Self::Request) -> Result<String, crate::operation::ValidationError> {
request.validate(crate::operation::ValidationLevel::Basic)?;
Ok(format!("<InstanceID>{}</InstanceID>", request.instance_id))
}
fn parse_response(xml: &xmltree::Element) -> Result<Self::Response, crate::error::ApiError> {
Ok(GetGroupMuteResponse {
current_mute: parse_sonos_bool(xml, "CurrentMute"),
})
}
}
pub fn get_group_mute_operation() -> crate::operation::OperationBuilder<GetGroupMuteOperation> {
let request = GetGroupMuteOperationRequest { instance_id: 0 };
crate::operation::OperationBuilder::new(request)
}
impl Validate for GetGroupMuteOperationRequest {}
pub use get_group_mute_operation as get_group_mute;
define_upnp_operation! {
operation: SetGroupMuteOperation,
action: "SetGroupMute",
service: GroupRenderingControl,
request: {
desired_mute: bool,
},
response: (),
payload: |req| {
format!(
"<InstanceID>{}</InstanceID><DesiredMute>{}</DesiredMute>",
req.instance_id,
if req.desired_mute { "1" } else { "0" }
)
},
parse: |_xml| Ok(()),
}
impl Validate for SetGroupMuteOperationRequest {}
pub use set_group_mute_operation as set_group_mute;
define_upnp_operation! {
operation: SnapshotGroupVolumeOperation,
action: "SnapshotGroupVolume",
service: GroupRenderingControl,
request: {},
response: (),
payload: |req| {
format!("<InstanceID>{}</InstanceID>", req.instance_id)
},
parse: |_xml| Ok(()),
}
impl Validate for SnapshotGroupVolumeOperationRequest {}
pub use snapshot_group_volume_operation as snapshot_group_volume;
pub const SERVICE: crate::Service = crate::Service::GroupRenderingControl;
pub fn subscribe(
client: &crate::SonosClient,
ip: &str,
callback_url: &str,
) -> crate::Result<crate::ManagedSubscription> {
client.subscribe(ip, SERVICE, callback_url)
}
pub fn subscribe_with_timeout(
client: &crate::SonosClient,
ip: &str,
callback_url: &str,
timeout_seconds: u32,
) -> crate::Result<crate::ManagedSubscription> {
client.subscribe_with_timeout(ip, SERVICE, callback_url, timeout_seconds)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operation::UPnPOperation;
#[test]
fn test_get_group_volume_builder() {
let op = get_group_volume().build().unwrap();
assert_eq!(op.metadata().action, "GetGroupVolume");
assert_eq!(op.request().instance_id, 0);
}
#[test]
fn test_set_group_volume_builder() {
let op = set_group_volume(75).build().unwrap();
assert_eq!(op.request().desired_volume, 75);
assert_eq!(op.request().instance_id, 0);
assert_eq!(op.metadata().action, "SetGroupVolume");
}
#[test]
fn test_set_relative_group_volume_builder() {
let op = set_relative_group_volume(10).build().unwrap();
assert_eq!(op.request().adjustment, 10);
assert_eq!(op.request().instance_id, 0);
assert_eq!(op.metadata().action, "SetRelativeGroupVolume");
}
#[test]
fn test_get_group_mute_builder() {
let op = get_group_mute().build().unwrap();
assert_eq!(op.metadata().action, "GetGroupMute");
assert_eq!(op.request().instance_id, 0);
}
#[test]
fn test_set_group_mute_builder() {
let op = set_group_mute(true).build().unwrap();
assert!(op.request().desired_mute);
assert_eq!(op.request().instance_id, 0);
assert_eq!(op.metadata().action, "SetGroupMute");
}
#[test]
fn test_snapshot_group_volume_builder() {
let op = snapshot_group_volume().build().unwrap();
assert_eq!(op.metadata().action, "SnapshotGroupVolume");
assert_eq!(op.request().instance_id, 0);
}
#[test]
fn test_get_group_volume_payload() {
let request = GetGroupVolumeOperationRequest { instance_id: 0 };
let payload = GetGroupVolumeOperation::build_payload(&request).unwrap();
assert_eq!(payload, "<InstanceID>0</InstanceID>");
}
#[test]
fn test_set_group_volume_payload() {
let request = SetGroupVolumeOperationRequest {
instance_id: 0,
desired_volume: 75,
};
let payload = SetGroupVolumeOperation::build_payload(&request).unwrap();
assert!(payload.contains("<InstanceID>0</InstanceID>"));
assert!(payload.contains("<DesiredVolume>75</DesiredVolume>"));
}
#[test]
fn test_set_relative_group_volume_payload() {
let request = SetRelativeGroupVolumeOperationRequest {
instance_id: 0,
adjustment: -25,
};
let payload = SetRelativeGroupVolumeOperation::build_payload(&request).unwrap();
assert!(payload.contains("<InstanceID>0</InstanceID>"));
assert!(payload.contains("<Adjustment>-25</Adjustment>"));
}
#[test]
fn test_get_group_mute_payload() {
let request = GetGroupMuteOperationRequest { instance_id: 0 };
let payload = GetGroupMuteOperation::build_payload(&request).unwrap();
assert_eq!(payload, "<InstanceID>0</InstanceID>");
}
#[test]
fn test_get_group_mute_parse_response_true() {
let xml_str =
r#"<GetGroupMuteResponse><CurrentMute>1</CurrentMute></GetGroupMuteResponse>"#;
let xml = xmltree::Element::parse(xml_str.as_bytes()).unwrap();
let response = GetGroupMuteOperation::parse_response(&xml).unwrap();
assert!(response.current_mute);
}
#[test]
fn test_get_group_mute_parse_response_false() {
let xml_str =
r#"<GetGroupMuteResponse><CurrentMute>0</CurrentMute></GetGroupMuteResponse>"#;
let xml = xmltree::Element::parse(xml_str.as_bytes()).unwrap();
let response = GetGroupMuteOperation::parse_response(&xml).unwrap();
assert!(!response.current_mute);
}
#[test]
fn test_set_group_mute_payload_true() {
let request = SetGroupMuteOperationRequest {
instance_id: 0,
desired_mute: true,
};
let payload = SetGroupMuteOperation::build_payload(&request).unwrap();
assert!(payload.contains("<InstanceID>0</InstanceID>"));
assert!(payload.contains("<DesiredMute>1</DesiredMute>"));
}
#[test]
fn test_set_group_mute_payload_false() {
let request = SetGroupMuteOperationRequest {
instance_id: 0,
desired_mute: false,
};
let payload = SetGroupMuteOperation::build_payload(&request).unwrap();
assert!(payload.contains("<InstanceID>0</InstanceID>"));
assert!(payload.contains("<DesiredMute>0</DesiredMute>"));
}
#[test]
fn test_snapshot_group_volume_payload() {
let request = SnapshotGroupVolumeOperationRequest { instance_id: 0 };
let payload = SnapshotGroupVolumeOperation::build_payload(&request).unwrap();
assert_eq!(payload, "<InstanceID>0</InstanceID>");
}
#[test]
fn test_set_group_volume_rejects_over_100() {
let request = SetGroupVolumeOperationRequest {
instance_id: 0,
desired_volume: 101,
};
assert!(request.validate_basic().is_err());
}
#[test]
fn test_set_group_volume_accepts_boundary_values() {
let request = SetGroupVolumeOperationRequest {
instance_id: 0,
desired_volume: 0,
};
assert!(request.validate_basic().is_ok());
let request = SetGroupVolumeOperationRequest {
instance_id: 0,
desired_volume: 100,
};
assert!(request.validate_basic().is_ok());
}
#[test]
fn test_set_relative_group_volume_rejects_under_minus_100() {
let request = SetRelativeGroupVolumeOperationRequest {
instance_id: 0,
adjustment: -101,
};
assert!(request.validate_basic().is_err());
}
#[test]
fn test_set_relative_group_volume_rejects_over_100() {
let request = SetRelativeGroupVolumeOperationRequest {
instance_id: 0,
adjustment: 101,
};
assert!(request.validate_basic().is_err());
}
#[test]
fn test_set_relative_group_volume_accepts_boundary_values() {
let request = SetRelativeGroupVolumeOperationRequest {
instance_id: 0,
adjustment: -100,
};
assert!(request.validate_basic().is_ok());
let request = SetRelativeGroupVolumeOperationRequest {
instance_id: 0,
adjustment: 0,
};
assert!(request.validate_basic().is_ok());
let request = SetRelativeGroupVolumeOperationRequest {
instance_id: 0,
adjustment: 100,
};
assert!(request.validate_basic().is_ok());
}
#[test]
fn test_service_constant() {
assert_eq!(SERVICE, crate::Service::GroupRenderingControl);
}
#[test]
fn test_subscribe_function_signature() {
let client = crate::SonosClient::new();
let _subscribe_fn = || subscribe(&client, "192.168.1.100", "http://callback.url");
}
#[test]
fn test_subscribe_with_timeout_function_signature() {
let client = crate::SonosClient::new();
let _subscribe_fn =
|| subscribe_with_timeout(&client, "192.168.1.100", "http://callback.url", 3600);
}
}