1use alloc::format;
14use alloc::string::String;
15use alloc::vec::Vec;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum WsdlVersion {
20 V11,
22 V20,
24}
25
26#[derive(Debug, Clone, PartialEq, Eq)]
28pub struct Operation {
29 pub name: String,
31 pub input_type: String,
33 pub output_type: String,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct WsdlGenerator {
40 service_name: String,
41 target_namespace: String,
42 endpoint_url: String,
43 operations: Vec<Operation>,
44 version: WsdlVersion,
45}
46
47impl WsdlGenerator {
48 #[must_use]
50 pub fn new(service_name: &str, namespace: &str, endpoint: &str, version: WsdlVersion) -> Self {
51 Self {
52 service_name: service_name.into(),
53 target_namespace: namespace.into(),
54 endpoint_url: endpoint.into(),
55 operations: Vec::new(),
56 version,
57 }
58 }
59
60 #[must_use]
62 pub fn operation(mut self, op: Operation) -> Self {
63 self.operations.push(op);
64 self
65 }
66
67 #[must_use]
69 pub fn render(&self) -> String {
70 match self.version {
71 WsdlVersion::V11 => self.render_v11(),
72 WsdlVersion::V20 => self.render_v20(),
73 }
74 }
75
76 fn render_v11(&self) -> String {
77 let mut out = String::new();
78 out.push_str("<?xml version=\"1.0\"?>\n");
79 out.push_str(&format!(
80 "<definitions name=\"{}\" targetNamespace=\"{}\" \
81xmlns=\"http://schemas.xmlsoap.org/wsdl/\" \
82xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" \
83xmlns:tns=\"{}\" \
84xmlns:soap=\"http://schemas.xmlsoap.org/wsdl/soap/\">\n",
85 self.service_name, self.target_namespace, self.target_namespace
86 ));
87 for op in &self.operations {
89 out.push_str(&format!(
90 " <message name=\"{}Request\"><part name=\"body\" element=\"tns:{}\"/></message>\n",
91 op.name, op.input_type
92 ));
93 out.push_str(&format!(
94 " <message name=\"{}Response\"><part name=\"body\" element=\"tns:{}\"/></message>\n",
95 op.name, op.output_type
96 ));
97 }
98 out.push_str(&format!(
100 " <portType name=\"{}PortType\">\n",
101 self.service_name
102 ));
103 for op in &self.operations {
104 out.push_str(&format!(
105 " <operation name=\"{0}\">\n <input message=\"tns:{0}Request\"/>\n <output message=\"tns:{0}Response\"/>\n </operation>\n",
106 op.name
107 ));
108 }
109 out.push_str(" </portType>\n");
110 out.push_str(&format!(
112 " <binding name=\"{0}SoapBinding\" type=\"tns:{0}PortType\">\n <soap:binding style=\"document\" transport=\"http://schemas.xmlsoap.org/soap/http\"/>\n",
113 self.service_name
114 ));
115 for op in &self.operations {
116 out.push_str(&format!(
117 " <operation name=\"{}\"><soap:operation soapAction=\"{}/{}\"/><input><soap:body use=\"literal\"/></input><output><soap:body use=\"literal\"/></output></operation>\n",
118 op.name, self.target_namespace, op.name
119 ));
120 }
121 out.push_str(" </binding>\n");
122 out.push_str(&format!(
124 " <service name=\"{0}\">\n <port name=\"{0}Port\" binding=\"tns:{0}SoapBinding\">\n <soap:address location=\"{1}\"/>\n </port>\n </service>\n",
125 self.service_name, self.endpoint_url
126 ));
127 out.push_str("</definitions>\n");
128 out
129 }
130
131 fn render_v20(&self) -> String {
132 let mut out = String::new();
133 out.push_str("<?xml version=\"1.0\"?>\n");
134 out.push_str(&format!(
135 "<description xmlns=\"http://www.w3.org/ns/wsdl\" \
136targetNamespace=\"{}\" \
137xmlns:tns=\"{}\" \
138xmlns:wsoap=\"http://www.w3.org/ns/wsdl/soap\">\n",
139 self.target_namespace, self.target_namespace
140 ));
141 out.push_str(&format!(
143 " <interface name=\"{}Interface\">\n",
144 self.service_name
145 ));
146 for op in &self.operations {
147 out.push_str(&format!(
148 " <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",
149 op.name, op.input_type, op.output_type
150 ));
151 }
152 out.push_str(" </interface>\n");
153 out.push_str(&format!(
155 " <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",
156 self.service_name
157 ));
158 out.push_str(&format!(
160 " <service name=\"{0}\" interface=\"tns:{0}Interface\">\n <endpoint name=\"{0}Endpoint\" binding=\"tns:{0}SoapBinding\" address=\"{1}\"/>\n </service>\n",
161 self.service_name, self.endpoint_url
162 ));
163 out.push_str("</description>\n");
164 out
165 }
166
167 #[must_use]
169 pub fn operation_count(&self) -> usize {
170 self.operations.len()
171 }
172}
173
174#[cfg(test)]
175#[allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
176mod tests {
177 use super::*;
178
179 fn echo_op() -> Operation {
180 Operation {
181 name: "Echo".into(),
182 input_type: "EchoRequest".into(),
183 output_type: "EchoResponse".into(),
184 }
185 }
186
187 #[test]
188 fn wsdl_11_emits_definitions_root() {
189 let g = WsdlGenerator::new(
190 "TraderService",
191 "http://demo/trader",
192 "http://localhost/svc",
193 WsdlVersion::V11,
194 )
195 .operation(echo_op());
196 let xml = g.render();
197 assert!(xml.contains("<definitions name=\"TraderService\""));
198 assert!(xml.contains("<portType name=\"TraderServicePortType\""));
199 assert!(xml.contains("<message name=\"EchoRequest\""));
200 assert!(xml.contains("<service name=\"TraderService\""));
201 assert!(xml.contains("schemas.xmlsoap.org/wsdl/"));
202 }
203
204 #[test]
205 fn wsdl_20_emits_description_root() {
206 let g = WsdlGenerator::new(
207 "TraderService",
208 "http://demo/trader",
209 "http://localhost/svc",
210 WsdlVersion::V20,
211 )
212 .operation(echo_op());
213 let xml = g.render();
214 assert!(xml.contains("<description xmlns=\"http://www.w3.org/ns/wsdl\""));
215 assert!(xml.contains("<interface name=\"TraderServiceInterface\">"));
216 assert!(xml.contains("<endpoint name=\"TraderServiceEndpoint\""));
217 }
218
219 #[test]
220 fn wsdl_11_includes_all_operation_messages() {
221 let g = WsdlGenerator::new("S", "http://demo/", "http://demo/svc", WsdlVersion::V11)
222 .operation(echo_op())
223 .operation(Operation {
224 name: "Ping".into(),
225 input_type: "PingRequest".into(),
226 output_type: "PingResponse".into(),
227 });
228 let xml = g.render();
229 assert!(xml.contains("EchoRequest"));
230 assert!(xml.contains("EchoResponse"));
231 assert!(xml.contains("PingRequest"));
232 assert!(xml.contains("PingResponse"));
233 }
234
235 #[test]
236 fn wsdl_11_binding_uses_soap_http_transport() {
237 let g = WsdlGenerator::new("S", "http://demo/", "http://demo/svc", WsdlVersion::V11)
238 .operation(echo_op());
239 let xml = g.render();
240 assert!(xml.contains("transport=\"http://schemas.xmlsoap.org/soap/http\""));
241 }
242
243 #[test]
244 fn wsdl_20_binding_targets_soap_protocol() {
245 let g = WsdlGenerator::new("S", "http://demo/", "http://demo/svc", WsdlVersion::V20)
246 .operation(echo_op());
247 let xml = g.render();
248 assert!(xml.contains("wsoap:protocol="));
249 assert!(xml.contains("soap/bindings/HTTP"));
250 }
251
252 #[test]
253 fn endpoint_address_is_included() {
254 let g = WsdlGenerator::new(
255 "S",
256 "http://demo/",
257 "http://example.org:8080/api",
258 WsdlVersion::V11,
259 )
260 .operation(echo_op());
261 let xml = g.render();
262 assert!(xml.contains("location=\"http://example.org:8080/api\""));
263 }
264}