feat: add soap adapter foundation
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
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")
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user