Files
crank/crates/crank-adapter-soap/src/wsdl.rs
T
2026-04-07 00:00:19 +03:00

166 lines
6.2 KiB
Rust

use std::collections::BTreeMap;
use roxmltree::Document;
use crate::{SoapAdapterError, SoapOperationSummary, SoapPortSummary, SoapServiceSummary};
#[derive(Clone, Debug)]
struct BindingSummary {
soap_version: String,
operations: Vec<SoapOperationSummary>,
}
pub fn inspect_wsdl(payload: &[u8]) -> Result<Vec<SoapServiceSummary>, 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<String, BindingSummary> {
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#"<?xml version="1.0"?>
<definitions xmlns="http://schemas.xmlsoap.org/wsdl/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tns="urn:crm"
targetNamespace="urn:crm">
<binding name="LeadBinding" type="tns:LeadPortType">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http" style="document"/>
<operation name="CreateLead">
<soap:operation soapAction="urn:createLead"/>
</operation>
</binding>
<service name="LeadService">
<port name="LeadPort" binding="tns:LeadBinding">
<soap:address location="https://soap.example.com/lead"/>
</port>
</service>
</definitions>"#,
)
.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")
);
}
}