stac_types/link.rs
1//! Links.
2
3use crate::{mime::APPLICATION_GEOJSON, Error, Href, Result, SelfHref};
4use mime::APPLICATION_JSON;
5use serde::{Deserialize, Serialize};
6use serde_json::{Map, Value};
7
8/// Child links.
9pub const CHILD_REL: &str = "child";
10/// Item link.
11pub const ITEM_REL: &str = "item";
12/// Parent link.
13pub const PARENT_REL: &str = "parent";
14/// Root link.
15pub const ROOT_REL: &str = "root";
16/// Self link.
17pub const SELF_REL: &str = "self";
18/// Collection link.
19pub const COLLECTION_REL: &str = "collection";
20
21/// This object describes a relationship with another entity.
22///
23/// Data providers are advised to be liberal with the links section, to describe
24/// things like the `Catalog`` an `Item` is in,
25/// related `Item`s, parent or child `Item`s (modeled in different ways, like an
26/// 'acquisition' or derived data). It is allowed to add additional fields such
27/// as a title and type.
28///
29/// This link structure includes a few fields from the [STAC API
30/// specification](https://github.com/radiantearth/stac-api-spec/tree/main/item-search#pagination).
31/// Generally we keep STAC API structures in the [stac-api
32/// crate](https://github.com/stac-utils/stac-rs/stac-api), but in this case it
33/// was simpler to include these attributes in the base [Link] rather to create a new one.
34#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
35pub struct Link {
36 /// The actual link in the format of an URL.
37 ///
38 /// Relative and absolute links are both allowed.
39 pub href: Href,
40
41 /// Relationship between the current document and the linked document.
42 ///
43 /// See the chapter on ["Relation
44 /// types"](https://github.com/radiantearth/stac-spec/blob/master/item-spec/item-spec.md#relation-types)
45 /// in the STAC spec for more information.
46 pub rel: String,
47
48 /// [Media type](crate::mime) of the referenced entity.
49 #[serde(rename = "type")]
50 #[serde(skip_serializing_if = "Option::is_none")]
51 pub r#type: Option<String>,
52
53 /// A human readable title to be used in rendered displays of the link.
54 #[serde(skip_serializing_if = "Option::is_none")]
55 pub title: Option<String>,
56
57 /// The HTTP method of the request, usually GET or POST. Defaults to GET.
58 ///
59 /// From the STAC API spec.
60 #[serde(skip_serializing_if = "Option::is_none")]
61 pub method: Option<String>,
62
63 /// A dictionary of header values that must be included in the next request
64 ///
65 /// From the STAC API spec.
66 #[serde(skip_serializing_if = "Option::is_none")]
67 pub headers: Option<Map<String, Value>>,
68
69 /// A JSON object containing fields/values that must be included in the body
70 /// of the next request.
71 ///
72 /// From the STAC API spec.
73 #[serde(skip_serializing_if = "Option::is_none")]
74 pub body: Option<Map<String, Value>>,
75
76 /// If true, the headers/body fields in the next link must be merged into
77 /// the original request and be sent combined in the next request. Defaults
78 /// to false
79 ///
80 /// From the STAC API spec.
81 #[serde(skip_serializing_if = "Option::is_none")]
82 pub merge: Option<bool>,
83
84 /// Additional fields on the link.
85 #[serde(flatten)]
86 pub additional_fields: Map<String, Value>,
87}
88
89/// Implemented by any object that has links.
90pub trait Links: SelfHref {
91 /// Returns a reference to this object's links.
92 ///
93 /// # Examples
94 ///
95 /// `Value` implements Links:
96 ///
97 /// ```
98 /// use stac::Links;
99 /// let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
100 /// let links = item.links();
101 /// ```
102 fn links(&self) -> &[Link];
103
104 /// Returns a mutable reference to this object's links.
105 ///
106 /// # Examples
107 ///
108 /// `Value`` implements Links:
109 ///
110 /// ```
111 /// use stac::Links;
112 /// let mut item: stac::Item = stac::read("examples/simple-item.json").unwrap();
113 /// let links = item.links_mut();
114 /// links.clear();
115 /// ```
116 fn links_mut(&mut self) -> &mut Vec<Link>;
117
118 /// Returns the first link with the given rel type.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use stac::Links;
124 /// let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
125 /// let link = item.link("root").unwrap();
126 /// ```
127 fn link(&self, rel: &str) -> Option<&Link> {
128 self.links().iter().find(|link| link.rel == rel)
129 }
130
131 /// Sets a link of the given rel type.
132 ///
133 /// This will remove all other links of that rel type, so should only be
134 /// used for e.g. "root", not e.g. "child".
135 ///
136 /// # Examples
137 ///
138 /// ```
139 /// use stac::{Links, Link};
140 /// let mut item: stac::Item = stac::read("examples/simple-item.json").unwrap();
141 /// item.set_link(Link::root("a/href"));
142 /// ```
143 fn set_link(&mut self, link: Link) {
144 self.links_mut().retain(|l| l.rel != link.rel);
145 self.links_mut().push(link)
146 }
147
148 /// Returns this object's root link.
149 ///
150 /// This is the first link with a rel="root".
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// use stac::Links;
156 /// let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
157 /// let link = item.root_link().unwrap();
158 /// ```
159 fn root_link(&self) -> Option<&Link> {
160 self.links().iter().find(|link| link.is_root())
161 }
162
163 /// Returns this object's self link.
164 ///
165 /// This is the first link with a rel="self".
166 ///
167 /// # Examples
168 ///
169 /// ```
170 /// use stac::Links;
171 /// let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
172 /// let link = item.root_link().unwrap();
173 /// ```
174 fn self_link(&self) -> Option<&Link> {
175 self.links().iter().find(|link| link.is_self())
176 }
177
178 /// Returns this object's parent link.
179 ///
180 /// This is the first link with a rel="parent".
181 ///
182 /// # Examples
183 ///
184 /// ```
185 /// use stac::Links;
186 /// let item: stac::Item = stac::read("examples/simple-item.json").unwrap();
187 /// let link = item.parent_link().unwrap();
188 /// ```
189 fn parent_link(&self) -> Option<&Link> {
190 self.links().iter().find(|link| link.is_parent())
191 }
192
193 /// Returns an iterator over this object's child links.
194 ///
195 /// # Examples
196 ///
197 /// ```
198 /// use stac::Links;
199 /// let collection: stac::Collection = stac::read("examples/collection.json").unwrap();
200 /// let links: Vec<_> = collection.iter_child_links().collect();
201 /// ```
202 fn iter_child_links(&self) -> Box<dyn Iterator<Item = &Link> + '_> {
203 Box::new(self.links().iter().filter(|link| link.is_child()))
204 }
205
206 /// Returns an iterator over this object's item links.
207 ///
208 /// # Examples
209 ///
210 /// ```
211 /// use stac::Links;
212 /// let collection: stac::Collection = stac::read("examples/collection.json").unwrap();
213 /// let links: Vec<_> = collection.iter_item_links().collect();
214 /// ```
215 fn iter_item_links(&self) -> Box<dyn Iterator<Item = &Link> + '_> {
216 Box::new(self.links().iter().filter(|link| link.is_item()))
217 }
218
219 /// Makes all relative links absolute with respect to this object's self href.
220 fn make_links_absolute(&mut self) -> Result<()> {
221 if let Some(href) = self.self_href().cloned() {
222 for link in self.links_mut() {
223 link.make_absolute(&href)?;
224 }
225 Ok(())
226 } else {
227 Err(Error::NoHref)
228 }
229 }
230
231 /// Makes all links relative with respect to this object's self href.
232 fn make_links_relative(&mut self) -> Result<()> {
233 if let Some(href) = self.self_href().cloned() {
234 for link in self.links_mut() {
235 link.make_relative(&href)?;
236 }
237 Ok(())
238 } else {
239 Err(Error::NoHref)
240 }
241 }
242
243 /// Removes all relative links.
244 ///
245 /// This can be useful e.g. if you're relocating a STAC object, but it
246 /// doesn't have a href, so the relative links wouldn't make any sense.
247 ///
248 /// # Examples
249 ///
250 /// ```
251 /// use stac::{Catalog, Links, Link};
252 /// let mut catalog = Catalog::new("an-id", "a description");
253 /// catalog.links.push(Link::new("./child.json", "child"));
254 /// catalog.remove_relative_links();
255 /// assert!(catalog.links.is_empty());
256 /// ```
257 fn remove_relative_links(&mut self) {
258 self.links_mut().retain(|link| link.is_absolute())
259 }
260
261 /// Removes all structural links.
262 ///
263 /// Useful if you're, e.g., going to re-populate the structural links as a
264 /// part of serving items with a STAC API.
265 ///
266 /// # Examples
267 ///
268 /// ```
269 /// use stac::{Catalog, Links, Link};
270 /// let mut catalog = Catalog::new("an-id", "a description");
271 /// catalog.links.push(Link::self_("http://stac.test/catalog.json"));
272 /// catalog.remove_structural_links();
273 /// assert!(catalog.links.is_empty());
274 /// ```
275 fn remove_structural_links(&mut self) {
276 self.links_mut().retain(|link| !link.is_structural())
277 }
278}
279
280impl Link {
281 /// Creates a new link with the provided href and rel type.
282 ///
283 /// # Examples
284 ///
285 /// ```
286 /// # use stac::Link;
287 /// let link = Link::new("an-href", "a-rel");
288 /// assert_eq!(link.href, "an-href");
289 /// assert_eq!(link.rel, "a-rel");
290 /// ```
291 pub fn new(href: impl Into<Href>, rel: impl ToString) -> Link {
292 Link {
293 href: href.into(),
294 rel: rel.to_string(),
295 r#type: None,
296 title: None,
297 method: None,
298 headers: None,
299 body: None,
300 merge: None,
301 additional_fields: Map::new(),
302 }
303 }
304
305 /// Sets this link's media type to JSON.
306 ///
307 /// # Examples
308 ///
309 /// ```
310 /// use stac::Link;
311 /// let link = Link::new("a/href", "rel-type").json();
312 /// assert_eq!(link.r#type.unwrap(), ::mime::APPLICATION_JSON.as_ref());
313 /// ```
314 pub fn json(mut self) -> Link {
315 self.r#type = Some(APPLICATION_JSON.to_string());
316 self
317 }
318
319 /// Returns true if this link's media type is JSON.
320 ///
321 /// # Examples
322 ///
323 /// ```
324 /// use stac::Link;
325 /// let link = Link::new("a/href", "rel-type").json();
326 /// assert!(link.is_json());
327 /// ```
328 pub fn is_json(&self) -> bool {
329 self.r#type
330 .as_ref()
331 .map(|t| t == APPLICATION_JSON.as_ref())
332 .unwrap_or(false)
333 }
334
335 /// Sets this link's media type to GeoJSON.
336 ///
337 /// # Examples
338 ///
339 /// ```
340 /// use stac::{Link, mime};
341 /// let link = Link::new("a/href", "rel-type").geojson();
342 /// assert_eq!(link.r#type.unwrap(), mime::GEOJSON);
343 /// ```
344 pub fn geojson(mut self) -> Link {
345 self.r#type = Some(APPLICATION_GEOJSON.to_string());
346 self
347 }
348
349 /// Returns true if this link's media type is GeoJSON.
350 ///
351 /// # Examples
352 ///
353 /// ```
354 /// use stac::Link;
355 /// let link = Link::new("a/href", "rel-type").geojson();
356 /// assert!(link.is_geojson());
357 /// ```
358 pub fn is_geojson(&self) -> bool {
359 self.r#type
360 .as_ref()
361 .map(|t| t == APPLICATION_GEOJSON)
362 .unwrap_or(false)
363 }
364
365 /// Sets this link's media type.
366 ///
367 /// # Examples
368 ///
369 /// ```
370 /// use stac::{Link, mime};
371 /// let link = Link::new("a/href", "rel-type").r#type(mime::GEOJSON.to_string());
372 /// assert_eq!(link.r#type.unwrap(), mime::GEOJSON);
373 /// ```
374 pub fn r#type(mut self, r#type: impl Into<Option<String>>) -> Link {
375 self.r#type = r#type.into();
376 self
377 }
378
379 /// Sets this link's title.
380 ///
381 /// # Examples
382 ///
383 /// ```
384 /// use stac::Link;
385 /// let link = Link::new("a/href", "rel-type").title("a title".to_string());
386 /// assert_eq!(link.title.unwrap(), "a title");
387 /// ```
388 pub fn title(mut self, title: impl Into<Option<String>>) -> Link {
389 self.title = title.into();
390 self
391 }
392
393 /// Creates a new root link with JSON media type.
394 ///
395 /// # Examples
396 ///
397 /// ```
398 /// # use stac::Link;
399 /// let link = Link::root("an-href");
400 /// assert!(link.is_root());
401 /// assert_eq!(link.r#type.as_ref().unwrap(), ::mime::APPLICATION_JSON.as_ref());
402 /// ```
403 pub fn root(href: impl Into<Href>) -> Link {
404 Link::new(href, ROOT_REL).json()
405 }
406
407 /// Creates a new self link with JSON media type.
408 ///
409 /// # Examples
410 ///
411 /// ```
412 /// # use stac::Link;
413 /// let link = Link::self_("an-href");
414 /// assert!(link.is_self());
415 /// assert_eq!(link.r#type.as_ref().unwrap(), ::mime::APPLICATION_JSON.as_ref());
416 /// ```
417 pub fn self_(href: impl Into<Href>) -> Link {
418 Link::new(href, SELF_REL).json()
419 }
420
421 /// Creates a new child link with JSON media type.
422 ///
423 /// # Examples
424 ///
425 /// ```
426 /// # use stac::Link;
427 /// let link = Link::child("an-href");
428 /// assert!(link.is_child());
429 /// assert_eq!(link.r#type.as_ref().unwrap(), ::mime::APPLICATION_JSON.as_ref());
430 /// ```
431 pub fn child(href: impl Into<Href>) -> Link {
432 Link::new(href, CHILD_REL).json()
433 }
434
435 /// Creates a new item link with JSON media type.
436 ///
437 /// # Examples
438 ///
439 /// ```
440 /// # use stac::Link;
441 /// let link = Link::item("an-href");
442 /// assert!(link.is_item());
443 /// assert_eq!(link.r#type.as_ref().unwrap(), ::mime::APPLICATION_JSON.as_ref());
444 /// ```
445 pub fn item(href: impl Into<Href>) -> Link {
446 Link::new(href, ITEM_REL).json()
447 }
448
449 /// Creates a new parent link with JSON media type.
450 ///
451 /// # Examples
452 ///
453 /// ```
454 /// # use stac::Link;
455 /// let link = Link::parent("an-href");
456 /// assert!(link.is_parent());
457 /// assert_eq!(link.r#type.as_ref().unwrap(), ::mime::APPLICATION_JSON.as_ref());
458 /// ```
459 pub fn parent(href: impl Into<Href>) -> Link {
460 Link::new(href, PARENT_REL).json()
461 }
462
463 /// Creates a new collection link with JSON media type.
464 ///
465 /// # Examples
466 ///
467 /// ```
468 /// # use stac::Link;
469 /// let link = Link::collection("an-href");
470 /// assert!(link.is_collection());
471 /// assert_eq!(link.r#type.as_ref().unwrap(), ::mime::APPLICATION_JSON.as_ref());
472 /// ```
473 pub fn collection(href: impl Into<Href>) -> Link {
474 Link::new(href, COLLECTION_REL).json()
475 }
476
477 /// Returns true if this link's rel is `"item"`.
478 ///
479 /// # Examples
480 ///
481 /// ```
482 /// # use stac::Link;
483 /// let link = Link::new("an-href", "item");
484 /// assert!(link.is_item());
485 /// let link = Link::new("an-href", "not-an-item");
486 /// assert!(!link.is_item());
487 /// ```
488 pub fn is_item(&self) -> bool {
489 self.rel == ITEM_REL
490 }
491
492 /// Returns true if this link's rel is `"child"`.
493 ///
494 /// # Examples
495 ///
496 /// ```
497 /// # use stac::Link;
498 /// let link = Link::new("an-href", "child");
499 /// assert!(link.is_child());
500 /// let link = Link::new("an-href", "not-a-child");
501 /// assert!(!link.is_child());
502 /// ```
503 pub fn is_child(&self) -> bool {
504 self.rel == CHILD_REL
505 }
506
507 /// Returns true if this link's rel is `"parent"`.
508 ///
509 /// # Examples
510 ///
511 /// ```
512 /// # use stac::Link;
513 /// let link = Link::new("an-href", "parent");
514 /// assert!(link.is_parent());
515 /// let link = Link::new("an-href", "not-a-parent");
516 /// assert!(!link.is_parent());
517 /// ```
518 pub fn is_parent(&self) -> bool {
519 self.rel == PARENT_REL
520 }
521
522 /// Returns true if this link's rel is `"root"`.
523 ///
524 /// # Examples
525 ///
526 /// ```
527 /// # use stac::Link;
528 /// let link = Link::new("an-href", "root");
529 /// assert!(link.is_root());
530 /// let link = Link::new("an-href", "not-a-root");
531 /// assert!(!link.is_root());
532 /// ```
533 pub fn is_root(&self) -> bool {
534 self.rel == ROOT_REL
535 }
536
537 /// Returns true if this link's rel is `"self"`.
538 ///
539 /// # Examples
540 ///
541 /// ```
542 /// # use stac::Link;
543 /// let link = Link::new("an-href", "self");
544 /// assert!(link.is_self());
545 /// let link = Link::new("an-href", "not-a-self");
546 /// assert!(!link.is_self());
547 /// ```
548 pub fn is_self(&self) -> bool {
549 self.rel == SELF_REL
550 }
551
552 /// Returns true if this link's rel is `"collection"`.
553 ///
554 /// # Examples
555 ///
556 /// ```
557 /// # use stac::Link;
558 /// let link = Link::new("an-href", "collection");
559 /// assert!(link.is_collection());
560 /// let link = Link::new("an-href", "not-a-collection");
561 /// assert!(!link.is_collection());
562 /// ```
563 pub fn is_collection(&self) -> bool {
564 self.rel == COLLECTION_REL
565 }
566
567 /// Returns true if this link is structural (i.e. not child, parent, item,
568 /// root, or self).
569 ///
570 /// Also includes some API structural link types such as "data",
571 /// "conformance", "items", and "search".
572 ///
573 /// # Examples
574 ///
575 /// ```
576 /// # use stac::Link;
577 /// let link = Link::new("an-href", "self");
578 /// assert!(link.is_structural());
579 /// let link = Link::new("an-href", "child");
580 /// assert!(link.is_structural());
581 /// let link = Link::new("an-href", "not-a-root");
582 /// assert!(!link.is_structural());
583 pub fn is_structural(&self) -> bool {
584 self.is_child()
585 || self.is_item()
586 || self.is_parent()
587 || self.is_root()
588 || self.is_self()
589 || self.is_collection()
590 || self.rel == "data"
591 || self.rel == "conformance"
592 || self.rel == "items"
593 || self.rel == "search"
594 || self.rel == "service-desc"
595 || self.rel == "service-doc"
596 || self.rel == "next"
597 || self.rel == "prev"
598 }
599
600 /// Returns true if this link's href is an absolute path or url.
601 ///
602 /// # Examples
603 ///
604 /// ```
605 /// use stac::Link;
606 ///
607 /// assert!(Link::new("/a/local/path/item.json", "rel").is_absolute());
608 /// assert!(Link::new("http://stac-rs.test/item.json", "rel").is_absolute());
609 /// assert!(!Link::new("./not/an/absolute/path", "rel").is_absolute());
610 /// ```
611 pub fn is_absolute(&self) -> bool {
612 self.href.is_absolute()
613 }
614
615 /// Returns true if this link's href is a relative path.
616 ///
617 /// # Examples
618 ///
619 /// ```
620 /// use stac::Link;
621 ///
622 /// assert!(!Link::new("/a/local/path/item.json", "rel").is_relative());
623 /// assert!(!Link::new("http://stac-rs.test/item.json", "rel").is_relative());
624 /// assert!(Link::new("./not/an/absolute/path", "rel").is_relative());
625 /// ```
626 pub fn is_relative(&self) -> bool {
627 !self.href.is_absolute()
628 }
629
630 /// Sets the method attribute on this link.
631 ///
632 /// # Examples
633 ///
634 /// ```
635 /// use stac::Link;
636 /// let link = Link::new("href", "rel").method("GET");
637 /// ```
638 pub fn method(mut self, method: impl ToString) -> Link {
639 self.method = Some(method.to_string());
640 self
641 }
642
643 /// Sets the body attribute on this link.
644 ///
645 /// # Examples
646 ///
647 /// ```
648 /// use stac::Link;
649 /// use serde_json::json;
650 ///
651 /// let link = Link::new("href", "rel").body(json!({"foo": "bar"})).unwrap();
652 /// ```
653 pub fn body<T: Serialize>(mut self, body: T) -> Result<Link> {
654 match serde_json::to_value(body)? {
655 Value::Object(body) => {
656 self.body = Some(body);
657 Ok(self)
658 }
659 value => Err(Error::IncorrectType {
660 actual: value.to_string(),
661 expected: "object".to_string(),
662 }),
663 }
664 }
665
666 /// Makes this link absolute.
667 ///
668 /// If the href is relative, use the passed in value as a base.
669 ///
670 /// # Examples
671 ///
672 /// ```
673 /// use stac::Link;
674 ///
675 /// let mut link = Link::new("./b/item.json", "rel");
676 /// link.make_absolute(&"/a/base/catalog.json".into()).unwrap();
677 /// assert_eq!(link.href, "/a/base/b/item.json")
678 /// ```
679 pub fn make_absolute(&mut self, base: &Href) -> Result<()> {
680 self.href = self.href.absolute(base)?;
681 Ok(())
682 }
683
684 /// Makes this link relative
685 pub fn make_relative(&mut self, base: &Href) -> Result<()> {
686 self.href = self.href.relative(base)?;
687 Ok(())
688 }
689}
690
691#[cfg(test)]
692mod tests {
693 use super::Link;
694
695 #[test]
696 fn new() {
697 let link = Link::new("an-href", "a-rel");
698 assert_eq!(link.href, "an-href");
699 assert_eq!(link.rel, "a-rel");
700 assert!(link.r#type.is_none());
701 assert!(link.title.is_none());
702 }
703
704 #[test]
705 fn skip_serializing() {
706 let link = Link::new("an-href", "a-rel");
707 let value = serde_json::to_value(link).unwrap();
708 assert!(value.get("type").is_none());
709 assert!(value.get("title").is_none());
710 }
711
712 mod links {
713 use stac::{Catalog, Item, Link, Links};
714
715 #[test]
716 fn link() {
717 let mut item = Item::new("an-item");
718 assert!(item.link("root").is_none());
719 item.links.push(Link::new("an-href", "root"));
720 assert!(item.link("root").is_some());
721 }
722
723 #[test]
724 fn root() {
725 let mut item = Item::new("an-item");
726 assert!(item.root_link().is_none());
727 item.links.push(Link::new("an-href", "root"));
728 assert!(item.root_link().is_some());
729 }
730
731 #[test]
732 fn self_() {
733 let mut item = Item::new("an-item");
734 assert!(item.self_link().is_none());
735 item.links.push(Link::new("an-href", "self"));
736 assert!(item.self_link().is_some());
737 }
738
739 #[test]
740 fn remove_relative_links() {
741 let mut catalog = Catalog::new("an-id", "a description");
742 catalog.links.push(Link::new("./child.json", "child"));
743 catalog.links.push(Link::new("/child.json", "child"));
744 catalog
745 .links
746 .push(Link::new("http://stac-rs.test/child.json", "child"));
747 catalog.remove_relative_links();
748 assert_eq!(catalog.links.len(), 2);
749 }
750 }
751}