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