stac_api/
filter.rs

1use crate::{Error, Result};
2use cql2::Expr;
3use serde::{Deserialize, Serialize};
4use serde_json::{Map, Value};
5use std::{convert::Infallible, str::FromStr};
6
7/// The language of the filter expression.
8#[derive(Clone, Debug, Serialize, Deserialize, PartialEq)]
9#[serde(tag = "filter-lang", content = "filter")]
10pub enum Filter {
11    /// `cql2-text`
12    #[serde(rename = "cql2-text")]
13    Cql2Text(String),
14
15    /// `cql2-json`
16    #[serde(rename = "cql2-json")]
17    Cql2Json(Map<String, Value>),
18}
19
20impl Filter {
21    /// Converts this filter to cql2-json.
22    pub fn into_cql2_json(self) -> Result<Filter> {
23        match self {
24            Filter::Cql2Json(_) => Ok(self),
25            Filter::Cql2Text(text) => {
26                let expr = cql2::parse_text(&text).map_err(Box::new)?;
27                Ok(Filter::Cql2Json(serde_json::from_value(
28                    serde_json::to_value(expr)?,
29                )?))
30            }
31        }
32    }
33
34    /// Converts this filter to cql2-json.
35    pub fn into_cql2_text(self) -> Result<Filter> {
36        match self {
37            Filter::Cql2Text(_) => Ok(self),
38            Filter::Cql2Json(json) => {
39                let expr: Expr = serde_json::from_value(Value::Object(json))?;
40                Ok(Filter::Cql2Text(expr.to_text().map_err(Box::new)?))
41            }
42        }
43    }
44}
45
46impl Default for Filter {
47    fn default() -> Self {
48        Filter::Cql2Json(Default::default())
49    }
50}
51
52impl FromStr for Filter {
53    type Err = Infallible;
54    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
55        Ok(Filter::Cql2Text(s.to_string()))
56    }
57}
58
59impl TryFrom<Filter> for Expr {
60    type Error = Error;
61    fn try_from(value: Filter) -> Result<Self> {
62        match value {
63            Filter::Cql2Json(json) => {
64                serde_json::from_value(Value::Object(json)).map_err(Error::from)
65            }
66            Filter::Cql2Text(text) => cql2::parse_text(&text).map_err(|e| Error::from(Box::new(e))),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use super::Filter;
74    use cql2::Expr;
75    use serde_json::json;
76
77    #[test]
78    fn json() {
79        let filter = Filter::Cql2Json(json!({
80                  "filter": {
81                    "op" : "and",
82                    "args": [
83                      {
84                        "op": "=",
85                        "args": [ { "property": "id" }, "LC08_L1TP_060247_20180905_20180912_01_T1_L1TP" ]
86                      },
87                      {
88                        "op": "=",
89                        "args" : [ { "property": "collection" }, "landsat8_l1tp" ]
90                      }
91                    ]
92                  }
93                }
94            ).as_object().unwrap().clone(),
95        );
96        let value = serde_json::to_value(filter).unwrap();
97        assert_eq!(value["filter-lang"], "cql2-json");
98        assert!(value.get("filter").is_some());
99    }
100
101    #[test]
102    fn text() {
103        let filter = Filter::Cql2Text(
104            "id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp'"
105                .to_string(),
106        );
107        let value = serde_json::to_value(filter).unwrap();
108        assert_eq!(value["filter-lang"], "cql2-text");
109        assert!(value.get("filter").is_some());
110    }
111
112    #[test]
113    fn expr() {
114        let filter = Filter::Cql2Text(
115            "id='LC08_L1TP_060247_20180905_20180912_01_T1_L1TP' AND collection='landsat8_l1tp'"
116                .to_string(),
117        );
118        let _: Expr = filter.try_into().unwrap();
119    }
120}