stac_api/
fields.rs

1use serde::{Deserialize, Serialize};
2use std::{
3    convert::Infallible,
4    fmt::{Display, Formatter},
5    str::FromStr,
6};
7
8/// Include/exclude fields from item collections.
9///
10/// By default, STAC API endpoints that return Item objects return every field
11/// of those Items. However, Item objects can have hundreds of fields, or large
12/// geometries, and even smaller Item objects can add up when large numbers of
13/// them are in results. Frequently, not all fields in an Item are used, so this
14/// specification provides a mechanism for clients to request that servers to
15/// explicitly include or exclude certain fields.
16#[derive(Clone, Debug, Serialize, Deserialize, Default, PartialEq)]
17pub struct Fields {
18    /// Fields to include.
19    #[serde(skip_serializing_if = "Vec::is_empty")]
20    pub include: Vec<String>,
21
22    /// Fields to exclude.
23    #[serde(skip_serializing_if = "Vec::is_empty")]
24    pub exclude: Vec<String>,
25}
26
27impl FromStr for Fields {
28    type Err = Infallible;
29
30    fn from_str(s: &str) -> Result<Self, Self::Err> {
31        let mut include = Vec::new();
32        let mut exclude = Vec::new();
33        for field in s.split(',').filter(|s| !s.is_empty()) {
34            if let Some(field) = field.strip_prefix('-') {
35                exclude.push(field.to_string());
36            } else if let Some(field) = field.strip_prefix('+') {
37                include.push(field.to_string());
38            } else {
39                include.push(field.to_string());
40            }
41        }
42        Ok(Fields { include, exclude })
43    }
44}
45
46impl Display for Fields {
47    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
48        let mut fields = Vec::new();
49        for include in &self.include {
50            fields.push(include.to_string());
51        }
52        for exclude in &self.exclude {
53            fields.push(format!("-{}", exclude));
54        }
55        write!(f, "{}", fields.join(","))
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::Fields;
62
63    #[test]
64    fn empty() {
65        assert_eq!(Fields::default(), "".parse().unwrap());
66    }
67
68    #[test]
69    fn plus() {
70        assert_eq!(
71            Fields {
72                include: vec!["foo".to_string()],
73                exclude: Vec::new(),
74            },
75            "+foo".parse().unwrap()
76        );
77    }
78
79    #[test]
80    fn includes() {
81        assert_eq!(
82            Fields {
83                include: vec![
84                    "id".to_string(),
85                    "type".to_string(),
86                    "geometry".to_string(),
87                    "bbox".to_string(),
88                    "properties".to_string(),
89                    "links".to_string(),
90                    "assets".to_string(),
91                ],
92                exclude: Vec::new()
93            },
94            "id,type,geometry,bbox,properties,links,assets"
95                .parse()
96                .unwrap()
97        );
98        assert_eq!(
99            Fields {
100                include: vec![
101                    "id".to_string(),
102                    "type".to_string(),
103                    "geometry".to_string(),
104                    "bbox".to_string(),
105                    "properties".to_string(),
106                    "links".to_string(),
107                    "assets".to_string(),
108                ],
109                exclude: Vec::new()
110            }
111            .to_string(),
112            "id,type,geometry,bbox,properties,links,assets"
113        )
114    }
115
116    #[test]
117    fn exclude() {
118        assert_eq!(
119            Fields {
120                include: Vec::new(),
121                exclude: vec!["geometry".to_string()]
122            },
123            "-geometry".parse().unwrap()
124        );
125        assert_eq!(
126            Fields {
127                include: Vec::new(),
128                exclude: vec!["geometry".to_string()]
129            }
130            .to_string(),
131            "-geometry"
132        );
133    }
134}