1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
use crate::{Item, Result};
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use stac::{Link, Links};

const ITEM_COLLECTION_TYPE: &str = "FeatureCollection";

/// The return value of the `/items` and `/search` endpoints.
///
/// This might be a [stac::ItemCollection], but if the [fields
/// extension](https://github.com/stac-api-extensions/fields) is used, it might
/// not be. Defined by the [itemcollection
/// fragment](https://github.com/radiantearth/stac-api-spec/blob/main/fragments/itemcollection/README.md).
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct ItemCollection {
    /// Always "FeatureCollection" to provide compatibility with GeoJSON.
    pub r#type: String,

    /// A possibly-empty array of Item objects.
    #[serde(rename = "features")]
    pub items: Vec<Item>,

    /// An array of Links related to this ItemCollection.
    pub links: Vec<Link>,

    /// The number of Items that meet the selection parameters, possibly estimated.
    #[serde(skip_serializing_if = "Option::is_none", rename = "numberMatched")]
    pub number_matched: Option<u64>,

    /// The number of Items in the features array.
    #[serde(skip_serializing_if = "Option::is_none", rename = "numberReturned")]
    pub number_returned: Option<u64>,

    /// The search-related metadata for the [ItemCollection].
    ///
    /// Part of the [context extension](https://github.com/stac-api-extensions/context).
    #[serde(skip_serializing_if = "Option::is_none")]
    pub context: Option<Context>,

    /// Additional fields.
    #[serde(flatten)]
    pub additional_fields: Map<String, Value>,
}

/// The search-related metadata for the [ItemCollection].
///
/// Part of the [context extension](https://github.com/stac-api-extensions/context).
#[derive(Debug, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Context {
    /// The count of results returned by this response. Equal to the cardinality
    /// of features array.
    pub returned: u64,

    /// The maximum number of results to which the result was limited.
    pub limit: Option<u64>,

    /// The count of total number of results that match for this query, possibly
    /// estimated, particularly in the context of NoSQL data stores.
    #[serde(skip_serializing_if = "Option::is_none")]
    pub matched: Option<u64>,

    /// Additional fields.
    #[serde(flatten)]
    pub additional_fields: Map<String, Value>,
}

impl ItemCollection {
    /// Creates a new [ItemCollection] from a vector of items.
    ///
    /// # Examples
    ///
    /// ```
    /// let item: stac_api::Item = stac::Item::new("an-id").try_into().unwrap();
    /// let item_collection = stac_api::ItemCollection::new(vec![item]).unwrap();
    /// ```
    pub fn new(items: Vec<Item>) -> Result<ItemCollection> {
        let number_returned = items.len();
        Ok(ItemCollection {
            r#type: ITEM_COLLECTION_TYPE.to_string(),
            items,
            links: Vec::new(),
            number_matched: None,
            number_returned: Some(number_returned.try_into()?),
            context: None,
            additional_fields: Map::new(),
        })
    }
}

impl Links for ItemCollection {
    fn links(&self) -> &[Link] {
        &self.links
    }
    fn links_mut(&mut self) -> &mut Vec<Link> {
        &mut self.links
    }
}

impl Default for ItemCollection {
    fn default() -> Self {
        ItemCollection {
            r#type: "FeatureCollection".to_string(),
            items: Vec::new(),
            links: Vec::new(),
            number_matched: None,
            number_returned: None,
            context: None,
            additional_fields: Map::default(),
        }
    }
}