Skip to main content

opensearch_dsl/search/queries/shape/
shape_query.rs

1use serde::{Deserialize, Deserializer, Serialize};
2
3use crate::{search::*, util::*};
4
5/// Queries documents that contain fields indexed using the `shape` type.
6///
7/// Requires the [`shape` Mapping](https://www.elastic.co/guide/en/opensearch/reference/current/shape.html).
8///
9/// <https://www.elastic.co/guide/en/opensearch/reference/current/query-dsl-shape-query.html>
10#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
11#[serde(remote = "Self")]
12pub struct ShapeQuery {
13    #[serde(skip)]
14    field: String,
15
16    #[serde(skip)]
17    shape: InlineShape,
18
19    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
20    ignore_unmapped: Option<bool>,
21
22    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
23    boost: Option<f32>,
24
25    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
26    _name: Option<String>,
27}
28
29#[derive(Default, Debug, Clone, PartialEq, Deserialize, Serialize)]
30struct InlineShape {
31    shape: Shape,
32
33    #[serde(default, skip_serializing_if = "ShouldSkip::should_skip")]
34    relation: Option<SpatialRelation>,
35}
36
37impl Query {
38    /// Creates an instance of [`ShapeQuery`]
39    ///
40    /// - `field` - Field you wish to search
41    /// - `shape` - Shape you with to search
42    pub fn shape<S, T>(field: S, shape: T) -> ShapeQuery
43    where
44        S: ToString,
45        T: Into<Shape>,
46    {
47        ShapeQuery {
48            field: field.to_string(),
49            shape: InlineShape {
50                shape: shape.into(),
51                relation: None,
52            },
53            ignore_unmapped: None,
54            boost: None,
55            _name: None,
56        }
57    }
58}
59
60impl ShapeQuery {
61    add_boost_and_name!();
62
63    /// The [shape strategy](https://www.elastic.co/guide/en/opensearch/reference/current/geo-shape.html#spatial-strategy)
64    /// mapping parameter determines which spatial relation operators may be
65    /// used at search time.
66    pub fn relation(mut self, relation: SpatialRelation) -> Self {
67        self.shape.relation = Some(relation);
68        self
69    }
70
71    /// When set to true the `ignore_unmapped` option will ignore an unmapped
72    /// field and will not match any documents for this query. This can be
73    /// useful when querying multiple indexes which might have different
74    /// mappings. When set to `false` (the default value) the query will throw
75    /// an exception if the field is not mapped.
76    pub fn ignore_unmapped(mut self, ignore_unmapped: bool) -> Self {
77        self.ignore_unmapped = Some(ignore_unmapped);
78        self
79    }
80}
81
82impl ShouldSkip for ShapeQuery {}
83
84serialize_with_root_key_value_pair!("shape": ShapeQuery, field, shape);
85impl<'de> Deserialize<'de> for ShapeQuery {
86    fn deserialize<D>(deserializer: D) -> ::std::result::Result<Self, D::Error>
87    where
88        D: Deserializer<'de>,
89    {
90        use std::fmt;
91        struct WrapperVisitor;
92
93        impl<'de> serde::de::Visitor<'de> for WrapperVisitor {
94            type Value = ShapeQuery;
95
96            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
97                formatter.write_str("struct shape")
98            }
99
100            fn visit_map<A>(self, mut map: A) -> Result<ShapeQuery, A::Error>
101            where
102                A: serde::de::MapAccess<'de>,
103            {
104                let mut query: ShapeQuery = ShapeQuery::default();
105                let mut found_value = false;
106
107                while let Some(key) = map.next_key::<String>()? {
108                    if key == "shape" {
109                        let inner_map =
110                            map.next_value::<serde_json::Map<String, serde_json::Value>>()?;
111                        for (k, v) in inner_map.iter() {
112                            match k.as_str() {
113                                "field" => match v.as_str() {
114                                    Some(field) => {
115                                        query.field = field.to_string();
116                                    }
117                                    None => {
118                                        return Err(serde::de::Error::invalid_type(
119                                            serde::de::Unexpected::Other("not a string"),
120                                            &"a string",
121                                        ));
122                                    }
123                                },
124                                "boost" => match v.as_f64() {
125                                    Some(boost) => {
126                                        query.boost = Some(boost as f32);
127                                    }
128                                    None => {
129                                        return Err(serde::de::Error::invalid_type(
130                                            serde::de::Unexpected::Other("not a float"),
131                                            &"a float",
132                                        ));
133                                    }
134                                },
135                                "_name" => match v.as_str() {
136                                    Some(_name) => {
137                                        query._name = Some(_name.to_string());
138                                    }
139                                    None => {
140                                        return Err(serde::de::Error::invalid_type(
141                                            serde::de::Unexpected::Other("not a string"),
142                                            &"a string",
143                                        ));
144                                    }
145                                },
146                                "ignore_unmapped" => match v.as_bool() {
147                                    Some(ignore_unmapped) => {
148                                        query.ignore_unmapped = Some(ignore_unmapped);
149                                    }
150                                    None => {
151                                        return Err(serde::de::Error::invalid_type(
152                                            serde::de::Unexpected::Other("not a boolean"),
153                                            &"a boolean",
154                                        ));
155                                    }
156                                },
157                                "value" => {
158                                    let shape = serde_json::from_value::<InlineShape>(v.clone());
159                                    match shape {
160                                        Ok(shape) => {
161                                            query.shape = shape;
162                                            found_value = true;
163                                        }
164                                        Err(e) => {
165                                            return Err(serde::de::Error::custom(format!(
166                                                "error parsing distance: {}",
167                                                e
168                                            )));
169                                        }
170                                    }
171                                }
172                                _ => {
173                                    query.field = k.to_owned();
174                                    let value = serde_json::from_value::<InlineShape>(v.clone());
175                                    match value {
176                                        Ok(value) => {
177                                            query.shape = value;
178                                            found_value = true;
179                                        }
180                                        Err(e) => {
181                                            return Err(serde::de::Error::custom(format!(
182                                                "error parsing {}: {}",
183                                                k, e
184                                            )));
185                                        }
186                                    }
187                                }
188                            }
189                        }
190                    }
191                }
192                if found_value {
193                    Ok(query)
194                } else {
195                    Err(serde::de::Error::missing_field("location value"))
196                }
197            }
198        }
199
200        deserializer.deserialize_struct("Wrapper", &["shape"], WrapperVisitor)
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_serialization() {
210        assert_serialize_query(
211            Query::shape("pin.location", Shape::point([2.2, 1.1])),
212            json!({
213                "shape": {
214                    "pin.location": {
215                        "shape": {
216                            "type": "point",
217                            "coordinates": [2.2, 1.1]
218                        }
219                    },
220                }
221            }),
222        );
223
224        assert_serialize_query(
225            Query::shape("pin.location", Shape::point([2.2, 1.1]))
226                .boost(2)
227                .name("test")
228                .ignore_unmapped(true)
229                .relation(SpatialRelation::Within),
230            json!({
231                "shape": {
232                    "_name": "test",
233                    "boost": 2.0,
234                    "ignore_unmapped": true,
235                    "pin.location": {
236                        "shape": {
237                            "type": "point",
238                            "coordinates": [2.2, 1.1]
239                        },
240                        "relation": "WITHIN"
241                    },
242                }
243            }),
244        );
245    }
246}