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}