use std::collections::BTreeMap; use roxmltree::Document; use crate::{SoapAdapterError, SoapOperationSummary, SoapPortSummary, SoapServiceSummary}; #[derive(Clone, Debug)] struct BindingSummary { soap_version: String, operations: Vec, } pub fn inspect_wsdl(payload: &[u8]) -> Result, SoapAdapterError> { let text = std::str::from_utf8(payload).map_err(|_| SoapAdapterError::EmptyBody)?; let document = Document::parse(text)?; let bindings = collect_bindings(&document); let services = document .descendants() .filter(|node| node.is_element() && node.tag_name().name() == "service") .map(|service| { let service_name = service.attribute("name").unwrap_or_default().to_owned(); let ports = service .children() .filter(|node| node.is_element() && node.tag_name().name() == "port") .map(|port| { let port_name = port.attribute("name").unwrap_or_default().to_owned(); let binding_name = local_name(port.attribute("binding").unwrap_or_default()); let endpoint = port .descendants() .find(|node| node.is_element() && node.tag_name().name() == "address") .and_then(|node| node.attribute("location")) .map(ToOwned::to_owned); let binding = bindings .get(&binding_name) .cloned() .unwrap_or(BindingSummary { soap_version: "soap_11".to_owned(), operations: Vec::new(), }); SoapPortSummary { port_name, binding_name, endpoint, soap_version: binding.soap_version, operations: binding.operations, } }) .collect(); SoapServiceSummary { service_name, ports, } }) .collect(); Ok(services) } fn collect_bindings(document: &Document<'_>) -> BTreeMap { document .descendants() .filter(|node| node.is_element() && node.tag_name().name() == "binding") .map(|binding| { let name = binding.attribute("name").unwrap_or_default().to_owned(); let soap_binding = binding .children() .find(|node| node.is_element() && node.tag_name().name() == "binding"); let soap_namespace = soap_binding .and_then(|node| node.tag_name().namespace()) .unwrap_or("http://schemas.xmlsoap.org/wsdl/soap/"); let soap_version = if soap_namespace.contains("soap12") { "soap_12" } else { "soap_11" } .to_owned(); let binding_style = soap_binding .and_then(|node| node.attribute("style")) .map(|value| match value { "rpc" => "rpc_literal", _ => "document_literal", }) .unwrap_or("document_literal") .to_owned(); let operations = binding .children() .filter(|node| node.is_element() && node.tag_name().name() == "operation") .map(|operation| SoapOperationSummary { operation_name: operation.attribute("name").unwrap_or_default().to_owned(), soap_action: operation .children() .find(|node| node.is_element() && node.tag_name().name() == "operation") .and_then(|node| node.attribute("soapAction")) .map(ToOwned::to_owned), binding_style: operation .children() .find(|node| node.is_element() && node.tag_name().name() == "operation") .and_then(|node| node.attribute("style")) .map(|value| match value { "rpc" => "rpc_literal", _ => "document_literal", }) .unwrap_or(binding_style.as_str()) .to_owned(), }) .collect(); ( name, BindingSummary { soap_version, operations, }, ) }) .collect() } fn local_name(value: &str) -> String { value.rsplit(':').next().unwrap_or(value).to_owned() } #[cfg(test)] mod tests { use super::inspect_wsdl; #[test] fn parses_services_ports_and_operations_from_wsdl() { let services = inspect_wsdl( br#" "#, ) .unwrap(); assert_eq!(services[0].service_name, "LeadService"); assert_eq!(services[0].ports[0].port_name, "LeadPort"); assert_eq!(services[0].ports[0].soap_version, "soap_11"); assert_eq!( services[0].ports[0].operations[0].operation_name, "CreateLead" ); assert_eq!( services[0].ports[0].operations[0].soap_action.as_deref(), Some("urn:createLead") ); } }