huelib2/bridge/
description.rs

1use mime::Mime;
2use serde::{de::Error, Deserialize, Deserializer};
3use std::{net::IpAddr, str::FromStr};
4use url::Url;
5use uuid::Uuid;
6
7/// Returns the description of the bridge with the given IP address.
8///
9/// This method internally calls [`Description::get`].
10#[cfg_attr(docsrs, doc(cfg(feature = "upnp-description")))]
11pub fn description(ip_address: IpAddr) -> crate::Result<Description> {
12    Description::get(ip_address)
13}
14
15/// Description of a bridge.
16#[cfg_attr(docsrs, doc(cfg(feature = "upnp-description")))]
17#[allow(missing_docs)]
18#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct Description {
21    pub spec_version: DescriptionSpecVersion,
22    #[serde(rename = "URLBase")]
23    pub url_base: Url,
24    pub device: DescriptionDevice,
25}
26
27impl Description {
28    /// Returns the description of the bridge with the given IP address.
29    ///
30    /// This method sends a HTTP GET request to `http://<bridge_ip_address>/description.xml` to get
31    /// the descriptor file.
32    pub fn get(ip_address: IpAddr) -> crate::Result<Self> {
33        let url = format!("http://{}/description.xml", ip_address);
34        let http_response = ureq::get(&url).call()?;
35        Ok(serde_xml_rs::from_reader(http_response.into_reader())?)
36    }
37}
38
39/// Spec version type of a description.
40#[cfg_attr(docsrs, doc(cfg(feature = "upnp-description")))]
41#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
42pub struct DescriptionSpecVersion {
43    /// The major version.
44    pub major: usize,
45    /// The minor version.
46    pub minor: usize,
47}
48
49/// Device type of a description.
50#[cfg_attr(docsrs, doc(cfg(feature = "upnp-description")))]
51#[allow(missing_docs)]
52#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
53#[serde(rename_all = "camelCase")]
54pub struct DescriptionDevice {
55    pub device_type: String,
56    pub friendly_name: String,
57    pub manufacturer: String,
58    #[serde(rename = "manufacturerURL")]
59    pub manufacturer_url: Url,
60    pub model_description: String,
61    pub model_name: String,
62    pub model_number: String,
63    #[serde(rename = "modelURL")]
64    pub model_url: Url,
65    pub serial_number: String,
66    #[serde(rename = "UDN", deserialize_with = "deserialize_uuid")]
67    pub udn: Uuid,
68    #[serde(rename = "presentationURL")]
69    pub presentation_url: String,
70    pub icon_list: Vec<DescriptionIcon>,
71}
72
73fn deserialize_uuid<'de, D>(deserializer: D) -> Result<Uuid, D::Error>
74where
75    D: Deserializer<'de>,
76{
77    let value = String::deserialize(deserializer)?;
78    let value = value.trim_start_matches("uuid:");
79    Uuid::from_str(value).map_err(D::Error::custom)
80}
81
82/// Icon type of a description.
83#[cfg_attr(docsrs, doc(cfg(feature = "upnp-description")))]
84#[allow(missing_docs)]
85#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
86#[serde(from = "deserialize::DescriptionIconWrapper")]
87pub struct DescriptionIcon {
88    pub mimetype: Mime,
89    pub height: usize,
90    pub width: usize,
91    pub depth: usize,
92    pub url: String,
93}
94
95impl From<deserialize::DescriptionIconWrapper> for DescriptionIcon {
96    fn from(value: deserialize::DescriptionIconWrapper) -> Self {
97        Self {
98            mimetype: value.icon.mimetype,
99            height: value.icon.height,
100            width: value.icon.width,
101            depth: value.icon.depth,
102            url: value.icon.url,
103        }
104    }
105}
106
107mod deserialize {
108    use super::*;
109
110    #[derive(Deserialize)]
111    pub(super) struct DescriptionIconWrapper {
112        pub(super) icon: DescriptionIcon,
113    }
114
115    #[derive(Deserialize)]
116    pub(super) struct DescriptionIcon {
117        #[serde(deserialize_with = "deserialize_mime")]
118        pub(super) mimetype: Mime,
119        pub(super) height: usize,
120        pub(super) width: usize,
121        pub(super) depth: usize,
122        pub(super) url: String,
123    }
124
125    fn deserialize_mime<'de, D>(deserializer: D) -> Result<Mime, D::Error>
126    where
127        D: Deserializer<'de>,
128    {
129        let value = String::deserialize(deserializer)?;
130        Mime::from_str(&value).map_err(D::Error::custom)
131    }
132}