// Xibo player Rust implementation, (c) 2022-2024 Georg Brandl. // Licensed under the GNU AGPL, version 3 or later. //! Build script that processes the XMDS WSDL file into Rust code //! that can call the services using ureq. use std::{fs::File, io::Write, path::PathBuf}; use elementtree::Element; const WSDLFILE: &str = "xmds_v5.wsdl"; const MESSAGE: &str = "{http://schemas.xmlsoap.org/wsdl/}message"; const PORT_TYPE: &str = "{http://schemas.xmlsoap.org/wsdl/}portType"; const OPERATION: &str = "{http://schemas.xmlsoap.org/wsdl/}operation"; const HEADER: &str = "// Auto-generated by build.rs. use std::{fmt, str::FromStr}; use anyhow::{bail, Context, Result}; use elementtree::Element; use ureq::Agent; use crate::util::Base64Field; "; const SERVICE_IMPL: &str = r###"pub struct Service { baseuri: String, agent: Agent, } impl Service { pub fn new(baseuri: String, agent: Agent) -> Self { Self { baseuri, agent } } fn request + fmt::Debug>(&mut self, name: &str, body: impl fmt::Display) -> Result { log::debug!("calling XMDS {}", name); self.agent.post(&self.baseuri).send_string(&format!(r#" {} "#, name, body, name)) .with_context(|| format!("sending {} SOAP request", name))? .into_string().with_context(|| format!("decoding {} SOAP response", name))? .parse().with_context(|| format!("parsing {} SOAP response", name)) } "###; fn main() { println!("cargo:rerun-if-changed={}", WSDLFILE); let tree = Element::from_reader(File::open(WSDLFILE).unwrap()).unwrap(); let out_dir = PathBuf::from(std::env::var("OUT_DIR").unwrap()); let mut out = File::create(out_dir.join("xmds_soap.rs")).unwrap(); writeln!(out, "{}", HEADER).unwrap(); // Go through all messages for msg in tree.find_all(MESSAGE) { let name = msg.get_attr("name").unwrap().to_string(); let is_req = name.ends_with("Request"); let mut msg_members = vec![]; // Collect members of the structure for part in msg.children() { let pname = part.get_attr("name").unwrap().to_string(); let rsname = if pname == "type" { "r#type" } else { &*pname }.to_string(); let xsdtype = part.get_attr("type").unwrap().to_string(); let rstype = match &*xsdtype { "xsd:string" => if is_req { "&'a str" } else { "String" }, "xsd:int" => "i64", "xsd:double" => "f64", "xsd:base64Binary" => "Base64Field", "xsd:boolean" => "bool", _ => unimplemented!() }; msg_members.push((pname, rsname, xsdtype, rstype)); } // Write struct definition writeln!(out, "#[derive(Debug)] pub struct {}{} {{", name, if is_req { "<'a>" } else { "" }).unwrap(); for (_, rsname, _, rstype) in &msg_members { writeln!(out, " pub {}: {},", rsname, rstype).unwrap(); } writeln!(out, "}}\n").unwrap(); if is_req { // Write serialization code if it's a request type writeln!(out, r#"impl<'a> fmt::Display for {}<'a> {{ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {{"#, name).unwrap(); for (pname, rsname, xsdtype, _) in &msg_members { writeln!(out, r#" write!(f, "<{} xsi:type=\"{}\">{{}}", self.{})?;"#, pname, xsdtype, pname, rsname).unwrap(); } writeln!(out, r#" Ok(()) }} }} "#).unwrap(); } else { // Write deserialization code if it's a response type writeln!(out, r#"impl FromStr for {} {{ type Err = anyhow::Error; fn from_str(s: &str) -> Result {{ let tree = Element::from_reader(&mut s.as_bytes()).context("XML parse")?; let tns = tree.get_child(0).and_then(|c| c.get_child(0)) .context("missing SOAP envelope")?; if tns.tag().name() != "{}" {{ if tns.tag().name() == "Fault" {{ bail!("got SOAP fault: {{}}", tns.find("faultstring") .map_or("no fault string", |fs| fs.text())); }} else {{ bail!("got unexpected content tag: {{}}", tns.tag().name()); }} }} Ok(Self {{"#, name, name).unwrap(); for (pname, rsname, _, _) in &msg_members { writeln!(out, r#" {}: tns.find("{}").context("missing {}")?.text().parse().context("parsing {}")?,"#, rsname, pname, pname, pname).unwrap(); } writeln!(out, " }}) }} }}\n").unwrap(); } } // Write Service impl with methods for each operation writeln!(out, "{}", SERVICE_IMPL).unwrap(); for ptype in tree.find_all(PORT_TYPE) { for port in ptype.find_all(OPERATION) { let name = port.get_attr("name").unwrap(); let inp = port.get_child(1).unwrap() .get_attr("message").unwrap() .trim_start_matches("tns:"); let outp = port.get_child(2).unwrap() .get_attr("message").unwrap() .trim_start_matches("tns:"); writeln!(out, " pub fn {}(&mut self, arg: {}) -> Result<{}> {{ self.request(\"{}\", arg) }}\n", name, inp, outp, name).unwrap(); } } writeln!(out, "}}").unwrap(); }