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 **rustac**:
10//!
11//! | Extension | Maturity | **rustac** 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::{Serialize, de::DeserializeOwned};
54use stac::{Catalog, Collection, 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)
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    }
184
185    /// Removes this extension and all of its fields from this object.
186    ///
187    /// # Examples
188    ///
189    /// ```
190    /// use stac::Item;
191    /// use stac_extensions::{Projection, Extensions};
192    ///
193    /// let mut item: Item = stac::read("examples/extensions-collection/proj-example/proj-example.json").unwrap();
194    /// assert!(item.has_extension::<Projection>());
195    /// item.remove_extension::<Projection>();
196    /// assert!(!item.has_extension::<Projection>());
197    /// ```
198    fn remove_extension<E: Extension>(&mut self) {
199        self.remove_fields_with_prefix(E::PREFIX);
200        self.extensions_mut()
201            .retain(|extension| !extension.starts_with(E::identifier_prefix()))
202    }
203}
204
205macro_rules! impl_extensions {
206    ($name:ident) => {
207        impl Extensions for $name {
208            fn extensions(&self) -> &Vec<String> {
209                &self.extensions
210            }
211            fn extensions_mut(&mut self) -> &mut Vec<String> {
212                &mut self.extensions
213            }
214        }
215    };
216}
217
218impl_extensions!(Item);
219impl_extensions!(Catalog);
220impl_extensions!(Collection);
221
222#[cfg(test)]
223mod tests {
224    use crate::{Extension, Extensions, Projection, raster::Raster};
225    use serde_json::json;
226    use stac::Item;
227
228    #[test]
229    fn identifer_prefix() {
230        assert_eq!(
231            Raster::identifier_prefix(),
232            "https://stac-extensions.github.io/raster/"
233        );
234        assert_eq!(
235            Projection::identifier_prefix(),
236            "https://stac-extensions.github.io/projection/"
237        );
238    }
239
240    #[test]
241    fn remove_extension() {
242        let mut item = Item::new("an-id");
243        item.extensions
244            .push("https://stac-extensions.github.io/projection/v2.0.0/schema.json".to_string());
245        let _ = item
246            .properties
247            .additional_fields
248            .insert("proj:code".to_string(), json!("EPSG:4326"));
249        assert!(item.has_extension::<Projection>());
250        item.remove_extension::<Projection>();
251        assert!(!item.has_extension::<Projection>());
252        assert!(item.extensions.is_empty());
253        assert!(item.properties.additional_fields.is_empty());
254    }
255}