Skip to main content

stac/
value.rs

1use crate::{
2    Catalog, Collection, Error, Item, ItemCollection, Link, Links, Migrate, Result, SelfHref,
3    Version,
4};
5use serde::{Deserialize, Serialize};
6use serde_json::Map;
7use std::convert::TryFrom;
8
9/// An enum that can hold any STAC object type.
10#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
11#[serde(untagged)]
12pub enum Value {
13    /// A STAC Item.
14    #[serde(rename = "Feature")]
15    Item(Item),
16
17    /// A STAC Catalog.
18    Catalog(Catalog),
19
20    /// A STAC Collection.
21    Collection(Collection),
22
23    /// An ItemCollection.
24    #[serde(rename = "FeatureCollection")]
25    ItemCollection(ItemCollection),
26}
27
28impl Value {
29    /// Returns true if this is a catalog.
30    ///
31    /// # Examples
32    ///
33    /// ```
34    /// # use stac::{Value, Catalog};
35    /// assert!(Value::Catalog(Catalog::new("an-id", "a description")).is_catalog());
36    /// ```
37    pub fn is_catalog(&self) -> bool {
38        matches!(self, Value::Catalog(_))
39    }
40
41    /// Returns a reference to this value as a catalog.
42    ///
43    /// # Examples
44    ///
45    /// ```
46    /// # use stac::{Value, Catalog};
47    /// let value = Value::Catalog(Catalog::new("an-id", "a description"));
48    /// assert_eq!(value.as_catalog().unwrap().id, "an-id");
49    /// ```
50    pub fn as_catalog(&self) -> Option<&Catalog> {
51        if let Value::Catalog(catalog) = self {
52            Some(catalog)
53        } else {
54            None
55        }
56    }
57
58    /// Returns a mutable reference to this value as a catalog.
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// # use stac::{Value, Catalog};
64    /// let mut value = Value::Catalog(Catalog::new("an-id", "a description"));
65    /// value.as_mut_catalog().unwrap().id = "another-id".to_string();
66    /// ```
67    pub fn as_mut_catalog(&mut self) -> Option<&mut Catalog> {
68        if let Value::Catalog(catalog) = self {
69            Some(catalog)
70        } else {
71            None
72        }
73    }
74
75    /// Returns true if this is a collection.
76    ///
77    /// # Examples
78    ///
79    /// ```
80    /// # use stac::{Value, Collection};
81    /// assert!(Value::Collection(Collection::new("an-id", "a description")).is_collection());
82    /// ```
83    pub fn is_collection(&self) -> bool {
84        matches!(self, Value::Collection(_))
85    }
86
87    /// Returns a reference to this value as a collection.
88    ///
89    /// # Examples
90    ///
91    /// ```
92    /// # use stac::{Value, Collection};
93    /// let value = Value::Collection(Collection::new("an-id", "a description"));
94    /// assert_eq!(value.as_collection().unwrap().id, "an-id");
95    /// ```
96    pub fn as_collection(&self) -> Option<&Collection> {
97        if let Value::Collection(collection) = self {
98            Some(collection)
99        } else {
100            None
101        }
102    }
103
104    /// Returns a mutable reference to this value as a collection.
105    ///
106    /// # Examples
107    ///
108    /// ```
109    /// # use stac::{Value, Collection};
110    /// let mut value = Value::Collection(Collection::new("an-id", "a description"));
111    /// value.as_mut_collection().unwrap().id = "another-id".to_string();
112    /// ```
113    pub fn as_mut_collection(&mut self) -> Option<&mut Collection> {
114        if let Value::Collection(collection) = self {
115            Some(collection)
116        } else {
117            None
118        }
119    }
120
121    /// Returns true if this is an item.
122    ///
123    /// # Examples
124    ///
125    /// ```
126    /// # use stac::{Value, Item};
127    /// assert!(Value::Item(Item::new("an-id")).is_item());
128    /// ```
129    pub fn is_item(&self) -> bool {
130        matches!(self, Value::Item(_))
131    }
132
133    /// Returns a reference to this value as an item.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// # use stac::{Value, Item};
139    /// let value = Value::Item(Item::new("an-id"));
140    /// assert_eq!(value.as_item().unwrap().id, "an-id");
141    /// ```
142    pub fn as_item(&self) -> Option<&Item> {
143        if let Value::Item(item) = self {
144            Some(item)
145        } else {
146            None
147        }
148    }
149
150    /// Returns a mutable reference to this value as an item.
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// # use stac::{Value, Item};
156    /// let mut value = Value::Item(Item::new("an-id"));
157    /// value.as_mut_item().unwrap().id = "another-id".to_string();
158    /// ```
159    pub fn as_mut_item(&mut self) -> Option<&mut Item> {
160        if let Value::Item(item) = self {
161            Some(item)
162        } else {
163            None
164        }
165    }
166
167    /// Returns this value's type name.
168    ///
169    /// This is "Item", "Catalog", "Collection", or "ItemCollection".
170    ///
171    /// We can't just use the `type` field, because items' type field is "Feature".
172    ///
173    /// # Examples
174    ///
175    /// ```
176    /// let value: stac::Value = stac::read("examples/simple-item.json").unwrap();
177    /// assert_eq!(value.type_name(), "Item");
178    /// ```
179    pub fn type_name(&self) -> &'static str {
180        use Value::*;
181        match self {
182            Item(_) => "Item",
183            Collection(_) => "Collection",
184            Catalog(_) => "Catalog",
185            ItemCollection(_) => "ItemCollection",
186        }
187    }
188}
189
190impl SelfHref for Value {
191    fn self_href(&self) -> Option<&str> {
192        use Value::*;
193        match self {
194            Catalog(catalog) => catalog.self_href(),
195            Collection(collection) => collection.self_href(),
196            Item(item) => item.self_href(),
197            ItemCollection(item_collection) => item_collection.self_href(),
198        }
199    }
200
201    fn self_href_mut(&mut self) -> &mut Option<String> {
202        use Value::*;
203        match self {
204            Catalog(catalog) => catalog.self_href_mut(),
205            Collection(collection) => collection.self_href_mut(),
206            Item(item) => item.self_href_mut(),
207            ItemCollection(item_collection) => item_collection.self_href_mut(),
208        }
209    }
210}
211
212impl Links for Value {
213    fn links(&self) -> &[Link] {
214        use Value::*;
215        match self {
216            Catalog(catalog) => catalog.links(),
217            Collection(collection) => collection.links(),
218            Item(item) => item.links(),
219            ItemCollection(item_collection) => item_collection.links(),
220        }
221    }
222
223    fn links_mut(&mut self) -> &mut Vec<Link> {
224        use Value::*;
225        match self {
226            Catalog(catalog) => catalog.links_mut(),
227            Collection(collection) => collection.links_mut(),
228            Item(item) => item.links_mut(),
229            ItemCollection(item_collection) => item_collection.links_mut(),
230        }
231    }
232}
233
234impl TryFrom<Value> for Map<String, serde_json::Value> {
235    type Error = Error;
236    fn try_from(value: Value) -> Result<Self> {
237        match serde_json::to_value(value)? {
238            serde_json::Value::Object(object) => Ok(object),
239            _ => {
240                panic!("all STAC values should serialize to a serde_json::Value::Object")
241            }
242        }
243    }
244}
245
246macro_rules! impl_from {
247    ($object:ident) => {
248        impl From<$object> for Value {
249            fn from(o: $object) -> Value {
250                Value::$object(o)
251            }
252        }
253    };
254}
255
256macro_rules! impl_try_from {
257    ($object:ident, $name:expr_2021) => {
258        impl TryFrom<Value> for $object {
259            type Error = Error;
260            fn try_from(value: Value) -> Result<$object> {
261                if let Value::$object(o) = value {
262                    Ok(o)
263                } else {
264                    Err(Error::IncorrectType {
265                        actual: value.type_name().to_string(),
266                        expected: $name.to_string(),
267                    }
268                    .into())
269                }
270            }
271        }
272    };
273}
274impl_from!(Item);
275impl_from!(Catalog);
276impl_from!(Collection);
277impl_from!(ItemCollection);
278impl_try_from!(Item, "Item");
279impl_try_from!(Catalog, "Catalog");
280impl_try_from!(Collection, "Collection");
281
282impl TryFrom<Value> for ItemCollection {
283    type Error = Error;
284    fn try_from(value: Value) -> Result<Self> {
285        match value {
286            Value::Item(item) => Ok(ItemCollection::from(vec![item])),
287            Value::ItemCollection(item_collection) => Ok(item_collection),
288            Value::Catalog(_) | Value::Collection(_) => Err(Error::IncorrectType {
289                actual: value.type_name().to_string(),
290                expected: "ItemCollection".to_string(),
291            }),
292        }
293    }
294}
295
296impl Migrate for Value {
297    fn migrate(self, version: &Version) -> Result<Value> {
298        match self {
299            Value::Item(item) => item.migrate(version).map(Value::Item),
300            Value::Catalog(catalog) => catalog.migrate(version).map(Value::Catalog),
301            Value::Collection(collection) => collection.migrate(version).map(Value::Collection),
302            Value::ItemCollection(item_collection) => {
303                item_collection.migrate(version).map(Value::ItemCollection)
304            }
305        }
306    }
307}
308
309#[cfg(test)]
310mod tests {
311    use super::Value;
312    use serde_json::json;
313
314    #[test]
315    fn catalog_from_json() {
316        let catalog = json!({
317            "type": "Catalog",
318            "stac_version": "1.0.0",
319            "id": "an-id",
320            "description": "a description",
321            "links": []
322        });
323        let value: Value = serde_json::from_value(catalog).unwrap();
324        assert!(value.is_catalog());
325    }
326
327    #[test]
328    fn collection_from_json() {
329        let collection = json!({
330            "type": "Collection",
331            "stac_version": "1.0.0",
332            "id": "an-id",
333            "description": "a description",
334            "license": "proprietary",
335            "extent": {
336                "spatial": [[]],
337                "temporal": [[]]
338            },
339            "links": []
340        });
341        let collection: Value = serde_json::from_value(collection).unwrap();
342        assert!(collection.is_collection());
343    }
344
345    #[test]
346    fn item_from_json() {
347        let item = json!({
348            "type": "Feature",
349            "stac_version": "1.0.0",
350            "id": "an-id",
351            "geometry": null,
352            "properties": {},
353            "links": [],
354            "assets": {}
355        });
356        let item: Value = serde_json::from_value(item).unwrap();
357        assert!(item.is_item());
358    }
359
360    #[test]
361    fn from_json_unknown_type() {
362        let catalog = json!({
363            "type": "Schmatalog",
364            "stac_version": "1.0.0",
365            "id": "an-id",
366            "description": "a description",
367            "links": []
368        });
369        assert!(serde_json::from_value::<Value>(catalog).is_err());
370    }
371
372    #[test]
373    fn from_json_invalid_type_field() {
374        let catalog = json!({
375            "type": {"foo": "bar"},
376            "stac_version": "1.0.0",
377            "id": "an-id",
378            "description": "a description",
379            "links": []
380        });
381        assert!(serde_json::from_value::<Value>(catalog).is_err());
382    }
383}