1use crate::{
2 find_in_xml,
3 utils::{self, HttpResponseExt, HyperBodyExt},
4 Error,
5};
6use bytes::Bytes;
7use http::Uri;
8use http_body_util::Empty;
9use hyper_util::rt::TokioExecutor;
10use roxmltree::{Document, Node};
11use ssdp_client::URN;
12use std::rc::Rc;
13
14mod action;
15mod state_variable;
16pub use action::*;
17pub use state_variable::*;
18
19#[derive(Debug)]
23pub struct SCPD {
24 urn: URN,
25 state_variables: Vec<Rc<StateVariable>>,
26 actions: Vec<Action>,
27}
28impl SCPD {
29 pub fn urn(&self) -> &URN {
30 &self.urn
31 }
32 pub fn state_variables(&self) -> &[Rc<StateVariable>] {
33 &self.state_variables
34 }
35 pub fn actions(&self) -> &[Action] {
36 &self.actions
37 }
38
39 pub(crate) async fn from_url(url: &Uri, urn: URN) -> Result<Self, Error> {
42 let body = hyper_util::client::legacy::Client::builder(TokioExecutor::new())
43 .build_http::<Empty<Bytes>>()
44 .get(url.clone())
45 .await?
46 .err_if_not_200()?
47 .into_body()
48 .bytes()
49 .await?;
50 let body = std::str::from_utf8(&body)?;
51
52 let document = Document::parse(body)?;
53 let scpd = utils::find_root(&document, "scpd", "Service Control Point Definition")?;
54
55 #[allow(non_snake_case)]
56 let (state_variables, actions) = find_in_xml! { scpd => serviceStateTable, actionList };
57
58 let state_variables: Vec<_> = state_variables
59 .children()
60 .filter(Node::is_element)
61 .map(StateVariable::from_xml)
62 .map(|sv| sv.map(Rc::new))
63 .collect::<Result<_, _>>()?;
64 let actions = actions
65 .children()
66 .filter(Node::is_element)
67 .map(|node| Action::from_xml(node, &state_variables))
68 .collect::<Result<_, _>>()?;
69
70 Ok(Self {
71 urn,
72 state_variables,
73 actions,
74 })
75 }
76}