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}