huelib2/bridge/
description.rs1use mime::Mime;
2use serde::{de::Error, Deserialize, Deserializer};
3use std::{net::IpAddr, str::FromStr};
4use url::Url;
5use uuid::Uuid;
6
7#[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#[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 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#[cfg_attr(docsrs, doc(cfg(feature = "upnp-description")))]
41#[derive(Clone, Debug, Eq, PartialEq, Hash, Deserialize)]
42pub struct DescriptionSpecVersion {
43 pub major: usize,
45 pub minor: usize,
47}
48
49#[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#[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}