Skip to main content

wadl/
ast.rs

1//! Abstract syntax tree for WADL documents.
2use iri_string::spec::IriSpec;
3use iri_string::types::RiReferenceString;
4use std::collections::BTreeMap;
5use url::Url;
6
7/// Identifier for a resource, method, parameter, etc.
8pub type Id = String;
9
10/// Parameter style
11#[derive(Debug, PartialEq, Eq, Clone)]
12pub enum ParamStyle {
13    /// Specifies a component of the representation formatted as a string encoding of the parameter value according to the rules of the media type.
14    Plain,
15
16    /// Specifies a matrix URI component.
17    Matrix,
18
19    /// Specifies a URI query parameter represented according to the rules for the query component media type specified by the queryType attribute.
20    Query,
21
22    /// Specifies a HTTP header that pertains to the HTTP request (resource or request) or HTTP response (response)
23    Header,
24
25    /// The parameter is represented as a string encoding of the parameter value and is substituted into the value of the path attribute of the resource element as described in section 2.6.1.
26    Template,
27}
28
29/// A WADL application.
30#[derive(Debug)]
31pub struct Application {
32    /// Resources defined at the application level.
33    pub resources: Vec<Resources>,
34
35    /// Resource types defined at the application level.
36    pub resource_types: Vec<ResourceType>,
37
38    /// Documentation for the application.
39    pub docs: Vec<Doc>,
40
41    /// List of grammars
42    pub grammars: Vec<Grammar>,
43
44    /// Representations defined at the application level.
45    pub representations: Vec<RepresentationDef>,
46}
47
48impl Application {
49    /// Get a resource type by its ID.
50    pub fn get_resource_type_by_id(&self, id: &str) -> Option<&ResourceType> {
51        self.resource_types.iter().find(|rt| id == rt.id.as_str())
52    }
53
54    /// Get a resource type by its href, which may be a fragment or a full URL.
55    pub fn get_resource_type_by_href(&self, href: &Url) -> Option<&ResourceType> {
56        // TODO(jelmer): Check that href matches us?
57        if let Some(fragment) = href.fragment() {
58            self.get_resource_type_by_id(fragment)
59        } else {
60            None
61        }
62    }
63
64    /// Iterate over all resources defined in this application.
65    pub fn iter_resources(&self) -> impl Iterator<Item = (Option<Url>, &Resource)> {
66        self.resources
67            .iter()
68            .flat_map(|rs| rs.resources.iter().map(|r| (r.url(rs.base.as_ref()), r)))
69    }
70
71    /// Get a resource by its ID.
72    pub fn get_resource_by_href(&self, href: &Url) -> Option<&Resource> {
73        self.iter_resources()
74            .find(|(url, _)| {
75                if let Some(url) = url {
76                    url == href
77                } else {
78                    false
79                }
80            })
81            .map(|(_, r)| r)
82    }
83
84    /// Iterate over all types defined in this application.
85    pub fn iter_referenced_types(&self) -> impl Iterator<Item = String> + '_ {
86        self.iter_resources()
87            .flat_map(|(_u, r)| r.iter_referenced_types().map(|s| s.to_string()))
88            .chain(
89                self.resource_types
90                    .iter()
91                    .flat_map(|rt| rt.iter_referenced_types().map(|s| s.to_string())),
92            )
93    }
94
95    /// Iterate over all parameters defined in this application.
96    pub fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
97        self.iter_resources()
98            .flat_map(|(_u, r)| r.iter_all_params())
99            .chain(
100                self.resource_types
101                    .iter()
102                    .flat_map(|rt| rt.iter_all_params()),
103            )
104            .chain(
105                self.representations
106                    .iter()
107                    .flat_map(|r| r.iter_all_params()),
108            )
109    }
110}
111
112impl std::str::FromStr for Application {
113    type Err = crate::parse::Error;
114
115    fn from_str(s: &str) -> Result<Self, Self::Err> {
116        crate::parse::parse_string(s)
117    }
118}
119
120#[derive(Debug)]
121/// A collection of resources.
122pub struct Resources {
123    /// The base URL for the resources.
124    pub base: Option<Url>,
125
126    /// The resources defined at this level.
127    pub resources: Vec<Resource>,
128}
129
130#[derive(Debug)]
131/// A grammar
132pub struct Grammar {
133    /// The href of the grammar.
134    pub href: RiReferenceString<IriSpec>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138/// A reference to a resource type.
139pub enum ResourceTypeRef {
140    /// A reference to a resource type defined in the same document.
141    Id(Id),
142
143    /// A reference to a resource type defined in another document.
144    Link(Url),
145
146    /// An empty reference.
147    Empty,
148}
149
150impl std::str::FromStr for ResourceTypeRef {
151    type Err = String;
152
153    fn from_str(s: &str) -> Result<Self, Self::Err> {
154        match s {
155            "" => Ok(ResourceTypeRef::Empty),
156            s => {
157                if let Some(s) = s.strip_prefix('#') {
158                    Ok(ResourceTypeRef::Id(s.to_string()))
159                } else {
160                    Ok(ResourceTypeRef::Link(
161                        s.parse().map_err(|e| format!("{}", e))?,
162                    ))
163                }
164            }
165        }
166    }
167}
168
169#[test]
170fn parse_resource_type_ref() {
171    use crate::ast::ResourceTypeRef::*;
172    use std::str::FromStr;
173    assert_eq!(Empty, ResourceTypeRef::from_str("").unwrap());
174    assert_eq!(
175        Id("id".to_owned()),
176        ResourceTypeRef::from_str("#id").unwrap()
177    );
178    assert_eq!(
179        Link(Url::parse("https://example.com").unwrap()),
180        ResourceTypeRef::from_str("https://example.com").unwrap()
181    );
182}
183
184impl ResourceTypeRef {
185    /// Return the ID of the resource type reference.
186    pub fn id(&self) -> Option<&str> {
187        match self {
188            ResourceTypeRef::Id(id) => Some(id),
189            ResourceTypeRef::Link(l) => l.fragment(),
190            ResourceTypeRef::Empty => None,
191        }
192    }
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
196/// An option element defines one of a set of possible values for the parameter represented by its parent param element.
197pub struct Options(BTreeMap<String, Option<mime::Mime>>);
198
199impl Options {
200    /// Create a new options object
201    pub fn new() -> Self {
202        Self::default()
203    }
204
205    /// Number of items in this Options
206    pub fn len(&self) -> usize {
207        self.0.len()
208    }
209
210    /// Iterate over all items in this Options
211    pub fn iter(&self) -> impl Iterator<Item = (&str, Option<&mime::Mime>)> {
212        self.0.iter().map(|(k, v)| (k.as_str(), v.as_ref()))
213    }
214
215    /// Return an iterator over all keys
216    pub fn keys(&self) -> impl Iterator<Item = &str> {
217        self.0.keys().map(|k| k.as_str())
218    }
219
220    /// Check if this Options is empty
221    pub fn is_empty(&self) -> bool {
222        self.0.is_empty()
223    }
224
225    /// Insert a new key-value pair into this Options
226    pub fn insert(&mut self, key: String, value: Option<mime::Mime>) {
227        self.0.insert(key, value);
228    }
229
230    /// Get the value for a key
231    pub fn get(&self, key: &str) -> Option<&Option<mime::Mime>> {
232        self.0.get(key)
233    }
234}
235
236impl From<Vec<String>> for Options {
237    fn from(v: Vec<String>) -> Self {
238        Self(v.into_iter().map(|s| (s, None)).collect())
239    }
240}
241
242impl From<Vec<&str>> for Options {
243    fn from(v: Vec<&str>) -> Self {
244        Self(v.into_iter().map(|s| (s.to_string(), None)).collect())
245    }
246}
247
248#[derive(Debug, Clone)]
249/// A resource
250pub struct Resource {
251    /// The ID of the resource.
252    pub id: Option<Id>,
253
254    /// The path of the resource.
255    pub path: Option<String>,
256
257    /// The types of the resource.
258    pub r#type: Vec<ResourceTypeRef>,
259
260    /// The query type of the resource.
261    pub query_type: mime::Mime,
262
263    /// The methods defined at this level.
264    pub methods: Vec<Method>,
265
266    /// The docs for the resource.
267    pub docs: Vec<Doc>,
268
269    /// Sub-resources of this resource.
270    pub subresources: Vec<Resource>,
271
272    /// The params for this resource.
273    pub params: Vec<Param>,
274}
275
276impl Resource {
277    /// Get the URL of this resource.
278    pub fn url(&self, base_url: Option<&Url>) -> Option<Url> {
279        self.path.as_ref().and_then(|path| {
280            if let Some(base_url) = base_url {
281                base_url.join(path).ok()
282            } else {
283                Url::parse(path).ok()
284            }
285        })
286    }
287
288    /// Iterate over all parameters defined in this resource.
289    pub(crate) fn iter_all_params(&self) -> Box<dyn Iterator<Item = &Param> + '_> {
290        Box::new(
291            self.params
292                .iter()
293                .chain(self.subresources.iter().flat_map(|r| r.iter_all_params()))
294                .chain(self.methods.iter().flat_map(|m| m.iter_all_params())),
295        )
296    }
297
298    /// Iterate over all types referenced by this resource.
299    pub fn iter_referenced_types(&self) -> impl Iterator<Item = &str> + '_ {
300        self.iter_all_params().map(|p| p.r#type.as_str())
301    }
302}
303
304#[test]
305fn test_resource_url() {
306    let r = Resource {
307        id: None,
308        path: Some("/foo".to_string()),
309        r#type: vec![],
310        query_type: mime::APPLICATION_JSON,
311        methods: vec![],
312        docs: vec![],
313        subresources: vec![],
314        params: vec![],
315    };
316    assert_eq!(
317        r.url(Some(&Url::parse("http://example.com").unwrap())),
318        Some(Url::parse("http://example.com/foo").unwrap())
319    );
320    assert_eq!(
321        r.url(Some(&Url::parse("http://example.com/bar").unwrap())),
322        Some(Url::parse("http://example.com/foo").unwrap())
323    );
324}
325
326#[derive(Debug, Clone)]
327/// A HTTP Method
328pub struct Method {
329    /// Identifier of this method
330    pub id: Id,
331
332    /// The name of the method.
333    pub name: String,
334
335    /// The docs for the method.
336    pub docs: Vec<Doc>,
337
338    /// The request for the method.
339    pub request: Request,
340
341    /// The responses for the method.
342    pub responses: Vec<Response>,
343}
344
345impl Method {
346    fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
347        self.request
348            .iter_all_params()
349            .chain(self.responses.iter().flat_map(|r| r.iter_all_params()))
350    }
351}
352
353#[derive(Debug, Clone, PartialEq, Eq, Default)]
354/// Documentation
355pub struct Doc {
356    /// The title of the documentation.
357    pub title: Option<String>,
358
359    /// The language of the documentation.
360    pub lang: Option<String>,
361
362    /// The content of the documentation.
363    pub content: String,
364
365    /// The namespace of the documentation.
366    pub xmlns: Option<url::Url>,
367}
368
369impl Doc {
370    /// Create a new documentation object.
371    pub fn new(content: String) -> Self {
372        Self {
373            content,
374            ..Default::default()
375        }
376    }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq)]
380/// A link to another resource.
381pub struct Link {
382    /// The resource type of the link.
383    pub resource_type: Option<ResourceTypeRef>,
384
385    /// Optional token that identifies the relationship of the resource identified by the link to
386    /// the resource whose representation the link is embedded in. The value is scoped by the value
387    /// of the ancestor representation element's profile attribute.
388    pub relation: Option<String>,
389
390    /// An optional token that identifies the relationship of the resource whose representation
391    /// the link is embedded in to the resource identified by the link. This is the reverse
392    /// relationship to that identified by the rel attribute. The value is scoped by the value
393    /// of the ancestor representation element's profile attribute.
394    pub reverse_relation: Option<String>,
395
396    /// Optional documentation
397    pub doc: Option<Doc>,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
401/// A parameter
402pub struct Param {
403    /// The style of the parameter.
404    pub style: ParamStyle,
405
406    /// The ID of the parameter.
407    pub id: Option<Id>,
408
409    /// The name of the parameter.
410    pub name: String,
411
412    /// The type of the parameter. This will be a XSD type, e.g. `xs:string`.
413    pub r#type: String,
414
415    /// Path of the parameter.
416    pub path: Option<String>,
417
418    /// Whether the parameter is required, i.e. must be present in the request.
419    pub required: bool,
420
421    /// Whether the parameter is repeating.
422    pub repeating: bool,
423
424    /// The fixed value of the parameter.
425    pub fixed: Option<String>,
426
427    /// The documentation for the parameter.
428    pub doc: Option<Doc>,
429
430    /// The links for the parameter.
431    pub links: Vec<Link>,
432
433    /// The options for the parameter.
434    pub options: Option<Options>,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Default)]
438/// A representation definition
439pub struct RepresentationDef {
440    /// The ID of the representation.
441    pub id: Option<Id>,
442
443    /// The media type of the representation.
444    pub media_type: Option<mime::Mime>,
445
446    /// The element of the representation.
447    pub element: Option<String>,
448
449    /// The profile of the representation.
450    pub profile: Option<String>,
451
452    /// The documentation for the representation.
453    pub docs: Vec<Doc>,
454
455    /// The parameters for the representation.
456    pub params: Vec<Param>,
457}
458
459impl RepresentationDef {
460    fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
461        self.params.iter()
462    }
463}
464
465#[derive(Debug, Clone)]
466/// A reference to a representation.
467pub enum RepresentationRef {
468    /// A reference to a representation defined in the same document.
469    Id(Id),
470
471    /// A reference to a representation defined in another document.
472    Link(Url),
473}
474
475impl RepresentationRef {
476    /// Return the ID of the representation reference.
477    pub fn id(&self) -> Option<&str> {
478        match self {
479            RepresentationRef::Id(id) => Some(id),
480            RepresentationRef::Link(l) => l.fragment(),
481        }
482    }
483}
484
485#[derive(Debug, Clone)]
486/// A representation
487pub enum Representation {
488    /// A reference to a representation defined in the same document.
489    Reference(RepresentationRef),
490
491    /// A definition of a representation.
492    Definition(RepresentationDef),
493}
494
495impl Representation {
496    /// Return the content type of this representation.
497    pub fn media_type(&self) -> Option<&mime::Mime> {
498        match self {
499            Representation::Reference(_) => None,
500            Representation::Definition(d) => d.media_type.as_ref(),
501        }
502    }
503
504    /// Return the URL of this representation.
505    pub fn url(&self, base_url: &Url) -> Option<Url> {
506        match self {
507            Representation::Reference(RepresentationRef::Id(id)) => {
508                let mut url = base_url.clone();
509                url.set_fragment(Some(id));
510                Some(url)
511            }
512            Representation::Reference(RepresentationRef::Link(l)) => Some(l.clone()),
513            Representation::Definition(d) => d.url(base_url),
514        }
515    }
516
517    /// Return the definition of this representation.
518    pub fn as_def(&self) -> Option<&RepresentationDef> {
519        match self {
520            Representation::Reference(_) => None,
521            Representation::Definition(d) => Some(d),
522        }
523    }
524
525    /// Iterate over all parameters defined in this representation.
526    pub fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
527        // TODO: Make this into a proper iterator
528        let params = match self {
529            Representation::Reference(_) => vec![],
530            Representation::Definition(d) => d.iter_all_params().collect::<Vec<_>>(),
531        };
532
533        params.into_iter()
534    }
535}
536
537#[test]
538fn test_representation_url() {
539    let base_url = Url::parse("http://example.com").unwrap();
540    let r = Representation::Reference(RepresentationRef::Id("foo".to_string()));
541    assert_eq!(
542        r.url(&base_url).unwrap(),
543        Url::parse("http://example.com#foo").unwrap()
544    );
545    let r = Representation::Reference(RepresentationRef::Link(
546        Url::parse("http://example.com#foo").unwrap(),
547    ));
548    assert_eq!(
549        r.url(&base_url).unwrap(),
550        Url::parse("http://example.com#foo").unwrap()
551    );
552    let r = Representation::Definition(RepresentationDef {
553        id: Some("foo".to_string()),
554        ..Default::default()
555    });
556    assert_eq!(
557        r.url(&base_url).unwrap(),
558        Url::parse("http://example.com#foo").unwrap()
559    );
560}
561
562#[test]
563fn test_representation_id() {
564    let r = Representation::Reference(RepresentationRef::Id("foo".to_string()));
565    assert_eq!(r.as_def(), None);
566    let r = Representation::Definition(RepresentationDef {
567        id: Some("foo".to_string()),
568        ..Default::default()
569    });
570    assert_eq!(r.as_def().unwrap().id, Some("foo".to_string()));
571}
572
573impl RepresentationDef {
574    /// Fully qualify the URL of this representation.
575    pub fn url(&self, base_url: &Url) -> Option<Url> {
576        if let Some(id) = &self.id {
577            let mut url = base_url.clone();
578            url.set_fragment(Some(id));
579            Some(url)
580        } else {
581            None
582        }
583    }
584}
585
586#[derive(Debug, Default, Clone)]
587/// A request
588pub struct Request {
589    /// The docs for the request.
590    pub docs: Vec<Doc>,
591
592    /// The parameters for the request.
593    pub params: Vec<Param>,
594
595    /// The representations for the request.
596    pub representations: Vec<Representation>,
597}
598
599impl Request {
600    fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
601        self.params.iter().chain(
602            self.representations
603                .iter()
604                .filter_map(|r| r.as_def().map(|r| r.iter_all_params()))
605                .flatten(),
606        )
607    }
608}
609
610#[derive(Debug, Clone, Default)]
611/// A response
612pub struct Response {
613    /// The docs for the response.
614    pub docs: Vec<Doc>,
615
616    /// The parameters for the response.
617    pub params: Vec<Param>,
618
619    /// The status of the response.
620    pub status: Option<i32>,
621
622    /// The representations for the response.
623    pub representations: Vec<Representation>,
624}
625
626impl Response {
627    fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
628        self.params.iter().chain(
629            self.representations
630                .iter()
631                .filter_map(|r| r.as_def().map(|r| r.iter_all_params()))
632                .flatten(),
633        )
634    }
635}
636
637#[derive(Debug)]
638/// A resource type
639pub struct ResourceType {
640    /// The ID of the resource type.
641    pub id: Id,
642
643    /// The query type of the resource type.
644    pub query_type: mime::Mime,
645
646    /// The methods defined at this level.
647    pub methods: Vec<Method>,
648
649    /// The docs for the resource type.
650    pub docs: Vec<Doc>,
651
652    /// The subresources of the resource type.
653    pub subresources: Vec<Resource>,
654
655    /// The params for the resource type.
656    pub params: Vec<Param>,
657}
658
659impl ResourceType {
660    /// Iterate over all parameters defined in this resource type.
661    pub(crate) fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
662        self.params
663            .iter()
664            .chain(self.methods.iter().flat_map(|m| m.iter_all_params()))
665    }
666
667    /// Returns an iterator over all types referenced by this resource type.
668    pub fn iter_referenced_types(&self) -> impl Iterator<Item = &str> + '_ {
669        self.iter_all_params().map(|p| p.r#type.as_str())
670    }
671}