what3words_api/models/
autosuggest.rs

1use super::gridsection::BoundingBox;
2use super::location::{Circle, Coordinates, Polygon, Square};
3use crate::service::{Error, ToHashMap, Validator};
4use serde::Deserialize;
5use std::{collections::HashMap, fmt};
6
7#[derive(Debug, Clone)]
8pub struct Autosuggest {
9    input: Option<String>,
10    n_results: Option<String>,
11    focus: Option<String>,
12    n_focus_result: Option<String>,
13    clip_to_country: Option<String>,
14    clip_to_bounding_box: Option<BoundingBox>,
15    clip_to_circle: Option<Circle>,
16    clip_to_polygon: Option<Polygon>,
17    input_type: Option<String>,
18    language: Option<String>,
19    prefer_land: Option<bool>,
20    locale: Option<String>,
21}
22
23impl Validator for Autosuggest {
24    fn validate(&self) -> std::result::Result<(), Error> {
25        if let Some(ref clip_to_polygon) = &self.clip_to_polygon {
26            clip_to_polygon.validate()?;
27        }
28        Ok(())
29    }
30}
31
32impl ToHashMap for Autosuggest {
33    fn to_hash_map<'a>(&self) -> Result<HashMap<&'a str, String>, Error> {
34        self.validate()?;
35        let mut map = HashMap::new();
36        if let Some(ref input) = &self.input {
37            map.insert("input", input.into());
38        }
39        if let Some(ref n_results) = &self.n_results {
40            map.insert("n-results", n_results.into());
41        }
42        if let Some(ref focus) = &self.focus {
43            map.insert("focus", focus.into());
44        }
45        if let Some(ref n_focus_result) = &self.n_focus_result {
46            map.insert("n-focus-result", n_focus_result.into());
47        }
48        if let Some(ref clip_to_country) = &self.clip_to_country {
49            map.insert("clip-to-country", clip_to_country.into());
50        }
51        if let Some(ref clip_to_bounding_box) = &self.clip_to_bounding_box {
52            map.insert("clip-to-bounding-box", clip_to_bounding_box.to_string());
53        }
54        if let Some(ref clip_to_circle) = &self.clip_to_circle {
55            map.insert("clip-to-circle", clip_to_circle.to_string());
56        }
57        if let Some(ref clip_to_polygon) = &self.clip_to_polygon {
58            map.insert("clip-to-polygon", clip_to_polygon.to_string());
59        }
60        if let Some(ref input_type) = &self.input_type {
61            map.insert("input-type", input_type.into());
62        }
63        if let Some(ref language) = &self.language {
64            map.insert("language", language.into());
65        }
66        if let Some(ref locale) = &self.locale {
67            map.insert("locale", locale.into());
68        }
69        if let Some(ref prefer_land) = &self.prefer_land {
70            map.insert("prefer-land", prefer_land.to_string());
71        }
72        Ok(map)
73    }
74}
75
76impl Autosuggest {
77    pub fn new(input: impl Into<String>) -> Self {
78        Self {
79            input: Some(input.into()),
80            n_results: None,
81            focus: None,
82            n_focus_result: None,
83            clip_to_country: None,
84            clip_to_bounding_box: None,
85            clip_to_circle: None,
86            clip_to_polygon: None,
87            input_type: None,
88            language: None,
89            prefer_land: None,
90            locale: None,
91        }
92    }
93    pub fn n_results(mut self, n_results: impl Into<String>) -> Self {
94        self.n_results = Some(n_results.into());
95        self
96    }
97
98    pub fn focus(mut self, focus: &Coordinates) -> Self {
99        self.focus = Some(focus.to_string());
100        self
101    }
102
103    pub fn n_focus_result(mut self, n_focus_result: impl Into<String>) -> Self {
104        self.n_focus_result = Some(n_focus_result.into());
105        self
106    }
107
108    pub fn clip_to_country(mut self, clip_to_country: &[impl Into<String> + Clone]) -> Self {
109        let countries = clip_to_country
110            .iter()
111            .map(|c| c.clone().into())
112            .collect::<Vec<String>>()
113            .join(",");
114        self.clip_to_country = Some(countries);
115        self
116    }
117
118    pub fn clip_to_bounding_box(mut self, clip_to_bounding_box: &BoundingBox) -> Self {
119        self.clip_to_bounding_box = Some(clip_to_bounding_box.clone());
120        self
121    }
122
123    pub fn clip_to_circle(mut self, clip_to_circle: &Circle) -> Self {
124        self.clip_to_circle = Some(clip_to_circle.clone());
125        self
126    }
127
128    pub fn clip_to_polygon(mut self, clip_to_polygon: &Polygon) -> Self {
129        self.clip_to_polygon = Some(clip_to_polygon.clone());
130        self
131    }
132
133    pub fn input_type(mut self, input_type: impl Into<String>) -> Self {
134        self.input_type = Some(input_type.into());
135        self
136    }
137
138    pub fn language(mut self, language: impl Into<String>) -> Self {
139        self.language = Some(language.into());
140        self
141    }
142
143    pub fn prefer_land(mut self, prefer_land: impl Into<bool>) -> Self {
144        self.prefer_land = Some(prefer_land.into());
145        self
146    }
147
148    pub fn locale(mut self, locale: impl Into<String>) -> Self {
149        self.locale = Some(locale.into());
150        self
151    }
152}
153
154impl fmt::Display for Autosuggest {
155    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
156        write!(f, "{:?}", self)
157    }
158}
159
160#[derive(Debug, Clone)]
161pub struct AutosuggestSelection {
162    raw_input: Option<String>,
163    options: Option<Autosuggest>,
164    suggestion: Option<Suggestion>,
165}
166
167impl ToHashMap for AutosuggestSelection {
168    fn to_hash_map<'a>(&self) -> Result<HashMap<&'a str, String>, Error> {
169        let mut map = HashMap::new();
170        if let Some(ref raw_input) = &self.raw_input {
171            map.insert("raw-input", raw_input.clone());
172        }
173        if let Some(ref suggestion) = &self.suggestion {
174            map.insert("rank", suggestion.rank.to_string());
175            map.insert("selection", suggestion.words.clone());
176        }
177        if let Some(ref options) = &self.options {
178            let options_map = options.to_hash_map()?;
179            map.extend(options_map);
180        }
181        Ok(map)
182    }
183}
184
185impl AutosuggestSelection {
186    pub fn new(raw_input: impl Into<String>, suggestion: &Suggestion) -> Self {
187        Self {
188            raw_input: Some(raw_input.into()),
189            options: None,
190            suggestion: Some(suggestion.clone()),
191        }
192    }
193    pub fn options(mut self, options: &Autosuggest) -> Self {
194        self.options = Some(options.clone());
195        self
196    }
197}
198
199#[derive(Debug, Clone, Deserialize)]
200pub struct Suggestion {
201    pub country: String,
202    #[serde(rename = "nearestPlace")]
203    pub nearest_place: String,
204    pub words: String,
205    pub rank: u32,
206    pub language: String,
207    #[serde(rename = "distanceToFocusKm")]
208    pub distance_to_focus_km: Option<u32>,
209    pub square: Option<Square>,
210    pub coordinates: Option<Coordinates>,
211    pub map: Option<String>,
212}
213
214#[derive(Debug, Deserialize)]
215pub struct AutosuggestResult {
216    pub suggestions: Vec<Suggestion>,
217}
218
219#[cfg(test)]
220mod autosuggest_tests {
221    use super::*;
222
223    #[test]
224    fn test_autosuggest_display() {
225        let autosuggest = Autosuggest::new("test input")
226            .n_results("5")
227            .focus(&Coordinates {
228                lat: 51.521251,
229                lng: -0.203586,
230            })
231            .n_focus_result("3")
232            .clip_to_country(&["GB"])
233            .clip_to_bounding_box(&BoundingBox::new(
234                51.521251, -0.203586, 51.521251, -0.203586,
235            ))
236            .clip_to_circle(&Circle::new(51.521251, -0.203586, 1000))
237            .clip_to_polygon(&Polygon::new(&[
238                Coordinates::new(51.521251, -0.203586),
239                Coordinates::new(51.521251, -0.203586),
240                Coordinates::new(51.521251, -0.203581),
241            ]))
242            .input_type("text")
243            .language("en")
244            .prefer_land(true)
245            .locale("en-GB");
246
247        assert_eq!(
248                    format!("{}", autosuggest),
249                    "Autosuggest { input: Some(\"test input\"), n_results: Some(\"5\"), focus: Some(\"51.521251,-0.203586\"), n_focus_result: Some(\"3\"), clip_to_country: Some(\"GB\"), clip_to_bounding_box: Some(BoundingBox { southwest: Coordinates { lat: 51.521251, lng: -0.203586 }, northeast: Coordinates { lat: 51.521251, lng: -0.203586 } }), clip_to_circle: Some(Circle { lat: 51.521251, lng: -0.203586, radius: 1000 }), clip_to_polygon: Some(Polygon { coordinates: [Coordinates { lat: 51.521251, lng: -0.203586 }, Coordinates { lat: 51.521251, lng: -0.203586 }, Coordinates { lat: 51.521251, lng: -0.203581 }] }), input_type: Some(\"text\"), language: Some(\"en\"), prefer_land: Some(true), locale: Some(\"en-GB\") }"
250                );
251    }
252
253    #[test]
254    fn test_autosuggest_to_hash_map() {
255        let autosuggest = Autosuggest::new("test input")
256            .n_results("5")
257            .focus(&Coordinates {
258                lat: 51.521251,
259                lng: -0.203586,
260            })
261            .n_focus_result("3")
262            .clip_to_country(&["GB"])
263            .clip_to_bounding_box(&BoundingBox::new(
264                51.521251, -0.203586, 51.521251, -0.203586,
265            ))
266            .clip_to_circle(&Circle::new(51.521251, -0.203586, 1000))
267            .clip_to_polygon(&Polygon::new(&[
268                Coordinates::new(51.521251, -0.203586),
269                Coordinates::new(51.521251, -0.203586),
270                Coordinates::new(51.521251, -0.203586),
271            ]))
272            .input_type("text")
273            .language("en")
274            .prefer_land(true)
275            .locale("en-GB");
276
277        if let Ok(map) = autosuggest.to_hash_map() {
278            assert_eq!(map.get("input"), Some(&"test input".to_string()));
279            assert_eq!(map.get("n-results"), Some(&"5".to_string()));
280            assert_eq!(map.get("focus"), Some(&"51.521251,-0.203586".to_string()));
281            assert_eq!(map.get("n-focus-result"), Some(&"3".to_string()));
282            assert_eq!(map.get("clip-to-country"), Some(&"GB".to_string()));
283            assert_eq!(
284                map.get("clip-to-bounding-box"),
285                Some(&"51.521251,-0.203586,51.521251,-0.203586".to_string())
286            );
287            assert_eq!(
288                map.get("clip-to-circle"),
289                Some(&"51.521251,-0.203586,1000".to_string())
290            );
291            assert_eq!(
292                map.get("clip-to-polygon"),
293                Some(&"51.521251,-0.203586,51.521251,-0.203586,51.521251,-0.203586".to_string())
294            );
295            assert_eq!(map.get("input-type"), Some(&"text".to_string()));
296            assert_eq!(map.get("language"), Some(&"en".to_string()));
297            assert_eq!(map.get("prefer-land"), Some(&"true".to_string()));
298            assert_eq!(map.get("locale"), Some(&"en-GB".to_string()));
299        }
300    }
301
302    #[test]
303    fn test_autosuggest_validator() {
304        // Test valid polygon
305        let autosuggest = Autosuggest::new("test input").clip_to_polygon(&Polygon::new(&[
306            Coordinates::new(51.521251, -0.203586),
307            Coordinates::new(51.521251, -0.203586),
308            Coordinates::new(51.521251, -0.203581),
309            Coordinates::new(51.521251, -0.203586),
310        ]));
311        assert!(autosuggest.validate().is_ok());
312
313        let invalid_autosuggest = Autosuggest::new("test input").clip_to_polygon(&Polygon::new(&[
314            Coordinates::new(51.521251, -0.203586),
315            Coordinates::new(51.521251, -0.203586),
316        ]));
317        assert!(invalid_autosuggest.validate().is_err());
318    }
319
320    #[test]
321    fn test_autosuggest_empty() {
322        let autosuggest = Autosuggest::new("");
323        if let Ok(map) = autosuggest.to_hash_map() {
324            assert_eq!(map.get("input"), Some(&"".to_string()));
325            assert_eq!(map.len(), 1);
326        }
327    }
328
329    #[test]
330    fn test_autosuggest_selection_empty() {
331        let suggestion = Suggestion {
332            country: "".to_string(),
333            nearest_place: "".to_string(),
334            words: "".to_string(),
335            rank: 0,
336            language: "".to_string(),
337            distance_to_focus_km: None,
338            square: None,
339            coordinates: None,
340            map: None,
341        };
342
343        let selection = AutosuggestSelection::new("", &suggestion);
344
345        if let Ok(map) = selection.to_hash_map() {
346            assert_eq!(map.get("raw-input"), Some(&"".to_string()));
347            assert_eq!(map.get("rank"), Some(&"0".to_string()));
348            assert_eq!(map.get("selection"), Some(&"".to_string()));
349            assert_eq!(map.len(), 3);
350        }
351    }
352
353    #[test]
354    fn test_autosuggest_selection_to_hash_map() {
355        let suggestion = Suggestion {
356            country: "GB".to_string(),
357            nearest_place: "London".to_string(),
358            words: "index.home.raft".to_string(),
359            rank: 1,
360            language: "en".to_string(),
361            distance_to_focus_km: Some(10),
362            square: None,
363            coordinates: None,
364            map: None,
365        };
366
367        let autosuggest = Autosuggest::new("test input")
368            .n_results("5")
369            .focus(&Coordinates {
370                lat: 51.521251,
371                lng: -0.203586,
372            });
373
374        let selection = AutosuggestSelection::new("test input", &suggestion).options(&autosuggest);
375
376        if let Ok(map) = selection.to_hash_map() {
377            assert_eq!(map.get("raw-input"), Some(&"test input".to_string()));
378            assert_eq!(map.get("rank"), Some(&"1".to_string()));
379            assert_eq!(map.get("selection"), Some(&"index.home.raft".to_string()));
380            assert_eq!(map.get("input"), Some(&"test input".to_string()));
381            assert_eq!(map.get("n-results"), Some(&"5".to_string()));
382            assert_eq!(map.get("focus"), Some(&"51.521251,-0.203586".to_string()));
383        }
384    }
385}