1use iri_string::spec::IriSpec;
3use iri_string::types::RiReferenceString;
4use std::collections::BTreeMap;
5use url::Url;
6
7pub type Id = String;
9
10#[derive(Debug, PartialEq, Eq, Clone)]
12pub enum ParamStyle {
13 Plain,
15
16 Matrix,
18
19 Query,
21
22 Header,
24
25 Template,
27}
28
29#[derive(Debug)]
31pub struct Application {
32 pub resources: Vec<Resources>,
34
35 pub resource_types: Vec<ResourceType>,
37
38 pub docs: Vec<Doc>,
40
41 pub grammars: Vec<Grammar>,
43
44 pub representations: Vec<RepresentationDef>,
46}
47
48impl Application {
49 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 pub fn get_resource_type_by_href(&self, href: &Url) -> Option<&ResourceType> {
56 if let Some(fragment) = href.fragment() {
58 self.get_resource_type_by_id(fragment)
59 } else {
60 None
61 }
62 }
63
64 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 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 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 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)]
121pub struct Resources {
123 pub base: Option<Url>,
125
126 pub resources: Vec<Resource>,
128}
129
130#[derive(Debug)]
131pub struct Grammar {
133 pub href: RiReferenceString<IriSpec>,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq)]
138pub enum ResourceTypeRef {
140 Id(Id),
142
143 Link(Url),
145
146 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 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)]
196pub struct Options(BTreeMap<String, Option<mime::Mime>>);
198
199impl Options {
200 pub fn new() -> Self {
202 Self::default()
203 }
204
205 pub fn len(&self) -> usize {
207 self.0.len()
208 }
209
210 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 pub fn keys(&self) -> impl Iterator<Item = &str> {
217 self.0.keys().map(|k| k.as_str())
218 }
219
220 pub fn is_empty(&self) -> bool {
222 self.0.is_empty()
223 }
224
225 pub fn insert(&mut self, key: String, value: Option<mime::Mime>) {
227 self.0.insert(key, value);
228 }
229
230 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)]
249pub struct Resource {
251 pub id: Option<Id>,
253
254 pub path: Option<String>,
256
257 pub r#type: Vec<ResourceTypeRef>,
259
260 pub query_type: mime::Mime,
262
263 pub methods: Vec<Method>,
265
266 pub docs: Vec<Doc>,
268
269 pub subresources: Vec<Resource>,
271
272 pub params: Vec<Param>,
274}
275
276impl Resource {
277 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 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 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)]
327pub struct Method {
329 pub id: Id,
331
332 pub name: String,
334
335 pub docs: Vec<Doc>,
337
338 pub request: Request,
340
341 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)]
354pub struct Doc {
356 pub title: Option<String>,
358
359 pub lang: Option<String>,
361
362 pub content: String,
364
365 pub xmlns: Option<url::Url>,
367}
368
369impl Doc {
370 pub fn new(content: String) -> Self {
372 Self {
373 content,
374 ..Default::default()
375 }
376 }
377}
378
379#[derive(Debug, Clone, PartialEq, Eq)]
380pub struct Link {
382 pub resource_type: Option<ResourceTypeRef>,
384
385 pub relation: Option<String>,
389
390 pub reverse_relation: Option<String>,
395
396 pub doc: Option<Doc>,
398}
399
400#[derive(Debug, Clone, PartialEq, Eq)]
401pub struct Param {
403 pub style: ParamStyle,
405
406 pub id: Option<Id>,
408
409 pub name: String,
411
412 pub r#type: String,
414
415 pub path: Option<String>,
417
418 pub required: bool,
420
421 pub repeating: bool,
423
424 pub fixed: Option<String>,
426
427 pub doc: Option<Doc>,
429
430 pub links: Vec<Link>,
432
433 pub options: Option<Options>,
435}
436
437#[derive(Debug, Clone, PartialEq, Eq, Default)]
438pub struct RepresentationDef {
440 pub id: Option<Id>,
442
443 pub media_type: Option<mime::Mime>,
445
446 pub element: Option<String>,
448
449 pub profile: Option<String>,
451
452 pub docs: Vec<Doc>,
454
455 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)]
466pub enum RepresentationRef {
468 Id(Id),
470
471 Link(Url),
473}
474
475impl RepresentationRef {
476 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)]
486pub enum Representation {
488 Reference(RepresentationRef),
490
491 Definition(RepresentationDef),
493}
494
495impl Representation {
496 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 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 pub fn as_def(&self) -> Option<&RepresentationDef> {
519 match self {
520 Representation::Reference(_) => None,
521 Representation::Definition(d) => Some(d),
522 }
523 }
524
525 pub fn iter_all_params(&self) -> impl Iterator<Item = &Param> {
527 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 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)]
587pub struct Request {
589 pub docs: Vec<Doc>,
591
592 pub params: Vec<Param>,
594
595 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)]
611pub struct Response {
613 pub docs: Vec<Doc>,
615
616 pub params: Vec<Param>,
618
619 pub status: Option<i32>,
621
622 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)]
638pub struct ResourceType {
640 pub id: Id,
642
643 pub query_type: mime::Mime,
645
646 pub methods: Vec<Method>,
648
649 pub docs: Vec<Doc>,
651
652 pub subresources: Vec<Resource>,
654
655 pub params: Vec<Param>,
657}
658
659impl ResourceType {
660 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 pub fn iter_referenced_types(&self) -> impl Iterator<Item = &str> + '_ {
669 self.iter_all_params().map(|p| p.r#type.as_str())
670 }
671}