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}