1use crate::ast::*;
2use iri_string::spec::IriSpec;
3use iri_string::types::RiReferenceString;
4use std::io::Read;
5use xmltree::Element;
6
7#[allow(unused)]
8pub const WADL_NS: &str = "http://wadl.dev.java.net/2009/02";
10
11#[derive(Debug)]
12pub enum Error {
14 Io(std::io::Error),
16
17 Xml(xmltree::ParseError),
19
20 Url(url::ParseError),
22
23 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
109pub 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
495pub 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
551pub 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
557pub fn parse_string(s: &str) -> Result<Application, Error> {
559 parse(s.as_bytes())
560}
561
562pub 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}