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#[derive(Clone, Debug, PartialEq, Deserialize, Serialize)]
11#[serde(untagged)]
12pub enum Value {
13 #[serde(rename = "Feature")]
15 Item(Item),
16
17 Catalog(Catalog),
19
20 Collection(Collection),
22
23 #[serde(rename = "FeatureCollection")]
25 ItemCollection(ItemCollection),
26}
27
28impl Value {
29 pub fn is_catalog(&self) -> bool {
38 matches!(self, Value::Catalog(_))
39 }
40
41 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 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 pub fn is_collection(&self) -> bool {
84 matches!(self, Value::Collection(_))
85 }
86
87 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 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 pub fn is_item(&self) -> bool {
130 matches!(self, Value::Item(_))
131 }
132
133 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 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 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}