stac_extensions/
lib.rs

1//! Extensions describe how STAC can use extensions that extend the
2//! functionality of the core spec or add fields for specific domains.
3//!
4//! Extensions can be published anywhere, although the preferred location for
5//! public extensions is in the GitHub
6//! [stac-extensions](https://github.com/stac-extensions/) organization.
7//! This crate currently supports only a few extensions, though we plan to add more as we find the time.
8//! See <https://stac-extensions.github.io/> for the latest table of community extensions.
9//! This table below lists all [stable](https://github.com/radiantearth/stac-spec/tree/master/extensions#extension-maturity) extensions, as well as any other extensions that are supported by **stac-rs**:
10//!
11//! | Extension | Maturity | **stac-rs** supported version |
12//! | -- | -- | -- |
13//! | [Authentication](https://github.com/stac-extensions/authentication) | Proposal | v1.1.0 |
14//! | [Electro-Optical](https://github.com/stac-extensions/eo) | Stable | v1.1.0 |
15//! | [File Info](https://github.com/stac-extensions/file) | Stable | n/a |
16//! | [Landsat](https://github.com/stac-extensions/landsat) | Stable | n/a |
17//! | [Projection](https://github.com/stac-extensions/projection) | Stable | v1.1.0 |
18//! | [Raster](https://github.com/stac-extensions/raster) | Candidate | v1.1.0 |
19//! | [Scientific Citation](https://github.com/stac-extensions/scientific) | Stable | n/a |
20//! | [View Geometry](https://github.com/stac-extensions/view) | Stable | n/a |
21//!
22//! ## Usage
23//!
24//! [Item], [Collection], and [Catalog] all implement the [Extensions] trait,
25//! which provides methods to get, set, and remove extension information:
26//!
27//! ```
28//! use stac::Item;
29//! use stac_extensions::{Extensions, Projection, projection::Centroid};
30//! let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
31//! assert!(item.has_extension::<Projection>());
32//!
33//! // Get extension information
34//! let mut projection: Projection = item.extension().unwrap();
35//! println!("code: {}", projection.code.as_ref().unwrap());
36//!
37//! // Set extension information
38//! projection.centroid = Some(Centroid { lat: 34.595302, lon: -101.344483 });
39//! Extensions::set_extension(&mut item, projection).unwrap();
40//!
41//! // Remove an extension
42//! Extensions::remove_extension::<Projection>(&mut item);
43//! assert!(!item.has_extension::<Projection>());
44//! ```
45
46pub mod authentication;
47pub mod electro_optical;
48pub mod projection;
49pub mod raster;
50
51pub use projection::Projection;
52pub use raster::Raster;
53use serde::{de::DeserializeOwned, Serialize};
54use stac::{Catalog, Collection, Error, Fields, Item, Result};
55
56/// A trait implemented by extensions.
57///
58/// So far, all extensions are assumed to live in under
59/// <https://stac-extensions.github.io> domain.
60pub trait Extension: Serialize + DeserializeOwned {
61    /// The schema URI.
62    const IDENTIFIER: &'static str;
63
64    /// The fiend name prefix.
65    const PREFIX: &'static str;
66
67    /// Returns everything from the identifier up until the version.
68    ///
69    /// # Examples
70    ///
71    /// ```
72    /// use stac_extensions::{Raster, Extension};
73    /// assert_eq!(Raster::identifier_prefix(), "https://stac-extensions.github.io/raster/");
74    /// ```
75    fn identifier_prefix() -> &'static str {
76        assert!(Self::IDENTIFIER.starts_with("https://stac-extensions.github.io/"));
77        let index = Self::IDENTIFIER["https://stac-extensions.github.io/".len()..]
78            .find('/')
79            .expect("all identifiers should have a first path segment");
80        &Self::IDENTIFIER[0.."https://stac-extensions.github.io/".len() + index + 1]
81    }
82}
83
84/// A trait for objects that may have STAC extensions.
85pub trait Extensions: Fields {
86    /// Returns a reference to this object's extensions.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use stac::Item;
92    /// use stac_extensions::Extensions;
93    ///
94    /// let item = Item::new("an-id");
95    /// assert!(item.extensions().is_empty());
96    /// ```
97    fn extensions(&self) -> &Vec<String>;
98
99    /// Returns a mutable reference to this object's extensions.
100    ///
101    /// # Examples
102    ///
103    /// ```
104    /// use stac::Item;
105    /// use stac_extensions::Extensions;
106    ///
107    /// let mut item = Item::new("an-id");
108    /// item.extensions_mut().push("https://stac-extensions.github.io/raster/v1.1.0/schema.json".to_string());
109    /// ```
110    fn extensions_mut(&mut self) -> &mut Vec<String>;
111
112    /// Returns true if this object has the given extension.
113    ///
114    /// # Examples
115    ///
116    /// ```
117    /// use stac::Item;
118    /// use stac_extensions::{Projection, Extensions};
119    ///
120    /// let mut item = Item::new("an-id");
121    /// assert!(!item.has_extension::<Projection>());
122    /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() };
123    /// item.set_extension(projection).unwrap();
124    /// assert!(item.has_extension::<Projection>());
125    /// ```
126    fn has_extension<E: Extension>(&self) -> bool {
127        self.extensions()
128            .iter()
129            .any(|extension| extension.starts_with(E::identifier_prefix()))
130    }
131
132    /// Returns an extension's data.
133    ///
134    /// # Examples
135    ///
136    /// ```
137    /// use stac::Item;
138    /// use stac_extensions::{Projection, Extensions};
139    ///
140    /// let item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
141    /// let projection: Projection = item.extension().unwrap();
142    /// assert_eq!(projection.code.unwrap(), "EPSG:32614");
143    /// ```
144    fn extension<E: Extension>(&self) -> Result<E> {
145        self.fields_with_prefix(E::PREFIX).map_err(Error::from)
146    }
147
148    /// Adds an extension's identifier to this object.
149    ///
150    /// # Examples
151    ///
152    /// ```
153    /// use stac::Item;
154    /// use stac_extensions::{Projection, Extensions};
155    ///
156    /// let mut item = Item::new("an-id");
157    /// item.add_extension::<Projection>();
158    /// ```
159    fn add_extension<E: Extension>(&mut self) {
160        self.extensions_mut().push(E::IDENTIFIER.to_string());
161        self.extensions_mut().dedup();
162    }
163
164    /// Sets an extension's data and adds its schema to this object's `extensions`.
165    ///
166    /// This will remove any previous versions of this extension.
167    ///
168    /// # Examples
169    ///
170    /// ```
171    /// use stac::Item;
172    /// use stac_extensions::{Projection, Extensions};
173    ///
174    /// let mut item = Item::new("an-id");
175    /// let projection = Projection { code: Some("EPSG:4326".to_string()), ..Default::default() };
176    /// item.set_extension(projection).unwrap();
177    /// ```
178    fn set_extension<E: Extension>(&mut self, extension: E) -> Result<()> {
179        self.extensions_mut().push(E::IDENTIFIER.to_string());
180        self.extensions_mut().dedup();
181        self.remove_fields_with_prefix(E::PREFIX);
182        self.set_fields_with_prefix(E::PREFIX, extension)
183            .map_err(Error::from)
184    }
185
186    /// Removes this extension and all of its fields from this object.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use stac::Item;
192    /// use stac_extensions::{Projection, Extensions};
193    ///
194    /// let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
195    /// assert!(item.has_extension::<Projection>());
196    /// item.remove_extension::<Projection>();
197    /// assert!(!item.has_extension::<Projection>());
198    /// ```
199    fn remove_extension<E: Extension>(&mut self) {
200        self.remove_fields_with_prefix(E::PREFIX);
201        self.extensions_mut()
202            .retain(|extension| !extension.starts_with(E::identifier_prefix()))
203    }
204}
205
206macro_rules! impl_extensions {
207    ($name:ident) => {
208        impl Extensions for $name {
209            fn extensions(&self) -> &Vec<String> {
210                &self.extensions
211            }
212            fn extensions_mut(&mut self) -> &mut Vec<String> {
213                &mut self.extensions
214            }
215        }
216    };
217}
218
219impl_extensions!(Item);
220impl_extensions!(Catalog);
221impl_extensions!(Collection);
222
223#[cfg(test)]
224mod tests {
225    use crate::{raster::Raster, Extension, Extensions, Projection};
226    use serde_json::json;
227    use stac::Item;
228
229    #[test]
230    fn identifer_prefix() {
231        assert_eq!(
232            Raster::identifier_prefix(),
233            "https://stac-extensions.github.io/raster/"
234        );
235        assert_eq!(
236            Projection::identifier_prefix(),
237            "https://stac-extensions.github.io/projection/"
238        );
239    }
240
241    #[test]
242    fn remove_extension() {
243        let mut item = Item::new("an-id");
244        item.extensions
245            .push("https://stac-extensions.github.io/projection/v2.0.0/schema.json".to_string());
246        let _ = item
247            .properties
248            .additional_fields
249            .insert("proj:code".to_string(), json!("EPSG:4326"));
250        assert!(item.has_extension::<Projection>());
251        item.remove_extension::<Projection>();
252        assert!(!item.has_extension::<Projection>());
253        assert!(item.extensions.is_empty());
254        assert!(item.properties.additional_fields.is_empty());
255    }
256}