wsdl/
wsdl.rs

1use roxmltree::{Document, ExpandedName, Node, NodeId};
2use thiserror::Error;
3
4type Result<'a, 'input, T> = std::result::Result<T, WsError>;
5
6#[derive(Error, Debug)]
7pub enum WsErrorMalformedType {
8    #[error("missing attribute \"{0}\"")]
9    MissingAttribute(String),
10    #[error("missing element \"{0}\"")]
11    MissingElement(String),
12}
13
14#[derive(Error, Debug)]
15pub enum WsErrorType {
16    #[error("The input WSDL document was malformed: {0}")]
17    MalformedWsdl(WsErrorMalformedType),
18    #[error("Attempt to refer to unknown element {0}")]
19    InvalidReference(String),
20    #[error("Node unexpectedly did not have a parent node")]
21    NoParentNode,
22}
23
24#[derive(Error, Debug)]
25pub struct WsError(pub NodeId, pub WsErrorType);
26
27impl WsError {
28    fn new(node: Node, typ: WsErrorType) -> Self {
29        Self(node.id(), typ)
30    }
31}
32
33impl std::fmt::Display for WsError {
34    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
35        f.write_fmt(format_args!("{}", self.1))
36    }
37}
38
39fn target_namespace<'a, 'input>(node: Node<'a, 'input>) -> Result<'a, 'input, &'a str> {
40    // Traverse the parents until we find the targetNamespace attribute.
41    let mut nparent = node.parent();
42    while let Some(parent) = nparent {
43        if let Some(ns) = parent.attribute("targetNamespace") {
44            return Ok(ns);
45        }
46
47        nparent = parent.parent();
48    }
49
50    Err(WsError::new(
51        node,
52        WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute(
53            "targetNamespace".to_string(),
54        )),
55    ))
56}
57
58fn resolve_qualified<'a, 'input: 'a>(
59    node: Node<'a, 'input>,
60    qualified_name: &'a str,
61) -> std::result::Result<ExpandedName<'a, 'a>, WsErrorType> {
62    if qualified_name.contains(":") {
63        let mut s = qualified_name.split(":");
64
65        let ns = s
66            .next()
67            .ok_or(WsErrorType::InvalidReference(qualified_name.to_string()))?;
68
69        let uri = node
70            .lookup_namespace_uri(Some(ns))
71            .ok_or(WsErrorType::InvalidReference(qualified_name.to_string()))?;
72
73        let name = s
74            .next()
75            .ok_or(WsErrorType::InvalidReference(qualified_name.to_string()))?;
76
77        Ok((uri, name).into())
78    } else {
79        Ok(qualified_name.into())
80    }
81}
82
83fn split_qualified(qualified_name: &str) -> std::result::Result<(Option<&str>, &str), WsErrorType> {
84    let (namespace, name) = {
85        if qualified_name.contains(":") {
86            let mut s = qualified_name.split(":");
87            let ns = s
88                .next()
89                .ok_or(WsErrorType::InvalidReference(qualified_name.to_string()))?;
90
91            let name = s
92                .next()
93                .ok_or(WsErrorType::InvalidReference(qualified_name.to_string()))?;
94
95            (Some(ns), name)
96        } else {
97            (None, qualified_name)
98        }
99    };
100
101    Ok((namespace, name))
102}
103
104// Given a qualified name such as `tns:MyAnnoyingXmlType`, look for an XML
105// node with both the name and element type.
106/*
107fn lookup_qualified<'a, 'input>(
108    root: Node<'a, 'input>,
109    name: &str,
110    tag: &str,
111) -> Option<Node<'a, 'input>> {
112    todo!()
113}
114*/
115
116/// Describes a WSDL `message`. These can otherwise be described as
117/// a list of function parameters.
118#[derive(Debug, Clone)]
119pub struct WsMessage<'a, 'input>(Node<'a, 'input>);
120
121impl<'a, 'input> WsMessage<'a, 'input> {
122    /// Retrieve the name of the message.
123    pub fn name(&self) -> Result<&'a str> {
124        self.0.attribute("name").ok_or(WsError::new(
125            self.0,
126            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
127        ))
128    }
129
130    /// Retrieve the parts of this message.
131    pub fn parts(&self) -> impl Iterator<Item = WsMessagePart> {
132        self.0
133            .children()
134            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "part")))
135            .map(|n| WsMessagePart(n))
136    }
137
138    /// Return the XML node this struct is associated with
139    pub fn node(&self) -> Node<'a, 'input> {
140        self.0
141    }
142}
143
144/// Describes a part of a WSDL message. This can otherwise be described
145/// as an individual function parameter.
146#[derive(Debug, Clone)]
147pub struct WsMessagePart<'a, 'input>(Node<'a, 'input>);
148
149impl<'a, 'input: 'a> WsMessagePart<'a, 'input> {
150    /// Retrieve the name of the part.
151    pub fn name(&self) -> Result<&'a str> {
152        self.0.attribute("name").ok_or(WsError::new(
153            self.0,
154            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
155        ))
156    }
157
158    /// Retrieve the typename of this parameter. This refers to a type defined
159    /// under the `wsdl:types` XML node.
160    pub fn typename(&self) -> Result<ExpandedName<'a, 'a>> {
161        let typename = self
162            .0
163            .attribute("element")
164            .or(self.0.attribute("type"))
165            .ok_or(WsError::new(
166                self.0,
167                WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute(
168                    "type".to_string(),
169                )),
170            ))?;
171
172        resolve_qualified(self.0, typename).map_err(|e| WsError::new(self.0, e))
173    }
174
175    /// Return the XML node this struct is associated with
176    pub fn node(&self) -> Node<'a, 'input> {
177        self.0
178    }
179}
180
181/// Describes a WSDL `portType`. These describe groups of operations.
182#[derive(Debug, Clone)]
183pub struct WsPortType<'a, 'input>(Node<'a, 'input>);
184
185impl<'a, 'input> WsPortType<'a, 'input> {
186    /// Retrieve the name of the port type.
187    pub fn name(&self) -> Result<&'a str> {
188        self.0.attribute("name").ok_or(WsError::new(
189            self.0,
190            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
191        ))
192    }
193
194    /// Retrieve the port type's target namespace.
195    pub fn target_namespace(&self) -> Result<&'a str> {
196        target_namespace(self.0)
197    }
198
199    /// Retrieve the operations associated with this port.
200    pub fn operations(&self) -> Result<impl Iterator<Item = WsPortOperation<'a, 'input>>> {
201        Ok(self
202            .0
203            .children()
204            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "operation")))
205            .map(|n| WsPortOperation(n)))
206    }
207
208    /// Return the XML node this struct is associated with
209    pub fn node(&self) -> Node<'a, 'input> {
210        self.0
211    }
212}
213
214/// Describes an operation associated with a WSDL `portType`.
215/// A WSDL operation can otherwise be described as a function.
216#[derive(Debug, Clone)]
217pub struct WsPortOperation<'a, 'input>(Node<'a, 'input>);
218
219impl<'a, 'input> WsPortOperation<'a, 'input> {
220    /// Retrieve the name of an operation.
221    pub fn name(&self) -> Result<&'a str> {
222        self.0.attribute("name").ok_or(WsError::new(
223            self.0,
224            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
225        ))
226    }
227
228    /// Retrieve the input message for this port.
229    pub fn input(&self) -> Result<Option<WsMessage<'a, 'input>>> {
230        let message_typename = match self
231            .0
232            .children()
233            .find(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "input")))
234            .map(|n| n.attribute("message"))
235            .flatten()
236        {
237            Some(n) => n,
238            None => return Ok(None),
239        };
240
241        let (_message_namespace, message_name) =
242            split_qualified(message_typename).map_err(|e| WsError::new(self.0, e))?;
243
244        let def = WsDefinitions::find_parent(self.0)?;
245        Ok(Some(
246            def.messages()?
247                .find(|n| n.0.attribute("name") == Some(message_name))
248                .ok_or(WsError::new(
249                    self.0,
250                    WsErrorType::InvalidReference(message_name.to_string()),
251                ))?,
252        ))
253    }
254
255    /// Retrieve the output message for this port.
256    pub fn output(&self) -> Result<Option<WsMessage<'a, 'input>>> {
257        let message_typename = match self
258            .0
259            .children()
260            .find(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "output")))
261            .map(|n| n.attribute("message"))
262            .flatten()
263        {
264            Some(n) => n,
265            None => return Ok(None),
266        };
267
268        let (_message_namespace, message_name) =
269            split_qualified(message_typename).map_err(|e| WsError::new(self.0, e))?;
270
271        let def = WsDefinitions::find_parent(self.0)?;
272        Ok(Some(
273            def.messages()?
274                .find(|n| n.0.attribute("name") == Some(message_name))
275                .ok_or(WsError::new(
276                    self.0,
277                    WsErrorType::InvalidReference(message_name.to_string()),
278                ))?,
279        ))
280    }
281
282    /// Retrieve the fault message for this port.
283    pub fn fault(&self) -> Result<Option<WsMessage<'a, 'input>>> {
284        let message_typename = match self
285            .0
286            .children()
287            .find(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "fault")))
288            .map(|n| n.attribute("message"))
289            .flatten()
290        {
291            Some(n) => n,
292            None => return Ok(None),
293        };
294
295        let (_message_namespace, message_name) =
296            split_qualified(message_typename).map_err(|e| WsError::new(self.0, e))?;
297
298        let def = WsDefinitions::find_parent(self.0)?;
299        Ok(Some(
300            def.messages()?
301                .find(|n| n.0.attribute("name") == Some(message_name))
302                .ok_or(WsError::new(
303                    self.0,
304                    WsErrorType::InvalidReference(message_name.to_string()),
305                ))?,
306        ))
307    }
308
309    /// Return the XML node this struct is associated with
310    pub fn node(&self) -> Node<'a, 'input> {
311        self.0
312    }
313}
314
315/// A WSDL binding operation.
316#[derive(Debug, Clone)]
317pub struct WsBindingOperation<'a, 'input>(Node<'a, 'input>);
318
319impl<'a, 'input> WsBindingOperation<'a, 'input> {
320    /// Return the name of the operation described.
321    pub fn name(&self) -> Result<&'a str> {
322        self.0.attribute("name").ok_or(WsError::new(
323            self.0,
324            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
325        ))
326    }
327
328    /// Retrieve the port operation that corresponds to this binding operation.
329    pub fn port_operation(&self) -> Result<WsPortOperation<'a, 'input>> {
330        let name = self.name()?;
331        let binding = WsBinding(
332            self.0
333                .parent()
334                .ok_or(WsError::new(self.0, WsErrorType::NoParentNode))?,
335        );
336
337        let port_type: WsPortType<'a, 'input> = binding.port_type()?;
338        let mut operations = port_type.operations()?;
339
340        operations
341            .try_find(|o| Ok(o.name()? == name))?
342            .ok_or(WsError::new(
343                self.0,
344                WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingElement(name.to_string())),
345            ))
346    }
347
348    /// Return the XML node this struct is associated with
349    pub fn node(&self) -> Node<'a, 'input> {
350        self.0
351    }
352}
353
354/// A WSDL binding that describes how the operations in a port type
355/// are bound to/from the wire.
356#[derive(Debug, Clone)]
357pub struct WsBinding<'a, 'input>(Node<'a, 'input>);
358
359impl<'a, 'input> WsBinding<'a, 'input> {
360    /// Retrieve the name of a binding.
361    pub fn name(&self) -> Result<&'a str> {
362        self.0.attribute("name").ok_or(WsError::new(
363            self.0,
364            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
365        ))
366    }
367
368    pub fn port_type(&self) -> Result<WsPortType<'a, 'input>> {
369        let port_typename = self.0.attribute("type").ok_or(WsError::new(
370            self.0,
371            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("type".to_string())),
372        ))?;
373
374        let (_port_namespace, port_name) =
375            split_qualified(port_typename).map_err(|e| WsError::new(self.0, e))?;
376
377        let def = WsDefinitions::find_parent(self.0)?;
378        def.port_types()?
379            .find(|n| n.0.attribute("name") == Some(port_name))
380            .ok_or(WsError::new(
381                self.0,
382                WsErrorType::InvalidReference(port_name.to_string()),
383            ))
384    }
385
386    pub fn operations(&self) -> Result<impl Iterator<Item = WsBindingOperation>> {
387        Ok(self
388            .0
389            .children()
390            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "operation")))
391            .map(|n| WsBindingOperation(n)))
392    }
393
394    /// Return the XML node this struct is associated with
395    pub fn node(&self) -> Node<'a, 'input> {
396        self.0
397    }
398}
399
400#[derive(Debug, Clone)]
401pub struct WsServicePort<'a, 'input>(Node<'a, 'input>);
402
403impl<'a, 'input> WsServicePort<'a, 'input> {
404    pub fn name(&self) -> Result<&'a str> {
405        self.0.attribute("name").ok_or(WsError::new(
406            self.0,
407            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
408        ))
409    }
410
411    /// Fetch the binding information associated with this service port.
412    pub fn binding(&self) -> Result<WsBinding<'a, 'input>> {
413        let binding_typename = self.0.attribute("binding").ok_or(WsError::new(
414            self.0,
415            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute(
416                "binding".to_string(),
417            )),
418        ))?;
419
420        let (_binding_namespace, binding_name) =
421            split_qualified(binding_typename).map_err(|e| WsError::new(self.0, e))?;
422
423        let def = WsDefinitions::find_parent(self.0)?;
424        def.bindings()?
425            .find(|n| n.0.attribute("name") == Some(binding_name))
426            .ok_or(WsError::new(
427                self.0,
428                WsErrorType::InvalidReference(binding_name.to_string()),
429            ))
430    }
431
432    /// Return the XML node this struct is associated with
433    pub fn node(&self) -> Node<'a, 'input> {
434        self.0
435    }
436}
437
438/// A WSDL service, usually describing an HTTP endpoint that serves
439/// messages bound with a [WsBinding]
440#[derive(Debug, Clone)]
441pub struct WsService<'a, 'input>(Node<'a, 'input>);
442
443impl<'a, 'input> WsService<'a, 'input> {
444    pub fn name(&self) -> Result<&'a str> {
445        self.0.attribute("name").ok_or(WsError::new(
446            self.0,
447            WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingAttribute("name".to_string())),
448        ))
449    }
450
451    pub fn ports(&self) -> Result<impl Iterator<Item = WsServicePort>> {
452        Ok(self
453            .0
454            .children()
455            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "port")))
456            .map(|n| WsServicePort(n)))
457    }
458
459    /// Return the XML node this struct is associated with
460    pub fn node(&self) -> Node<'a, 'input> {
461        self.0
462    }
463}
464
465#[derive(Debug, Clone)]
466pub struct WsTypes<'a, 'input>(Node<'a, 'input>);
467
468impl<'a, 'input> WsTypes<'a, 'input> {
469    /// Return the schemas contained within. These are defined according to the XML schema specification,
470    /// and are out of scope for this library to interpret.
471    pub fn schemas(&self) -> Result<impl Iterator<Item = Node<'a, 'input>>> {
472        Ok(self
473            .0
474            .children()
475            .filter(|n| n.has_tag_name(("http://www.w3.org/2001/XMLSchema", "schema"))))
476    }
477}
478
479#[derive(Debug, Clone)]
480pub struct WsDefinitions<'a, 'input>(Node<'a, 'input>);
481
482impl<'a, 'input> WsDefinitions<'a, 'input> {
483    /// Find the definitions block from one of the node's parents
484    fn find_parent(mut node: Node<'a, 'input>) -> Result<'a, 'input, Self> {
485        loop {
486            node = node
487                .parent()
488                .ok_or(WsError::new(node, WsErrorType::NoParentNode))?;
489
490            if let Ok(definitions) = Self::from_node(node) {
491                return Ok(definitions);
492            }
493        }
494    }
495
496    pub fn from_node(node: Node<'a, 'input>) -> Result<'a, 'input, Self> {
497        if node.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "definitions")) {
498            Ok(Self(node))
499        } else {
500            Err(WsError::new(
501                node,
502                WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingElement(
503                    "definitions".to_string(),
504                )),
505            ))
506        }
507    }
508
509    pub fn from_document(document: &'a Document<'input>) -> Result<'a, 'input, Self> {
510        document
511            .root()
512            .children()
513            .find(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "definitions")))
514            .ok_or(WsError::new(
515                document.root_element(),
516                WsErrorType::MalformedWsdl(WsErrorMalformedType::MissingElement(
517                    "definitions".to_string(),
518                )),
519            ))
520            .map(|n| Self(n))
521    }
522
523    pub fn port_types(&self) -> Result<impl Iterator<Item = WsPortType<'a, 'input>>> {
524        Ok(self
525            .0
526            .children()
527            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "portType")))
528            .map(|n| WsPortType(n))
529            .into_iter())
530    }
531
532    pub fn messages(&self) -> Result<impl Iterator<Item = WsMessage<'a, 'input>>> {
533        Ok(self
534            .0
535            .children()
536            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "message")))
537            .map(|n| WsMessage(n))
538            .into_iter())
539    }
540
541    pub fn bindings(&self) -> Result<impl Iterator<Item = WsBinding<'a, 'input>>> {
542        Ok(self
543            .0
544            .children()
545            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "binding")))
546            .map(|n| WsBinding(n))
547            .into_iter())
548    }
549
550    pub fn services(&self) -> Result<impl Iterator<Item = WsService<'a, 'input>>> {
551        Ok(self
552            .0
553            .children()
554            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "service")))
555            .map(|n| WsService(n))
556            .into_iter())
557    }
558
559    pub fn types(&self) -> Result<impl Iterator<Item = Node<'a, 'input>>> {
560        // FIXME: I'm pretty sure only one of these nodes can exist?
561        Ok(self
562            .0
563            .children()
564            .filter(|n| n.has_tag_name(("http://schemas.xmlsoap.org/wsdl/", "types")))
565            .into_iter())
566    }
567}