use alloc::format;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WsdlVersion {
V11,
V20,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Operation {
pub name: String,
pub input_type: String,
pub output_type: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WsdlGenerator {
service_name: String,
target_namespace: String,
endpoint_url: String,
operations: Vec<Operation>,
version: WsdlVersion,
}
impl WsdlGenerator {
#[must_use]
pub fn new(service_name: &str, namespace: &str, endpoint: &str, version: WsdlVersion) -> Self {
Self {
service_name: service_name.into(),
target_namespace: namespace.into(),
endpoint_url: endpoint.into(),
operations: Vec::new(),
version,
}
}
#[must_use]
pub fn operation(mut self, op: Operation) -> Self {
self.operations.push(op);
self
}
#[must_use]
pub fn render(&self) -> String {
match self.version {
WsdlVersion::V11 => self.render_v11(),
WsdlVersion::V20 => self.render_v20(),
}
}
fn render_v11(&self) -> String {
let mut out = String::new();
out.push_str("<?xml version=\"1.0\"?>\n");
out.push_str(&format!(
"<definitions name=\"{}\" targetNamespace=\"{}\" \
xmlns=\"http://schemas.xmlsoap.org/wsdl/\" \
xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" \
xmlns:tns=\"{}\" \
xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\">\n",
self.service_name, self.target_namespace, self.target_namespace
));
for op in &self.operations {
out.push_str(&format!(
" <message name=\"{}Request\"><part name=\"body\" element=\"tns:{}\"/></message>\n",
op.name, op.input_type
));
out.push_str(&format!(
" <message name=\"{}Response\"><part name=\"body\" element=\"tns:{}\"/></message>\n",
op.name, op.output_type
));
}
out.push_str(&format!(
" <portType name=\"{}PortType\">\n",
self.service_name
));
for op in &self.operations {
out.push_str(&format!(
" <operation name=\"{0}\">\n <input message=\"tns:{0}Request\"/>\n <output message=\"tns:{0}Response\"/>\n </operation>\n",
op.name
));
}
out.push_str(" </portType>\n");
out.push_str(&format!(
" <binding name=\"{0}SoapBinding\" type=\"tns:{0}PortType\">\n <soap:binding style=\"document\" transport=\"http://schemas.xmlsoap.org/soap/http\"/>\n",
self.service_name
));
for op in &self.operations {
out.push_str(&format!(
" <operation name=\"{}\"><soap:operation soapAction=\"{}/{}\"/><input><soap:body use=\"literal\"/></input><output><soap:body use=\"literal\"/></output></operation>\n",
op.name, self.target_namespace, op.name
));
}
out.push_str(" </binding>\n");
out.push_str(&format!(
" <service name=\"{0}\">\n <port name=\"{0}Port\" binding=\"tns:{0}SoapBinding\">\n <soap:address location=\"{1}\"/>\n </port>\n </service>\n",
self.service_name, self.endpoint_url
));
out.push_str("</definitions>\n");
out
}
fn render_v20(&self) -> String {
let mut out = String::new();
out.push_str("<?xml version=\"1.0\"?>\n");
out.push_str(&format!(
"<description xmlns=\"http://www.w3.org/ns/wsdl\" \
targetNamespace=\"{}\" \
xmlns:tns=\"{}\" \
xmlns:wsoap=\"http://www.w3.org/ns/wsdl/soap\">\n",
self.target_namespace, self.target_namespace
));
out.push_str(&format!(
" <interface name=\"{}Interface\">\n",
self.service_name
));
for op in &self.operations {
out.push_str(&format!(
" <operation name=\"{0}\" pattern=\"http://www.w3.org/ns/wsdl/in-out\">\n <input element=\"tns:{1}\"/>\n <output element=\"tns:{2}\"/>\n </operation>\n",
op.name, op.input_type, op.output_type
));
}
out.push_str(" </interface>\n");
out.push_str(&format!(
" <binding name=\"{0}SoapBinding\" interface=\"tns:{0}Interface\" type=\"http://www.w3.org/ns/wsdl/soap\" wsoap:protocol=\"http://www.w3.org/2003/05/soap/bindings/HTTP/\"/>\n",
self.service_name
));
out.push_str(&format!(
" <service name=\"{0}\" interface=\"tns:{0}Interface\">\n <endpoint name=\"{0}Endpoint\" binding=\"tns:{0}SoapBinding\" address=\"{1}\"/>\n </service>\n",
self.service_name, self.endpoint_url
));
out.push_str("</description>\n");
out
}
#[must_use]
pub fn operation_count(&self) -> usize {
self.operations.len()
}
}
#[cfg(test)]
#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
mod tests {
use super::*;
fn echo_op() -> Operation {
Operation {
name: "Echo".into(),
input_type: "EchoRequest".into(),
output_type: "EchoResponse".into(),
}
}
#[test]
fn wsdl_11_emits_definitions_root() {
let g = WsdlGenerator::new(
"TraderService",
"http://demo/trader",
"http://localhost/svc",
WsdlVersion::V11,
)
.operation(echo_op());
let xml = g.render();
assert!(xml.contains("<definitions name=\"TraderService\""));
assert!(xml.contains("<portType name=\"TraderServicePortType\""));
assert!(xml.contains("<message name=\"EchoRequest\""));
assert!(xml.contains("<service name=\"TraderService\""));
assert!(xml.contains("schemas.xmlsoap.org/wsdl/"));
}
#[test]
fn wsdl_20_emits_description_root() {
let g = WsdlGenerator::new(
"TraderService",
"http://demo/trader",
"http://localhost/svc",
WsdlVersion::V20,
)
.operation(echo_op());
let xml = g.render();
assert!(xml.contains("<description xmlns=\"http://www.w3.org/ns/wsdl\""));
assert!(xml.contains("<interface name=\"TraderServiceInterface\">"));
assert!(xml.contains("<endpoint name=\"TraderServiceEndpoint\""));
}
#[test]
fn wsdl_11_includes_all_operation_messages() {
let g = WsdlGenerator::new("S", "http://demo/", "http://demo/svc", WsdlVersion::V11)
.operation(echo_op())
.operation(Operation {
name: "Ping".into(),
input_type: "PingRequest".into(),
output_type: "PingResponse".into(),
});
let xml = g.render();
assert!(xml.contains("EchoRequest"));
assert!(xml.contains("EchoResponse"));
assert!(xml.contains("PingRequest"));
assert!(xml.contains("PingResponse"));
}
#[test]
fn wsdl_11_binding_uses_soap_http_transport() {
let g = WsdlGenerator::new("S", "http://demo/", "http://demo/svc", WsdlVersion::V11)
.operation(echo_op());
let xml = g.render();
assert!(xml.contains("transport=\"http://schemas.xmlsoap.org/soap/http\""));
}
#[test]
fn wsdl_20_binding_targets_soap_protocol() {
let g = WsdlGenerator::new("S", "http://demo/", "http://demo/svc", WsdlVersion::V20)
.operation(echo_op());
let xml = g.render();
assert!(xml.contains("wsoap:protocol="));
assert!(xml.contains("soap/bindings/HTTP"));
}
#[test]
fn endpoint_address_is_included() {
let g = WsdlGenerator::new(
"S",
"http://demo/",
"http://example.org:8080/api",
WsdlVersion::V11,
)
.operation(echo_op());
let xml = g.render();
assert!(xml.contains("location=\"http://example.org:8080/api\""));
}
}