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}