wadl/
parse.rs

1use crate::ast::*;
2use iri_string::spec::IriSpec;
3use iri_string::types::RiReferenceString;
4use std::io::Read;
5use xmltree::Element;
6
7#[allow(unused)]
8/// The namespace of the WADL XML schema.
9pub const WADL_NS: &str = "http://wadl.dev.java.net/2009/02";
10
11#[derive(Debug)]
12/// Errors that can occur while parsing a WADL document.
13pub enum Error {
14    /// An I/O error occurred while reading the document.
15    Io(std::io::Error),
16
17    /// An error occurred while parsing the XML document.
18    Xml(xmltree::ParseError),
19
20    /// An error occurred while parsing a URL.
21    Url(url::ParseError),
22
23    /// An error occurred while parsing a MIME type.
24    Mime(mime::FromStrError),
25}
26
27impl From<std::io::Error> for Error {
28    fn from(e: std::io::Error) -> Self {
29        Error::Io(e)
30    }
31}
32
33impl From<xmltree::ParseError> for Error {
34    fn from(e: xmltree::ParseError) -> Self {
35        Error::Xml(e)
36    }
37}
38
39impl From<url::ParseError> for Error {
40    fn from(e: url::ParseError) -> Self {
41        Error::Url(e)
42    }
43}
44
45impl From<mime::FromStrError> for Error {
46    fn from(e: mime::FromStrError) -> Self {
47        Error::Mime(e)
48    }
49}
50
51impl std::fmt::Display for Error {
52    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
53        match &self {
54            Error::Io(e) => write!(f, "IO error: {}", e),
55            Error::Xml(e) => write!(f, "XML error: {}", e),
56            Error::Url(e) => write!(f, "URL error: {}", e),
57            Error::Mime(e) => write!(f, "MIME error: {}", e),
58        }
59    }
60}
61
62impl std::error::Error for Error {}
63
64pub fn parse_options(element: &Element) -> Option<Options> {
65    let mut options = Options::new();
66
67    for option_node in &element.children {
68        if let Some(element) = option_node.as_element() {
69            if element.name == "option" {
70                let value = element.attributes.get("value").cloned();
71                let media_type = element
72                    .attributes
73                    .get("mediaType")
74                    .cloned()
75                    .map(|x| x.parse().unwrap());
76                options.insert(value.unwrap(), media_type);
77            }
78        }
79    }
80
81    if options.is_empty() {
82        None
83    } else {
84        Some(options)
85    }
86}
87
88#[test]
89fn test_parse_options() {
90    let xml = r#"
91        <param name="format">
92            <option value="json" mediaType="application/json"/>
93            <option value="xml" mediaType="application/xml"/>
94        </param>
95    "#;
96    let element = Element::parse(xml.as_bytes()).unwrap();
97    let options = parse_options(&element).unwrap();
98    assert_eq!(options.len(), 2);
99    assert_eq!(
100        options.get("json").unwrap(),
101        &Some("application/json".parse().unwrap())
102    );
103    assert_eq!(
104        options.get("xml").unwrap(),
105        &Some("application/xml".parse().unwrap())
106    );
107}
108
109/// Parse a `param` element.
110pub fn parse_params(resource_element: &Element, allowed_styles: &[ParamStyle]) -> Vec<Param> {
111    let mut params = Vec::new();
112
113    for param_node in &resource_element.children {
114        if let Some(element) = param_node.as_element() {
115            if element.name == "param" {
116                let style = element
117                    .attributes
118                    .get("style")
119                    .cloned()
120                    .map(|s| match s.as_str() {
121                        "plain" => ParamStyle::Plain,
122                        "matrix" => ParamStyle::Matrix,
123                        "query" => ParamStyle::Query,
124                        "header" => ParamStyle::Header,
125                        "template" => ParamStyle::Template,
126                        _ => panic!("Unknown param style: {}", s),
127                    })
128                    .unwrap();
129                let options = parse_options(element);
130                let id = element.attributes.get("id").cloned();
131                let links = element
132                    .children
133                    .iter()
134                    .filter_map(|node| {
135                        if let Some(element) = node.as_element() {
136                            if element.name == "link" {
137                                let resource_type: Option<ResourceTypeRef> = element
138                                    .attributes
139                                    .get("resource_type")
140                                    .map(|x| x.parse().unwrap());
141                                let relation = element.attributes.get("rel").cloned();
142                                let reverse_relation = element.attributes.get("rev").cloned();
143                                let doc = parse_docs(element);
144                                Some(Link {
145                                    resource_type,
146                                    relation,
147                                    reverse_relation,
148                                    doc: if doc.len() == 1 {
149                                        Some(doc.into_iter().next().unwrap())
150                                    } else {
151                                        assert!(doc.is_empty());
152                                        None
153                                    },
154                                })
155                            } else {
156                                None
157                            }
158                        } else {
159                            None
160                        }
161                    })
162                    .collect::<Vec<_>>();
163                let name = element.attributes.get("name").cloned().unwrap();
164                let r#type = element
165                    .attributes
166                    .get("type")
167                    .cloned()
168                    .unwrap_or_else(|| "string".to_string());
169                let path = element.attributes.get("path").cloned();
170                let required = element
171                    .attributes
172                    .get("required")
173                    .cloned()
174                    .map(|s| s == "true")
175                    .unwrap_or(false);
176                let repeating = element
177                    .attributes
178                    .get("repeating")
179                    .cloned()
180                    .map(|s| s == "true")
181                    .unwrap_or(false);
182                let fixed = element.attributes.get("fixed").cloned();
183                if !allowed_styles.contains(&style) {
184                    log::warn!(
185                        "Invalid param style: {:?} for element {} (expected one of: {:?})",
186                        style,
187                        name,
188                        allowed_styles
189                    );
190                }
191                let doc = parse_docs(element);
192                params.push(Param {
193                    style,
194                    id,
195                    name,
196                    r#type,
197                    path,
198                    required,
199                    repeating,
200                    fixed,
201                    links,
202                    options,
203                    doc: if doc.len() == 1 {
204                        Some(doc.into_iter().next().unwrap())
205                    } else {
206                        assert!(doc.is_empty());
207                        None
208                    },
209                });
210            }
211        }
212    }
213
214    params
215}
216
217fn parse_resource(element: &Element) -> Result<Resource, Error> {
218    let id = element.attributes.get("id").cloned();
219    let path = element.attributes.get("path").cloned();
220    let r#type = element
221        .attributes
222        .get("type")
223        .map(|s| s.as_str())
224        .unwrap_or("")
225        .split(' ')
226        .map(|x| {
227            x.parse::<ResourceTypeRef>()
228                .expect("cannot parse to Resource Ref")
229        })
230        .collect();
231    let query_type: mime::Mime = element
232        .attributes
233        .get("queryType")
234        .map(|s| s.as_str().parse())
235        .transpose()?
236        .unwrap_or(mime::APPLICATION_WWW_FORM_URLENCODED);
237
238    let docs = parse_docs(element);
239
240    let methods = parse_methods(element);
241
242    let subresources = parse_resources(element)?;
243
244    let params = parse_params(
245        element,
246        &[
247            ParamStyle::Matrix,
248            ParamStyle::Query,
249            ParamStyle::Header,
250            ParamStyle::Template,
251        ],
252    );
253
254    Ok(Resource {
255        id,
256        path,
257        r#type,
258        query_type,
259        methods,
260        docs,
261        subresources,
262        params,
263    })
264}
265
266#[test]
267fn test_parse_resource() {
268    let xml = r#"
269        <resource id="foo" path="/blah" type='#bar'>
270            <doc title="Title">
271                <p>Resource Description</p>
272            </doc>
273            <param name="foo" type="string" style="query" required="true">
274                <doc title="Param Title">
275                    <p>Param Description</p>
276                </doc>
277            </param>
278            <param name="bar" type="string" style="query" required="true">
279                <doc title="Bar">
280                    <p>Bar</p>
281                </doc>
282            </param>
283            <method name="GET">
284                <doc title="Get Foo">
285                    <p>Get Foo</p>
286                </doc>
287                <response code="200">
288                    <doc title="Foo">
289                        <p>Foo</p>
290                    </doc>
291                    <representation mediaType="application/json">
292                        <doc title="Foo">
293                            <p>Foo</p>
294                        </doc>
295                    </representation>
296                </response>
297            </method>
298            <resource id="bar" path="/bar" type='#bar1'>
299                <doc title="Bar">
300                    <p>Bar</p>
301                </doc>
302                <method name="GET">
303                    <doc title="Get Bar">
304                        <p>Get Bar</p>
305                    </doc>
306                    <response code="200">
307                        <doc title="Bar">
308                            <p>Bar</p>
309                        </doc>
310                        <representation mediaType="application/json">
311                            <doc title="Bar">
312                                <p>Bar</p>
313                            </doc>
314                        </representation>
315                    </response>
316                </method>
317            </resource>
318        </resource>
319    "#;
320
321    let element = Element::parse(xml.as_bytes()).unwrap();
322
323    let resource = parse_resource(&element).unwrap();
324
325    assert_eq!(resource.id, Some("foo".to_string()));
326    assert_eq!(resource.path, Some("/blah".to_string()));
327    assert_eq!(resource.query_type, mime::APPLICATION_WWW_FORM_URLENCODED);
328    assert_eq!(resource.docs.len(), 1);
329}
330
331fn parse_resources(resources_element: &Element) -> Result<Vec<Resource>, Error> {
332    let mut resources = Vec::new();
333
334    for resource_node in &resources_element.children {
335        if let Some(element) = resource_node.as_element() {
336            if element.name == "resource" {
337                resources.push(parse_resource(element)?);
338            }
339        }
340    }
341
342    Ok(resources)
343}
344
345#[test]
346fn test_parse_resources() {
347    let xml = r#"
348        <resources base="http://example.com">
349            <resource id="foo" path="/blah" type='#bar'>
350                <doc title="Title">
351                    <p>Resource Description</p>
352                </doc>
353                <param name="foo" type="string" style="query" required="true">
354                    <doc title="Param Title">
355                        <p>Param Description</p>
356                    </doc>
357                </param>
358                <param name="bar" type="string" style="query" required="true">
359                    <doc title="Bar">
360                        <p>Bar</p>
361                    </doc>
362                </param>
363                <method name="GET">
364                    <doc title="Get Foo">
365                        <p>Get Foo</p>
366                    </doc>
367                    <response code="200">
368                        <doc title="Foo">
369                            <p>Foo</p>
370                        </doc>
371                        <representation mediaType="application/json">
372                            <doc title="Foo">
373                                <p>Foo</p>
374                            </doc>
375                        </representation>
376                    </response>
377                </method>
378                <resource id="bar" path="/bar" type='#bar1'>
379                    <doc title="Bar">
380                        <p>Bar</p>
381                    </doc>
382                    <method name="GET">
383                        <doc title="Get Bar">
384                            <p>Get Bar</p>
385                        </doc>
386                        <response code="200">
387                            <doc title="Bar">
388                                <p>Bar</p>
389                            </doc>
390                            <representation mediaType="application/json">
391                                <doc title="Bar">
392                                    <p>Bar</p>
393                                </doc>
394                            </representation>
395                        </response>
396                    </method>
397                </resource>
398            </resource>
399        </resources>
400    "#;
401
402    let element = Element::parse(xml.as_bytes()).unwrap();
403
404    let resources = parse_resources(&element).unwrap();
405
406    assert_eq!(resources.len(), 1);
407
408    let resource = &resources[0];
409
410    assert_eq!(resource.id, Some("foo".to_string()));
411    assert_eq!(resource.path, Some("/blah".to_string()));
412    assert_eq!(resource.query_type, mime::APPLICATION_WWW_FORM_URLENCODED);
413    assert_eq!(resource.docs.len(), 1);
414}
415
416fn parse_docs(resource_element: &Element) -> Vec<Doc> {
417    let mut docs = Vec::new();
418
419    for doc_node in &resource_element.children {
420        if let Some(element) = doc_node.as_element() {
421            if element.name == "doc" {
422                let title = element.attributes.get("title").cloned();
423                use std::io::Write;
424                let content = Vec::new();
425                let mut cursor = std::io::Cursor::new(content);
426                for child in &element.children {
427                    match child {
428                        xmltree::XMLNode::Text(t) => {
429                            cursor.write_all(t.as_bytes()).unwrap();
430                        }
431                        xmltree::XMLNode::Element(e) => {
432                            e.write(&mut cursor).unwrap();
433                        }
434                        _ => {}
435                    };
436                }
437                let lang = element.attributes.get("lang").cloned();
438
439                let namespaces = element.namespaces.as_ref();
440
441                let xmlns = namespaces
442                    .and_then(|x| x.get(""))
443                    .filter(|s| !s.is_empty())
444                    .map(|u| {
445                        u.parse()
446                            .map_err(|e| {
447                                format!("Cannot parse string \"{}\" to Url with error {}", u, e)
448                            })
449                            .expect("provided string should be successfully parsed to Url")
450                    });
451
452                docs.push(Doc {
453                    title,
454                    lang,
455                    content: String::from_utf8_lossy(cursor.into_inner().as_slice()).to_string(),
456                    xmlns,
457                });
458            }
459        }
460    }
461
462    docs
463}
464
465fn parse_resource_type(resource_type_element: &Element) -> Result<ResourceType, Error> {
466    let id = resource_type_element.attributes.get("id").cloned().unwrap();
467    let query_type: mime::Mime = resource_type_element
468        .attributes
469        .get("queryType")
470        .cloned()
471        .unwrap_or("application/x-www-form-urlencoded".to_string())
472        .parse()?;
473
474    let docs = parse_docs(resource_type_element);
475
476    let methods = parse_methods(resource_type_element);
477
478    let subresources = parse_resources(resource_type_element)?;
479
480    let params = parse_params(
481        resource_type_element,
482        &[ParamStyle::Header, ParamStyle::Query],
483    );
484
485    Ok(ResourceType {
486        id,
487        query_type,
488        methods,
489        docs,
490        subresources,
491        params,
492    })
493}
494
495/// Parse an XML application description from a reader.
496pub fn parse<R: Read>(reader: R) -> Result<Application, Error> {
497    let mut resources = Vec::new();
498    let mut resource_types = Vec::new();
499    let mut grammars = Vec::new();
500    let root = Element::parse(reader).map_err(Error::Xml)?;
501
502    let docs = parse_docs(&root);
503
504    for resource_node in &root.children {
505        if let Some(element) = resource_node.as_element() {
506            if element.name == "resources" {
507                let more_resources = parse_resources(element)?;
508                let base = element.attributes.get("base").cloned();
509                resources.push(Resources {
510                    base: base.map(|s| s.parse().unwrap()),
511                    resources: more_resources,
512                });
513            } else if element.name == "grammars" {
514                for grammar_node in &element.children {
515                    if let Some(element) = grammar_node.as_element() {
516                        if element.name == "include" {
517                            let href: RiReferenceString<IriSpec> = element
518                                .attributes
519                                .get("href")
520                                .cloned()
521                                .expect("href attribute is required")
522                                .parse::<RiReferenceString<IriSpec>>()
523                                .expect("cannot parse to Iri");
524                            grammars.push(Grammar { href });
525                        }
526                    }
527                }
528            } else if element.name == "resource_type" {
529                resource_types.push(parse_resource_type(element)?);
530            }
531        }
532    }
533
534    let representations = parse_representations(&root);
535
536    Ok(Application {
537        resources,
538        docs,
539        resource_types,
540        grammars,
541        representations: representations
542            .into_iter()
543            .map(|r| match r {
544                Representation::Definition(r) => r,
545                Representation::Reference(_) => panic!("Reference in root"),
546            })
547            .collect(),
548    })
549}
550
551/// Parse an XML application description from a file.
552pub fn parse_file<P: AsRef<std::path::Path>>(path: P) -> Result<Application, Error> {
553    let file = std::fs::File::open(path).map_err(Error::Io)?;
554    parse(file)
555}
556
557/// Parse a string containing an XML application description.
558pub fn parse_string(s: &str) -> Result<Application, Error> {
559    parse(s.as_bytes())
560}
561
562/// Parse a byte slice containing an XML application description.
563pub fn parse_bytes(bytes: &[u8]) -> Result<Application, Error> {
564    parse(bytes)
565}
566
567fn parse_representations(request_element: &Element) -> Vec<Representation> {
568    let mut representations = Vec::new();
569
570    for representation_node in &request_element.children {
571        if let Some(element) = representation_node.as_element() {
572            if element.name == "representation" {
573                if let Some(href) = element.attributes.get("href") {
574                    if let Some(id) = href.strip_prefix('#') {
575                        representations.push(Representation::Reference(RepresentationRef::Id(
576                            id.to_string(),
577                        )));
578                    } else {
579                        representations.push(Representation::Reference(RepresentationRef::Link(
580                            href.parse().expect("Invalid URL"),
581                        )));
582                    }
583                } else {
584                    let element_name = element.attributes.get("element").cloned();
585                    let media_type = element
586                        .attributes
587                        .get("mediaType")
588                        .map(|s| s.parse().unwrap());
589                    let docs = parse_docs(element);
590                    let id = element.attributes.get("id").cloned();
591                    let profile = element.attributes.get("profile").cloned();
592                    let params = parse_params(element, &[ParamStyle::Plain, ParamStyle::Query]);
593                    representations.push(Representation::Definition(RepresentationDef {
594                        id,
595                        media_type,
596                        docs,
597                        element: element_name,
598                        profile,
599                        params,
600                    }));
601                }
602            }
603        }
604    }
605
606    representations
607}
608
609#[test]
610fn test_parse_representations() {
611    let xml = r#"<response xmlns:xml="http://www.w3.org/XML/1998/namespace">
612        <representation id="foo" mediaType="application/json">
613            <doc xml:lang="en">Foo</doc>
614            <param name="foo" style="plain" type="xs:string" required="true" default="bar" fixed="baz">
615                <doc xml:lang="en">Foo</doc>
616            </param>
617            <param name="bar" style="query" type="xs:string" required="true" default="bar" fixed="baz">
618                <doc xml:lang="en">Bar</doc>
619            </param>
620        </representation>
621        <representation href='#bar' />
622        <representation href="http://example.com/bar" />
623        </response>
624    "#;
625
626    let root = Element::parse(xml.as_bytes()).unwrap();
627
628    let representations = parse_representations(&root);
629
630    assert_eq!(representations.len(), 3);
631
632    if let Representation::Definition(r) = &representations[0] {
633        assert_eq!(r.id, Some("foo".to_string()));
634        assert_eq!(r.media_type, Some("application/json".parse().unwrap()));
635        assert_eq!(r.docs.len(), 1);
636        assert_eq!(r.docs[0].content, "Foo");
637        assert_eq!(r.docs[0].lang, Some("en".to_string()));
638        assert_eq!(r.params.len(), 2);
639        assert_eq!(r.params[0].name, "foo");
640        assert_eq!(r.params[0].style, ParamStyle::Plain);
641        assert!(r.params[0].required);
642        assert_eq!(r.params[0].fixed, Some("baz".to_string()));
643        assert_eq!(r.params[0].doc.as_ref().unwrap().content, "Foo");
644        assert_eq!(
645            r.params[0].doc.as_ref().unwrap().lang,
646            Some("en".to_string())
647        );
648        assert_eq!(r.params[1].name, "bar");
649        assert_eq!(r.params[1].style, ParamStyle::Query);
650        assert!(r.params[1].required);
651        assert_eq!(r.params[1].fixed, Some("baz".to_string()));
652    }
653}
654
655fn parse_response(response_element: &Element) -> Response {
656    let docs = parse_docs(response_element);
657
658    let representations = parse_representations(response_element);
659
660    let status = response_element
661        .attributes
662        .get("status")
663        .filter(|s| !s.is_empty())
664        .map(|s| {
665            s.parse::<i32>()
666                .map_err(|e| format!("Cannot parse String \"{}\" into status code. {}", s, e))
667                .expect("should parse status code from string")
668        });
669
670    let params = parse_params(response_element, &[ParamStyle::Header]);
671
672    Response {
673        docs,
674        params,
675        status,
676        representations,
677    }
678}
679
680#[test]
681fn test_parses_response() {
682    let xml = r#"
683        <response status="200">
684            <param name="foo" style="plain" type="xs:string" required="true" default="bar" fixed="baz">
685                <doc xml:lang="en">Foo</doc>
686            </param>
687            <param name="bar" style="query" type="xs:string" required="true" default="bar" fixed="baz">
688                <doc xml:lang="en">Bar</doc>
689            </param>
690            <param name="baz" style="header" type="xs:string" required="true" default="bar" fixed="baz">
691                <doc xml:lang="en">Baz</doc>
692            </param>
693            <representation href='#foo' />
694            <representation href="http://example.com/bar" />
695            <representation mediaType="application/json" />
696            <representation element="foo" />
697            <representation profile="http://example.com/profile" />
698        </response>
699    "#;
700
701    let element = Element::parse(xml.as_bytes()).unwrap();
702
703    let response = parse_response(&element);
704
705    assert_eq!(response.status, Some(200));
706    assert_eq!(response.representations.len(), 5);
707}
708
709fn parse_request(request_element: &Element) -> Request {
710    let docs = parse_docs(request_element);
711
712    let params = parse_params(request_element, &[ParamStyle::Header, ParamStyle::Query]);
713
714    let representations = parse_representations(request_element);
715
716    Request {
717        docs,
718        params,
719        representations,
720    }
721}
722
723#[test]
724fn test_parse_request() {
725    let xml = r#"
726        <request>
727            <param name="foo" style="plain" type="xs:string" required="true" default="bar" fixed="baz">
728                <doc xml:lang="en">Foo</doc>
729            </param>
730            <param name="bar" style="query" type="xs:string" required="true" default="bar" fixed="baz">
731                <doc xml:lang="en">Bar</doc>
732            </param>
733            <param name="baz" style="header" type="xs:string" required="true" default="bar" fixed="baz">
734                <doc xml:lang="en">Baz</doc>
735            </param>
736            <representation mediaType="application/json" element="foo" profile="bar" id="baz">
737                <doc xml:lang="en">Foo</doc>
738            </representation>
739            <representation href='#qux'/>
740        </request>
741    "#;
742
743    let element = Element::parse(xml.as_bytes()).unwrap();
744
745    let request = parse_request(&element);
746
747    assert_eq!(request.docs.len(), 0);
748    assert_eq!(request.params.len(), 3);
749    assert_eq!(request.representations.len(), 2);
750}
751
752fn parse_method(method_element: &Element) -> Method {
753    let id = method_element
754        .attributes
755        .get("id")
756        .cloned()
757        .unwrap_or_default();
758    let name = method_element
759        .attributes
760        .get("name")
761        .cloned()
762        .unwrap_or_default();
763
764    let request_element = method_element
765        .children
766        .iter()
767        .find(|node| node.as_element().map_or(false, |e| e.name == "request"))
768        .and_then(|node| node.as_element());
769
770    let request = request_element.map(parse_request).unwrap_or_default();
771
772    let responses = method_element
773        .children
774        .iter()
775        .filter(|node| node.as_element().map_or(false, |e| e.name == "response"))
776        .map(|node| node.as_element().unwrap())
777        .map(parse_response)
778        .collect();
779
780    let docs = parse_docs(method_element);
781
782    Method {
783        id,
784        name,
785        docs,
786        request,
787        responses,
788    }
789}
790
791#[test]
792fn test_parse_method() {
793    let xml = r#"
794        <method name="GET">
795            <doc>Get a list of all the widgets</doc>
796            <request>
797                <doc>Filter the list of widgets</doc>
798                <param name="filter" style="query" type="string" required="false">
799                    <doc>Filter the list of widgets</doc>
800                </param>
801            </request>
802            <response status="200">
803                <doc>Return a list of widgets</doc>
804                <representation mediaType="application/json">
805                    <doc>Return a list of widgets</doc>
806                </representation>
807                <param name="id" style="plain" type="string" required="true">
808                    <doc>Return a list of widgets</doc>
809                </param>
810                <param name="name" style="plain" type="string" required="true">
811                    <doc>Return a list of widgets</doc>
812                </param>
813
814            </response>
815        </method>
816    "#;
817
818    let method = parse_method(&Element::parse(xml.as_bytes()).unwrap());
819
820    assert_eq!(method.id, "");
821    assert_eq!(method.name, "GET");
822    assert_eq!(
823        method.docs,
824        vec![Doc {
825            content: "Get a list of all the widgets".to_string(),
826            ..Default::default()
827        }]
828    );
829    assert_eq!(
830        method.request.docs,
831        vec![Doc {
832            content: "Filter the list of widgets".to_string(),
833            ..Default::default()
834        }]
835    );
836    assert_eq!(method.request.params.len(), 1);
837    assert_eq!(method.request.params[0].name, "filter");
838    assert_eq!(
839        method.request.params[0].doc.as_ref().unwrap(),
840        &Doc {
841            content: "Filter the list of widgets".to_string(),
842            ..Default::default()
843        }
844    );
845    assert_eq!(method.responses.len(), 1);
846    assert_eq!(
847        method.responses[0].docs,
848        vec![Doc {
849            content: "Return a list of widgets".to_string(),
850            ..Default::default()
851        }]
852    );
853    assert_eq!(method.responses[0].status, Some(200));
854    assert_eq!(method.responses[0].representations.len(), 1);
855    assert_eq!(
856        method.responses[0].representations[0]
857            .as_def()
858            .unwrap()
859            .media_type,
860        Some("application/json".parse().unwrap())
861    );
862    assert_eq!(method.responses[0].params.len(), 2);
863}
864
865fn parse_methods(resource_element: &Element) -> Vec<Method> {
866    let mut methods = Vec::new();
867
868    for method_node in &resource_element.children {
869        if let Some(element) = method_node.as_element() {
870            if element.name == "method" {
871                methods.push(parse_method(element));
872            }
873        }
874    }
875
876    methods
877}
878
879#[test]
880fn test_parse_methods() {
881    let xml = r#"
882        <methods>
883            <method name="GET">
884                <doc>Get a list of all the widgets</doc>
885                <request>
886                    <doc>Filter the list of widgets</doc>
887                    <param name="filter" style="query" type="string" required="false">
888                        <doc>Filter the list of widgets</doc>
889                    </param>
890                </request>
891                <response status="200">
892                    <doc>Return a list of widgets</doc>
893                    <representation mediaType="application/json">
894                        <doc>Return a list of widgets</doc>
895                    </representation>
896                    <param name="id" style="plain" type="string" required="true">
897                        <doc>Return a list of widgets</doc>
898                    </param>
899                    <param name="name" style="plain" type="string" required="true">
900                        <doc>Return a list of widgets</doc>
901                    </param>
902
903                </response>
904            </method>
905        </methods>
906    "#;
907
908    let methods = parse_methods(&Element::parse(xml.as_bytes()).unwrap());
909
910    assert_eq!(methods.len(), 1);
911    assert_eq!(methods[0].id, "");
912    assert_eq!(methods[0].name, "GET");
913    assert_eq!(
914        methods[0].docs,
915        vec![Doc {
916            content: "Get a list of all the widgets".to_string(),
917            ..Default::default()
918        }]
919    );
920    assert_eq!(
921        methods[0].request.docs,
922        vec![Doc {
923            content: "Filter the list of widgets".to_string(),
924            ..Default::default()
925        }]
926    );
927    assert_eq!(methods[0].request.params.len(), 1);
928    assert_eq!(methods[0].request.params[0].name, "filter");
929    assert_eq!(
930        methods[0].request.params[0].doc.as_ref().unwrap(),
931        &Doc {
932            content: "Filter the list of widgets".to_string(),
933            ..Default::default()
934        }
935    );
936    assert_eq!(methods[0].responses.len(), 1);
937    assert_eq!(
938        methods[0].responses[0].docs,
939        vec![Doc {
940            content: "Return a list of widgets".to_string(),
941            ..Default::default()
942        }]
943    );
944}