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}