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 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}