algoliasearch/index/mod.rs
1use std::{fmt, marker::PhantomData};
2
3use chrono::{DateTime, Utc};
4
5use reqwest::{header::HeaderMap, Client};
6use serde::{
7 de::{self, Deserialize, DeserializeOwned, Deserializer, Visitor},
8 ser::{Serialize, Serializer},
9};
10
11use crate::error::Error;
12
13pub mod settings;
14pub mod task;
15
16#[derive(Debug, Deserialize)]
17#[serde(rename_all = "camelCase")]
18/// Search result
19pub struct SearchResult<T> {
20 /// Hits
21 pub hits: Vec<T>,
22 /// Number of hits
23 pub nb_hits: u64,
24 /// Page
25 pub page: u64,
26 /// Number of pages
27 pub nb_pages: u64,
28 /// Number of hits per page
29 pub hits_per_page: u64,
30 #[serde(rename = "processingTimeMS")]
31 /// Processing time (ms)
32 pub processing_time_ms: u64,
33 /// Is the search exhaustive?
34 pub exhaustive_nb_hits: bool,
35 /// Query
36 pub query: String,
37 /// Params
38 pub params: String,
39}
40
41#[derive(Clone, Debug, PartialEq, Hash)]
42/// [https://www.algolia.com/doc/api-reference/api-parameters/aroundRadius/](https://www.algolia.com/doc/api-reference/api-parameters/aroundRadius/)
43pub enum AroundRadius {
44 #[allow(missing_docs)]
45 All,
46 #[allow(missing_docs)]
47 Radius(u64),
48}
49
50struct AroundRadiusVisitor;
51
52impl<'de> Visitor<'de> for AroundRadiusVisitor {
53 type Value = AroundRadius;
54
55 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
56 formatter.write_str("all or radius in meters")
57 }
58
59 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
60 where
61 E: de::Error,
62 {
63 if value == "all" {
64 Ok(AroundRadius::All)
65 } else {
66 Err(E::custom(format!(r#"expected "all", got "{}""#, value)))
67 }
68 }
69
70 fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
71where {
72 Ok(AroundRadius::Radius(value))
73 }
74}
75
76impl<'de> Deserialize<'de> for AroundRadius {
77 fn deserialize<D>(deserializer: D) -> Result<AroundRadius, D::Error>
78 where
79 D: Deserializer<'de>,
80 {
81 deserializer.deserialize_any(AroundRadiusVisitor)
82 }
83}
84
85impl Serialize for AroundRadius {
86 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
87 where
88 S: Serializer,
89 {
90 match self {
91 AroundRadius::All => serializer.serialize_str("all"),
92 AroundRadius::Radius(v) => serializer.serialize_u64(*v),
93 }
94 }
95}
96
97#[cfg(test)]
98mod ignore_plurals_tests {
99 use super::*;
100 use serde_json;
101
102 #[test]
103 fn test_serialize() {
104 assert_eq!(
105 serde_json::to_string(&AroundRadius::All).unwrap(),
106 r#""all""#
107 );
108 assert_eq!(
109 serde_json::to_string(&AroundRadius::Radius(20)).unwrap(),
110 r#"20"#
111 );
112 }
113
114 #[test]
115 fn test_deserialize() {
116 assert_eq!(
117 serde_json::from_str::<AroundRadius>(r#""all""#).unwrap(),
118 AroundRadius::All
119 );
120 assert_eq!(
121 serde_json::from_str::<AroundRadius>(r#"20"#).unwrap(),
122 AroundRadius::Radius(20)
123 );
124 assert_eq!(
125 serde_json::from_str::<AroundRadius>(r#""unknown""#)
126 .unwrap_err()
127 .to_string(),
128 "expected \"all\", got \"unknown\" at line 1 column 9"
129 );
130 }
131}
132
133#[derive(Clone, Debug, Serialize, Deserialize)]
134pub enum StringOrVecOfString {
135 String(String),
136 VecOfString(Vec<String>),
137}
138
139#[derive(Debug, Serialize, Default, Builder)]
140#[serde(rename_all = "camelCase")]
141#[builder(default)]
142/// algolia search parameters
143/// see [https://www.algolia.com/doc/api-reference/search-api-parameters/](https://www.algolia.com/doc/api-reference/search-api-parameters/)
144pub struct SearchQuery {
145 // search
146 #[builder(setter(into))]
147 /// [https://www.algolia.com/doc/api-reference/api-parameters/query/](https://www.algolia.com/doc/api-reference/api-parameters/query/)
148 query: Option<String>,
149
150 // attributes
151 #[builder(setter(into))]
152 #[serde(skip_serializing_if = "Option::is_none")]
153 /// [https://www.algolia.com/doc/api-reference/api-parameters/attributesToRetrieve/](https://www.algolia.com/doc/api-reference/api-parameters/attributesToRetrieve/)
154 attributes_to_retrieve: Option<Vec<String>>,
155 #[builder(setter(into))]
156 #[serde(skip_serializing_if = "Option::is_none")]
157 /// [](https://www.algolia.com/doc/api-reference/api-parameters/restrictSearchableAttributes/https://www.algolia.com/doc/api-reference/api-parameters/restrictSearchableAttributes/)
158 restrict_searchable_attributes: Option<Vec<String>>,
159
160 // filtering
161 #[builder(setter(into))]
162 #[serde(skip_serializing_if = "Option::is_none")]
163 /// [https://www.algolia.com/doc/api-reference/api-parameters/filters/](https://www.algolia.com/doc/api-reference/api-parameters/filters/)
164 filters: Option<String>,
165 #[builder(setter(into))]
166 #[serde(skip_serializing_if = "Option::is_none")]
167 /// [https://www.algolia.com/doc/api-reference/api-parameters/facetFilters/](https://www.algolia.com/doc/api-reference/api-parameters/facetFilters/)
168 facet_filters: Option<Vec<StringOrVecOfString>>,
169 #[builder(setter(into))]
170 #[serde(skip_serializing_if = "Option::is_none")]
171 /// [https://www.algolia.com/doc/api-reference/api-parameters/optionalFilters/](https://www.algolia.com/doc/api-reference/api-parameters/optionalFilters/)
172 optional_filters: Option<Vec<StringOrVecOfString>>,
173 #[builder(setter(into))]
174 #[serde(skip_serializing_if = "Option::is_none")]
175 /// [https://www.algolia.com/doc/api-reference/api-parameters/numericFilters/](https://www.algolia.com/doc/api-reference/api-parameters/numericFilters/)
176 numeric_filters: Option<Vec<String>>,
177 #[builder(setter(into))]
178 #[serde(skip_serializing_if = "Option::is_none")]
179 /// [https://www.algolia.com/doc/api-reference/api-parameters/tagFilters/](https://www.algolia.com/doc/api-reference/api-parameters/tagFilters/)
180 tag_filters: Option<StringOrVecOfString>,
181 #[builder(setter(into))]
182 #[serde(skip_serializing_if = "Option::is_none")]
183 /// [https://www.algolia.com/doc/api-reference/api-parameters/sumOrFiltersScores/](https://www.algolia.com/doc/api-reference/api-parameters/sumOrFiltersScores/)
184 sum_or_filters_scores: Option<bool>,
185
186 // faceting
187 #[builder(setter(into))]
188 #[serde(skip_serializing_if = "Option::is_none")]
189 /// [https://www.algolia.com/doc/api-reference/api-parameters/facets/](https://www.algolia.com/doc/api-reference/api-parameters/facets/)
190 facets: Option<Vec<String>>,
191 #[builder(setter(into))]
192 #[serde(skip_serializing_if = "Option::is_none")]
193 /// [https://www.algolia.com/doc/api-reference/api-parameters/maxValuesPerFacet/](https://www.algolia.com/doc/api-reference/api-parameters/maxValuesPerFacet/)
194 max_values_per_facet: Option<u64>,
195 #[builder(setter(into))]
196 #[serde(skip_serializing_if = "Option::is_none")]
197 /// [https://www.algolia.com/doc/api-reference/api-parameters/facetingAfterDistinct/](https://www.algolia.com/doc/api-reference/api-parameters/facetingAfterDistinct/)
198 faceting_after_distinct: Option<bool>,
199 #[builder(setter(into))]
200 #[serde(skip_serializing_if = "Option::is_none")]
201 /// [https://www.algolia.com/doc/api-reference/api-parameters/sortFacetValuesBy/](https://www.algolia.com/doc/api-reference/api-parameters/sortFacetValuesBy/)
202 sort_facet_values_by: Option<crate::settings::SortFacetValuesBy>,
203
204 // highlighting-snippeting
205 #[builder(setter(into))]
206 #[serde(skip_serializing_if = "Option::is_none")]
207 /// [https://www.algolia.com/doc/api-reference/api-parameters/attributesToHighlight/](https://www.algolia.com/doc/api-reference/api-parameters/attributesToHighlight/)
208 attributes_to_highlight: Option<Vec<String>>,
209 #[builder(setter(into))]
210 #[serde(skip_serializing_if = "Option::is_none")]
211 /// [https://www.algolia.com/doc/api-reference/api-parameters/attributesToSnippet/](https://www.algolia.com/doc/api-reference/api-parameters/attributesToSnippet/)
212 attributes_to_snippet: Option<Vec<String>>,
213 #[builder(setter(into))]
214 #[serde(skip_serializing_if = "Option::is_none")]
215 /// [https://www.algolia.com/doc/api-reference/api-parameters/highlightPreTag/](https://www.algolia.com/doc/api-reference/api-parameters/highlightPreTag/)
216 highlight_pre_tag: Option<String>,
217 #[builder(setter(into))]
218 #[serde(skip_serializing_if = "Option::is_none")]
219 /// [https://www.algolia.com/doc/api-reference/api-parameters/highlightPostTag/](https://www.algolia.com/doc/api-reference/api-parameters/highlightPostTag/)
220 highlight_post_tag: Option<String>,
221 #[builder(setter(into))]
222 #[serde(skip_serializing_if = "Option::is_none")]
223 /// [https://www.algolia.com/doc/api-reference/api-parameters/snippetEllipsisText/](https://www.algolia.com/doc/api-reference/api-parameters/snippetEllipsisText/)
224 snippet_ellipsis_text: Option<String>,
225 #[builder(setter(into))]
226 #[serde(skip_serializing_if = "Option::is_none")]
227 /// [https://www.algolia.com/doc/api-reference/api-parameters/restrictHighlightAndSnippetArrays/](https://www.algolia.com/doc/api-reference/api-parameters/restrictHighlightAndSnippetArrays/)
228 restrict_highlight_and_snippet_arrays: Option<bool>,
229
230 // pagination
231 #[builder(setter(into))]
232 #[serde(skip_serializing_if = "Option::is_none")]
233 /// [https://www.algolia.com/doc/api-reference/api-parameters/page/](https://www.algolia.com/doc/api-reference/api-parameters/page/)
234 page: Option<u64>,
235 #[builder(setter(into))]
236 #[serde(skip_serializing_if = "Option::is_none")]
237 /// [https://www.algolia.com/doc/api-reference/api-parameters/hitsPerPage/](https://www.algolia.com/doc/api-reference/api-parameters/hitsPerPage/)
238 hits_per_page: Option<u64>,
239 #[builder(setter(into))]
240 #[serde(skip_serializing_if = "Option::is_none")]
241 /// [https://www.algolia.com/doc/api-reference/api-parameters/offset/](https://www.algolia.com/doc/api-reference/api-parameters/offset/)
242 offset: Option<u64>,
243 #[builder(setter(into))]
244 #[serde(skip_serializing_if = "Option::is_none")]
245 /// [https://www.algolia.com/doc/api-reference/api-parameters/length/](https://www.algolia.com/doc/api-reference/api-parameters/length/)
246 length: Option<u64>,
247
248 // typos
249 #[builder(setter(into))]
250 #[serde(skip_serializing_if = "Option::is_none")]
251 #[serde(rename = "minWordSizefor1Typo")]
252 /// [https://www.algolia.com/doc/api-reference/api-parameters/minWordSizefor1Typo/](https://www.algolia.com/doc/api-reference/api-parameters/minWordSizefor1Typo/)
253 min_word_sizefor_1_typo: Option<u64>,
254 #[builder(setter(into))]
255 #[serde(skip_serializing_if = "Option::is_none")]
256 #[serde(rename = "minWordSizefor2Typo")]
257 /// [https://www.algolia.com/doc/api-reference/api-parameters/minWordSizefor2Typos/](https://www.algolia.com/doc/api-reference/api-parameters/minWordSizefor2Typos/)
258 min_word_sizefor_2_typos: Option<u64>,
259 #[builder(setter(into))]
260 #[serde(skip_serializing_if = "Option::is_none")]
261 /// [https://www.algolia.com/doc/api-reference/api-parameters/typoTolerance/](https://www.algolia.com/doc/api-reference/api-parameters/typoTolerance/)
262 typo_tolerance: Option<crate::settings::TypoTolerance>,
263 #[builder(setter(into))]
264 #[serde(skip_serializing_if = "Option::is_none")]
265 /// [https://www.algolia.com/doc/api-reference/api-parameters/allowTyposOnNumericTokens/](https://www.algolia.com/doc/api-reference/api-parameters/allowTyposOnNumericTokens/)
266 allow_typos_on_numeric_tokens: Option<bool>,
267 #[builder(setter(into))]
268 #[serde(skip_serializing_if = "Option::is_none")]
269 /// [https://www.algolia.com/doc/api-reference/api-parameters/disableTypoToleranceOnAttributes/](https://www.algolia.com/doc/api-reference/api-parameters/disableTypoToleranceOnAttributes/)
270 disable_typo_tolerance_on_attributes: Option<Vec<String>>,
271
272 // geo-search
273 #[builder(setter(into))]
274 #[serde(skip_serializing_if = "Option::is_none")]
275 /// [https://www.algolia.com/doc/api-reference/api-parameters/aroundLatLng/](https://www.algolia.com/doc/api-reference/api-parameters/aroundLatLng/)
276 around_lat_lng: Option<String>,
277 #[builder(setter(into))]
278 #[serde(skip_serializing_if = "Option::is_none")]
279 #[serde(rename = "aroundLatLngViaIP")]
280 /// [https://www.algolia.com/doc/api-reference/api-parameters/aroundLatLngViaIP/](https://www.algolia.com/doc/api-reference/api-parameters/aroundLatLngViaIP/)
281 around_lat_lng_via_ip: Option<bool>,
282 #[builder(setter(into))]
283 #[serde(skip_serializing_if = "Option::is_none")]
284 /// [https://www.algolia.com/doc/api-reference/api-parameters/aroundRadius/](https://www.algolia.com/doc/api-reference/api-parameters/aroundRadius/)
285 around_radius: Option<AroundRadius>,
286 #[builder(setter(into))]
287 #[serde(skip_serializing_if = "Option::is_none")]
288 /// [https://www.algolia.com/doc/api-reference/api-parameters/aroundPrecision/](https://www.algolia.com/doc/api-reference/api-parameters/aroundPrecision/)
289 around_precision: Option<u64>,
290 #[builder(setter(into))]
291 #[serde(skip_serializing_if = "Option::is_none")]
292 /// [https://www.algolia.com/doc/api-reference/api-parameters/minimumAroundRadius/](https://www.algolia.com/doc/api-reference/api-parameters/minimumAroundRadius/)
293 minimum_around_radius: Option<u64>,
294 #[builder(setter(into))]
295 #[serde(skip_serializing_if = "Option::is_none")]
296 /// [https://www.algolia.com/doc/api-reference/api-parameters/insideBoundingBox/](https://www.algolia.com/doc/api-reference/api-parameters/insideBoundingBox/)
297 inside_bounding_box: Option<Vec<f64>>,
298 #[builder(setter(into))]
299 #[serde(skip_serializing_if = "Option::is_none")]
300 /// [https://www.algolia.com/doc/api-reference/api-parameters/insidePolygon/](https://www.algolia.com/doc/api-reference/api-parameters/insidePolygon/)
301 inside_polygon: Option<Vec<f64>>,
302
303 // languages
304 #[builder(setter(into))]
305 #[serde(skip_serializing_if = "Option::is_none")]
306 /// [https://www.algolia.com/doc/api-reference/api-parameters/ignorePlurals/](https://www.algolia.com/doc/api-reference/api-parameters/ignorePlurals/)
307 ignore_plurals: Option<crate::settings::IgnorePlurals>,
308 #[builder(setter(into))]
309 #[serde(skip_serializing_if = "Option::is_none")]
310 /// [https://www.algolia.com/doc/api-reference/api-parameters/removeStopWords/](https://www.algolia.com/doc/api-reference/api-parameters/removeStopWords/)
311 remove_stop_words: Option<crate::settings::IgnorePlurals>,
312 #[builder(setter(into))]
313 #[serde(skip_serializing_if = "Option::is_none")]
314 /// [https://www.algolia.com/doc/api-reference/api-parameters/queryLanguages/](https://www.algolia.com/doc/api-reference/api-parameters/queryLanguages/)
315 query_languages: Option<Vec<String>>,
316
317 // query-strategy
318 #[builder(setter(into))]
319 #[serde(skip_serializing_if = "Option::is_none")]
320 /// [https://www.algolia.com/doc/api-reference/api-parameters/queryType/](https://www.algolia.com/doc/api-reference/api-parameters/queryType/)
321 query_type: Option<crate::settings::QueryType>,
322 #[builder(setter(into))]
323 #[serde(skip_serializing_if = "Option::is_none")]
324 /// [https://www.algolia.com/doc/api-reference/api-parameters/removeWordsIfNoResults/](https://www.algolia.com/doc/api-reference/api-parameters/removeWordsIfNoResults/)
325 remove_words_if_no_results: Option<crate::settings::RemoveWordsIfNoResults>,
326 #[builder(setter(into))]
327 #[serde(skip_serializing_if = "Option::is_none")]
328 /// [https://www.algolia.com/doc/api-reference/api-parameters/advancedSyntax/](https://www.algolia.com/doc/api-reference/api-parameters/advancedSyntax/)
329 advanced_syntax: Option<bool>,
330 #[builder(setter(into))]
331 #[serde(skip_serializing_if = "Option::is_none")]
332 /// [https://www.algolia.com/doc/api-reference/api-parameters/optionalWords/](https://www.algolia.com/doc/api-reference/api-parameters/optionalWords/)
333 optional_words: Option<Vec<String>>,
334 #[builder(setter(into))]
335 #[serde(skip_serializing_if = "Option::is_none")]
336 /// [https://www.algolia.com/doc/api-reference/api-parameters/disableExactOnAttributes/](https://www.algolia.com/doc/api-reference/api-parameters/disableExactOnAttributes/)
337 disable_exact_on_attributes: Option<Vec<String>>,
338 #[builder(setter(into))]
339 #[serde(skip_serializing_if = "Option::is_none")]
340 /// [https://www.algolia.com/doc/api-reference/api-parameters/exactOnSingleWordQuery/](https://www.algolia.com/doc/api-reference/api-parameters/exactOnSingleWordQuery/)
341 exact_on_single_word_query: Option<crate::settings::ExactOnSingleWordQuery>,
342 #[builder(setter(into))]
343 #[serde(skip_serializing_if = "Option::is_none")]
344 /// [https://www.algolia.com/doc/api-reference/api-parameters/alternativesAsExact/](https://www.algolia.com/doc/api-reference/api-parameters/alternativesAsExact/)
345 alternatives_as_exact: Option<crate::settings::AlternativesAsExact>,
346
347 // query-rules
348 #[builder(setter(into))]
349 #[serde(skip_serializing_if = "Option::is_none")]
350 /// [https://www.algolia.com/doc/api-reference/api-parameters/enableRules/](https://www.algolia.com/doc/api-reference/api-parameters/enableRules/)
351 enable_rules: Option<bool>,
352 #[builder(setter(into))]
353 #[serde(skip_serializing_if = "Option::is_none")]
354 /// [https://www.algolia.com/doc/api-reference/api-parameters/ruleContexts/](https://www.algolia.com/doc/api-reference/api-parameters/ruleContexts/)
355 rule_contexts: Option<Vec<String>>,
356
357 // personalization
358 #[builder(setter(into))]
359 #[serde(skip_serializing_if = "Option::is_none")]
360 /// [https://www.algolia.com/doc/api-reference/api-parameters/enablePersonalization/](https://www.algolia.com/doc/api-reference/api-parameters/enablePersonalization/)
361 enable_personalization: Option<bool>,
362 #[builder(setter(into))]
363 #[serde(skip_serializing_if = "Option::is_none")]
364 /// [https://www.algolia.com/doc/api-reference/api-parameters/userToken/](https://www.algolia.com/doc/api-reference/api-parameters/userToken/)
365 user_token: Option<String>,
366
367 // advanced
368 #[builder(setter(into))]
369 #[serde(skip_serializing_if = "Option::is_none")]
370 /// [https://www.algolia.com/doc/api-reference/api-parameters/distinct/](https://www.algolia.com/doc/api-reference/api-parameters/distinct/)
371 distinct: Option<crate::settings::Distinct>,
372 #[builder(setter(into))]
373 #[serde(skip_serializing_if = "Option::is_none")]
374 /// [https://www.algolia.com/doc/api-reference/api-parameters/getRankingInfo/](https://www.algolia.com/doc/api-reference/api-parameters/getRankingInfo/)
375 get_ranking_info: Option<bool>,
376 #[builder(setter(into))]
377 #[serde(skip_serializing_if = "Option::is_none")]
378 /// [https://www.algolia.com/doc/api-reference/api-parameters/clickAnalytics/](https://www.algolia.com/doc/api-reference/api-parameters/clickAnalytics/)
379 click_analytics: Option<bool>,
380 #[builder(setter(into))]
381 #[serde(skip_serializing_if = "Option::is_none")]
382 /// [https://www.algolia.com/doc/api-reference/api-parameters/analytics/](https://www.algolia.com/doc/api-reference/api-parameters/analytics/)
383 analytics: Option<bool>,
384 #[builder(setter(into))]
385 #[serde(skip_serializing_if = "Option::is_none")]
386 /// [https://www.algolia.com/doc/api-reference/api-parameters/analyticsTags/](https://www.algolia.com/doc/api-reference/api-parameters/analyticsTags/)
387 analytics_tags: Option<Vec<String>>,
388 #[builder(setter(into))]
389 #[serde(skip_serializing_if = "Option::is_none")]
390 /// [https://www.algolia.com/doc/api-reference/api-parameters/synonyms/](https://www.algolia.com/doc/api-reference/api-parameters/synonyms/)
391 synonyms: Option<bool>,
392 #[builder(setter(into))]
393 #[serde(skip_serializing_if = "Option::is_none")]
394 /// [https://www.algolia.com/doc/api-reference/api-parameters/replaceSynonymsInHighlight/](https://www.algolia.com/doc/api-reference/api-parameters/replaceSynonymsInHighlight/)
395 replace_synonyms_in_highlight: Option<bool>,
396 #[builder(setter(into))]
397 #[serde(skip_serializing_if = "Option::is_none")]
398 /// [https://www.algolia.com/doc/api-reference/api-parameters/minProximity/](https://www.algolia.com/doc/api-reference/api-parameters/minProximity/)
399 min_proximity: Option<crate::settings::MinProximity>,
400 #[builder(setter(into))]
401 #[serde(skip_serializing_if = "Option::is_none")]
402 /// [https://www.algolia.com/doc/api-reference/api-parameters/responseFields/](https://www.algolia.com/doc/api-reference/api-parameters/responseFields/)
403 response_fields: Option<Vec<String>>,
404 #[builder(setter(into))]
405 #[serde(skip_serializing_if = "Option::is_none")]
406 /// [https://www.algolia.com/doc/api-reference/api-parameters/maxFacetHits/](https://www.algolia.com/doc/api-reference/api-parameters/maxFacetHits/)
407 max_facet_hits: Option<u64>,
408 #[builder(setter(into))]
409 #[serde(skip_serializing_if = "Option::is_none")]
410 /// [https://www.algolia.com/doc/api-reference/api-parameters/percentileComputation/](https://www.algolia.com/doc/api-reference/api-parameters/percentileComputation/)
411 percentile_computation: Option<u64>,
412}
413
414#[cfg(test)]
415mod query_builder {
416 use super::*;
417 use serde_json;
418
419 #[test]
420 fn test_serialize() {
421 let query = SearchQueryBuilder::default()
422 .page(1)
423 .hits_per_page(6)
424 .build()
425 .unwrap();
426
427 assert_eq!(
428 serde_json::to_string(&query).unwrap(),
429 r#"{"query":null,"page":1,"hitsPerPage":6}"#
430 );
431 }
432}
433
434#[derive(Serialize)]
435struct SearchQueryBody {
436 params: String,
437}
438
439impl From<&str> for SearchQuery {
440 fn from(item: &str) -> Self {
441 SearchQuery {
442 query: Some(item.to_string()),
443 ..Default::default()
444 }
445 }
446}
447
448#[derive(Debug, Deserialize)]
449#[serde(rename_all = "camelCase")]
450pub struct AddObjectResult {
451 pub created_at: DateTime<Utc>,
452 #[serde(rename = "taskID")]
453 pub task_id: u64,
454 #[serde(rename = "objectID")]
455 pub object_id: String,
456}
457
458#[derive(Debug, Deserialize)]
459#[serde(rename_all = "camelCase")]
460pub struct UpdateOperationResult {
461 pub updated_at: DateTime<Utc>,
462 #[serde(rename = "taskID")]
463 pub task_id: u64,
464}
465
466#[derive(Debug, Deserialize)]
467#[serde(rename_all = "camelCase")]
468pub struct DeleteObjectResult {
469 pub deleted_at: DateTime<Utc>,
470 #[serde(rename = "taskID")]
471 pub task_id: u64,
472}
473
474#[derive(Serialize)]
475struct BatchedOperationItem<T> {
476 action: String,
477 body: T,
478}
479
480#[derive(Serialize)]
481struct BatchedOperation<T> {
482 requests: Vec<BatchedOperationItem<T>>,
483}
484
485#[derive(Debug, Deserialize)]
486pub struct BatchedOperatioResult {
487 #[serde(rename = "taskID")]
488 pub task_id: u64,
489 #[serde(rename = "objectIDs")]
490 pub object_ids: Vec<String>,
491}
492
493#[derive(Debug)]
494/// Index
495pub struct Index<T> {
496 /// Application id
497 pub application_id: String,
498 /// Index name
499 pub index_name: String,
500 pub(crate) api_key: String,
501 pub(crate) base_url: String,
502 pub(crate) index_type: PhantomData<T>,
503}
504
505impl<T: DeserializeOwned + Serialize> Index<T> {
506 /// Search the index.
507 /// This method accepts a [&str](https://doc.rust-lang.org/std/str/index.html):
508 /// ```no_run
509 /// # #[macro_use] extern crate serde_derive;
510 /// # use tokio;
511 /// # use algoliasearch::{Client, SearchQueryBuilder, Error};
512 /// # #[derive(Debug, Serialize, Deserialize)]
513 /// # struct User;
514 /// # #[tokio::main]
515 /// # async fn main() -> Result<(), Box<Error>> {
516 /// # let index = Client::default().init_index::<User>("users");
517 /// let res = index.search("Bernardo").await?;
518 /// dbg!(res.hits); // [User { name: "Bernardo", age: 32} ]
519 /// # Ok(())
520 /// # }
521 /// ```
522 /// Or a [SearchQuery](struct.SearchQuery.html) object, that can be build with the [SearchQueryBuilder](struct.SearchQueryBuilder.html):
523 /// ```no_run
524 /// # #[macro_use] extern crate serde_derive;
525 /// # use tokio;
526 /// # use algoliasearch::{settings::TypoTolerance, Client, SearchQueryBuilder, Error};
527 /// # #[derive(Serialize, Deserialize)]
528 /// # struct User;
529 /// # #[tokio::main]
530 /// # async fn main() -> Result<(), Box<Error>> {
531 /// # let index = Client::default().init_index::<User>("users");
532 /// let query = SearchQueryBuilder::default()
533 /// .query("Bernardo".to_string())
534 /// .analytics(false)
535 /// .typo_tolerance(TypoTolerance::Strict)
536 /// .page(1)
537 /// .build()
538 /// .unwrap();
539 /// let res = index.search(query).await?;
540 /// # Ok(())
541 /// # }
542 /// ```
543 pub async fn search(&self, query: impl Into<SearchQuery>) -> Result<SearchResult<T>, Error> {
544 let query = query.into();
545 let uri = format!("{}/indexes/{}/query", self.base_url, self.index_name);
546 let params = serde_urlencoded::to_string(query).expect("failed to encode params");
547 let params = &SearchQueryBody { params };
548 Client::new()
549 .post(&uri)
550 .headers(self.get_headers())
551 .json(¶ms)
552 .send()
553 .await?
554 .json::<SearchResult<T>>()
555 .await
556 .map_err(|e| e.into())
557 }
558 /// Get an object from the index.
559 /// ```no_run
560 /// # #[macro_use] extern crate serde_derive;
561 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
562 /// # #[derive(Serialize, Deserialize, Debug)]
563 /// # struct User;
564 /// # #[tokio::main]
565 /// # async fn main() -> Result<(), Box<Error>> {
566 /// # let index = Client::default().init_index::<User>("users");
567 /// let object_1 = index.get_object(&"THE_ID", None).await?;
568 /// dbg!(object_1); // User { name: "Bernardo", age: 32 };
569 /// # Ok(())
570 /// # }
571 /// ```
572 pub async fn get_object(
573 &self,
574 object_id: &str,
575 attributes_to_retrieve: Option<&[&str]>,
576 ) -> Result<T, Error> {
577 let uri = format!(
578 "{}/indexes/{}/{}",
579 self.base_url, self.index_name, object_id
580 );
581 Client::new()
582 .get(&uri)
583 .headers(self.get_headers())
584 .query(&[(
585 "attributes_to_retrieve",
586 attributes_to_retrieve.map(|el| el.join(",")),
587 )])
588 .send()
589 .await?
590 .json()
591 .await
592 .map_err(|e| e.into())
593 }
594 /// Add an object to the index.
595 /// ```no_run
596 /// # #[macro_use] extern crate serde_derive;
597 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
598 /// # #[derive(Serialize, Deserialize)]
599 /// # struct User { name: String, age: u32, }
600 /// # #[tokio::main]
601 /// # async fn main() -> Result<(), Box<Error>> {
602 /// # let index = Client::default().init_index::<User>("users");
603 /// let object_1 = User { name: "Bernardo".into(), age: 32 };
604 /// index.add_object(&object_1).await?;
605 /// # Ok(())
606 /// # }
607 /// ```
608 pub async fn add_object(&self, object: &T) -> Result<AddObjectResult, Error> {
609 let uri = format!("{}/indexes/{}", self.base_url, self.index_name);
610 Client::new()
611 .post(&uri)
612 .headers(self.get_headers())
613 .json(&object)
614 .send()
615 .await?
616 .json()
617 .await
618 .map_err(|e| e.into())
619 }
620 /// Add several objects to the index.
621 /// ```no_run
622 /// # #[macro_use] extern crate serde_derive;
623 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
624 /// # #[derive(Serialize, Deserialize)]
625 /// # struct User { name: String, age: u32, }
626 /// # #[tokio::main]
627 /// # async fn main() -> Result<(), Box<Error>> {
628 /// # let index = Client::default().init_index::<User>("users");
629 /// let object_1 = User { name: "Bernardo".into(), age: 32 };
630 /// let object_2 = User { name: "Esmeralda".into(), age: 45 };
631 /// index.add_objects(&[&object_1, &object_2]).await?;
632 /// # Ok(())
633 /// # }
634 /// ```
635 pub async fn add_objects(&self, objects: &[&T]) -> Result<BatchedOperatioResult, Error> {
636 let uri = format!("{}/indexes/{}/batch", self.base_url, self.index_name);
637 let requests = objects.iter().fold(vec![], |mut acc, x| {
638 acc.push(BatchedOperationItem {
639 action: "addObject".to_string(),
640 body: x,
641 });
642 acc
643 });
644 let requests = BatchedOperation { requests };
645 Client::new()
646 .post(&uri)
647 .headers(self.get_headers())
648 .json(&requests)
649 .send()
650 .await?
651 .json()
652 .await
653 .map_err(|e| e.into())
654 }
655 /// Add or replace an object with a given object ID.
656 /// If the object does not exist, it will be created. If it already exists, it will be replaced.
657 /// ```no_run
658 /// # #[macro_use] extern crate serde_derive;
659 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
660 /// # #[derive(Serialize, Deserialize)]
661 /// # struct User { object_id: String }
662 /// # #[tokio::main]
663 /// # async fn main() -> Result<(), Box<Error>> {
664 /// # let index = Client::default().init_index::<User>("users");
665 /// # let object_1 = User { object_id: String::from("123") };
666 /// index.update_object(&object_1, &object_1.object_id).await?;
667 /// # Ok(())
668 /// # }
669 /// ```
670 pub async fn update_object(
671 &self,
672 object: &T,
673 object_id: &str,
674 ) -> Result<UpdateOperationResult, Error> {
675 let uri = format!(
676 "{}/indexes/{}/{}",
677 self.base_url, self.index_name, object_id
678 );
679 Client::new()
680 .put(&uri)
681 .headers(self.get_headers())
682 .json(object)
683 .send()
684 .await?
685 .json()
686 .await
687 .map_err(|e| e.into())
688 }
689 /// Add or replace several objects with a given object ID.
690 /// If the object does not exist, it will be created. If it already exists, it will be replaced..
691 /// ```no_run
692 /// # #[macro_use] extern crate serde_derive;
693 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
694 /// # #[derive(Serialize, Deserialize)]
695 /// # struct User;
696 /// # #[tokio::main]
697 /// # async fn main() -> Result<(), Box<Error>> {
698 /// # let index = Client::default().init_index::<User>("users");
699 /// # let object_1 = User;
700 /// # let object_2 = User;
701 /// index.update_objects(&[&object_1, &object_2]).await?;
702 /// # Ok(())
703 /// # }
704 /// ```
705 pub async fn update_objects(&self, objects: &[&T]) -> Result<BatchedOperatioResult, Error> {
706 let uri = format!("{}/indexes/{}/batch", self.base_url, self.index_name);
707 let requests = objects.iter().fold(vec![], |mut acc, x| {
708 acc.push(BatchedOperationItem {
709 action: "updateObject".to_string(),
710 body: x,
711 });
712 acc
713 });
714 let requests = BatchedOperation { requests };
715 Client::new()
716 .post(&uri)
717 .headers(self.get_headers())
718 .json(&requests)
719 .send()
720 .await?
721 .json()
722 .await
723 .map_err(|e| e.into())
724 }
725 /// Delete an object from the index.
726 /// ```no_run
727 /// # #[macro_use] extern crate serde_derive;
728 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
729 /// # #[derive(Serialize, Deserialize)]
730 /// # struct User { object_id: String, }
731 /// # #[tokio::main]
732 /// # async fn main() -> Result<(), Box<Error>> {
733 /// # let index = Client::default().init_index::<User>("users");
734 /// # let object_1 = User { object_id: "test".into(), };
735 /// index.delete_object(&object_1.object_id).await?;
736 /// # Ok(())
737 /// # }
738 /// ```
739 pub async fn delete_object(&self, object_id: &str) -> Result<DeleteObjectResult, Error> {
740 let uri = format!(
741 "{}/indexes/{}/{}",
742 self.base_url, self.index_name, object_id
743 );
744 Client::new()
745 .delete(&uri)
746 .headers(self.get_headers())
747 .send()
748 .await?
749 .json()
750 .await
751 .map_err(|e| e.into())
752 }
753 /// Clear all objects from an index.
754 /// ```no_run
755 /// # #[macro_use] extern crate serde_derive;
756 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
757 /// # #[derive(Serialize, Deserialize)]
758 /// # struct User;
759 /// # #[tokio::main]
760 /// # async fn main() -> Result<(), Box<Error>> {
761 /// # let index = Client::default().init_index::<User>("users");
762 /// index.clear_objects().await?;
763 /// # Ok(())
764 /// # }
765 /// ```
766 pub async fn clear_objects(&self) -> Result<UpdateOperationResult, Error> {
767 let uri = format!("{}/indexes/{}/clear", self.base_url, self.index_name);
768 Client::new()
769 .post(&uri)
770 .headers(self.get_headers())
771 .send()
772 .await?
773 .json()
774 .await
775 .map_err(|e| e.into())
776 }
777 /// Get the index's settings.
778 /// ```no_run
779 /// # #[macro_use] extern crate serde_derive;
780 /// # use algoliasearch::{Error, Client, SearchQueryBuilder};
781 /// # #[derive(Serialize, Deserialize)]
782 /// # struct User;
783 /// # #[tokio::main]
784 /// # async fn main() -> Result<(), Box<Error>> {
785 /// # let index = Client::default().init_index::<User>("users");
786 /// let settings = index.get_settings().await?;
787 /// dbg!(settings.hits_per_page); // 20
788 /// # Ok(())
789 /// # }
790 /// ```
791 pub async fn get_settings(&self) -> Result<settings::IndexSettings, Error> {
792 let uri = format!("{}/indexes/{}/settings", self.base_url, self.index_name);
793 Client::new()
794 .get(&uri)
795 .headers(self.get_headers())
796 .send()
797 .await?
798 .json()
799 .await
800 .map_err(|e| e.into())
801 }
802 /// Set the index's settings.
803 /// ```no_run
804 /// # #[macro_use] extern crate serde_derive;
805 /// # use futures::Future;
806 /// # use algoliasearch::{
807 /// # Client, SearchQueryBuilder,
808 /// # Error,
809 /// # settings::{IndexSettingsBuilder, SortFacetValuesBy}
810 /// # };
811 /// # #[derive(Serialize, Deserialize)]
812 /// # struct User;
813 /// # #[tokio::main]
814 /// # async fn main() -> Result<(), Box<Error>> {
815 /// # let index = Client::default().init_index::<User>("users");
816 /// let settings = IndexSettingsBuilder::default()
817 /// .hits_per_page(30)
818 /// .sort_facet_values_by(SortFacetValuesBy::Count)
819 /// .build()
820 /// .unwrap();
821 /// index.set_settings(settings, None).await?;
822 /// # Ok(())
823 /// # }
824 /// ```
825 pub async fn set_settings(
826 &self,
827 settings: settings::IndexSettings,
828 forward_to_replicas: Option<bool>,
829 ) -> Result<UpdateOperationResult, Error> {
830 let forward_to_replicas = forward_to_replicas.unwrap_or(false);
831 let uri = format!("{}/indexes/{}/settings", self.base_url, self.index_name);
832 Client::new()
833 .put(&uri)
834 .headers(self.get_headers())
835 .json(&settings)
836 .query(&[("forwardToReplicas", forward_to_replicas)])
837 .send()
838 .await?
839 .json()
840 .await
841 .map_err(|e| e.into())
842 }
843 // Build authentication headers.
844 fn get_headers(&self) -> HeaderMap {
845 let mut headers = HeaderMap::new();
846 headers.insert(
847 crate::APPLICATION_ID_HEADER,
848 self.application_id.parse().unwrap(),
849 );
850 headers.insert(crate::API_KEY_HEADER, self.api_key.parse().unwrap());
851 headers
852 }
853
854 /// Get a task's status.
855 /// ```no_run
856 /// # #[macro_use] extern crate serde_derive;
857 /// # use futures::Future;
858 /// # use algoliasearch::{
859 /// # Client, SearchQueryBuilder,
860 /// # Error,
861 /// # settings::{IndexSettingsBuilder, SortFacetValuesBy}
862 /// # };
863 /// # #[derive(Serialize, Deserialize)]
864 /// # struct User;
865 /// # #[tokio::main]
866 /// # async fn main() -> Result<(), Box<Error>> {
867 /// # let index = Client::default().init_index::<User>("users");
868 /// let task = index.get_task_status(123);
869 /// # Ok(())
870 /// # }
871 /// ```
872 pub async fn get_task_status(&self, task_id: u64) -> Result<task::TaskStatus, Error> {
873 let uri = format!(
874 "{}/indexes/{}/task/{}",
875 self.base_url, self.index_name, task_id
876 );
877 Client::new()
878 .get(&uri)
879 .headers(self.get_headers())
880 .send()
881 .await?
882 .json()
883 .await
884 .map_err(|e| e.into())
885 }
886}