1use crate::{
2 client::{PreparedCommand, prepare_command},
3 resp::{Response, cmd, serialize_flag},
4};
5use serde::{
6 Deserialize, Deserializer, Serialize,
7 de::{
8 self, Unexpected, Visitor,
9 value::{BytesDeserializer, SeqAccessDeserializer},
10 },
11};
12use std::{fmt, marker::PhantomData};
13
14pub trait GeoCommands<'a>: Sized {
19 #[must_use]
28 fn geoadd(
29 self,
30 key: impl Serialize,
31 condition: impl Into<Option<GeoAddCondition>>,
32 change: bool,
33 items: impl Serialize,
34 ) -> PreparedCommand<'a, Self, usize> {
35 prepare_command(
36 self,
37 cmd("GEOADD")
38 .key(key)
39 .arg(condition.into())
40 .arg_if(change, "CH")
41 .arg(items),
42 )
43 }
44
45 #[must_use]
54 fn geodist(
55 self,
56 key: impl Serialize,
57 member1: impl Serialize,
58 member2: impl Serialize,
59 unit: GeoUnit,
60 ) -> PreparedCommand<'a, Self, Option<f64>> {
61 prepare_command(
62 self,
63 cmd("GEODIST").key(key).arg(member1).arg(member2).arg(unit),
64 )
65 }
66
67 #[must_use]
76 fn geohash<R: Response>(
77 self,
78 key: impl Serialize,
79 members: impl Serialize,
80 ) -> PreparedCommand<'a, Self, R> {
81 prepare_command(self, cmd("GEOHASH").key(key).arg(members))
82 }
83
84 #[must_use]
95 fn geopos(
96 self,
97 key: impl Serialize,
98 members: impl Serialize,
99 ) -> PreparedCommand<'a, Self, Vec<Option<(f64, f64)>>> {
100 prepare_command(self, cmd("GEOPOS").key(key).arg(members))
101 }
102
103 #[must_use]
113 fn geosearch<'b, R: Response>(
114 self,
115 key: impl Serialize,
116 from: GeoSearchFrom<'b>,
117 by: GeoSearchBy,
118 options: GeoSearchOptions,
119 ) -> PreparedCommand<'a, Self, R> {
120 prepare_command(
121 self,
122 cmd("GEOSEARCH").key(key).arg(from).arg(by).arg(options),
123 )
124 }
125
126 #[must_use]
134 fn geosearchstore<'b>(
135 self,
136 destination: impl Serialize,
137 source: impl Serialize,
138 from: GeoSearchFrom<'b>,
139 by: GeoSearchBy,
140 options: GeoSearchStoreOptions,
141 ) -> PreparedCommand<'a, Self, u32> {
142 prepare_command(
143 self,
144 cmd("GEOSEARCHSTORE")
145 .key(destination)
146 .key(source)
147 .arg(from)
148 .arg(by)
149 .arg(options),
150 )
151 }
152}
153
154#[derive(Serialize)]
156#[serde(rename_all = "UPPERCASE")]
157pub enum GeoAddCondition {
158 NX,
160 XX,
162}
163
164#[derive(Serialize)]
166pub enum GeoUnit {
167 #[serde(rename = "M")]
168 Meters,
169 #[serde(rename = "KM")]
170 Kilometers,
171 #[serde(rename = "MI")]
172 Miles,
173 #[serde(rename = "FT")]
174 Feet,
175}
176
177#[derive(Serialize)]
179#[serde(rename_all(serialize = "UPPERCASE"))]
180pub enum GeoSearchFrom<'a> {
181 FromMember(&'a str),
183 FromLonLat(f64, f64),
185}
186
187impl<'a> GeoSearchFrom<'a> {
188 pub fn from_member(member: &'a str) -> Self {
189 Self::FromMember(member)
190 }
191
192 pub fn from_longitude_latitude(longitude: f64, latitude: f64) -> Self {
193 Self::FromLonLat(longitude, latitude)
194 }
195}
196
197#[derive(Serialize)]
199#[serde(rename_all(serialize = "UPPERCASE"))]
200pub enum GeoSearchBy {
201 ByRadius(f64, GeoUnit),
203 ByBox(f64, f64, GeoUnit),
205}
206
207impl GeoSearchBy {
208 pub fn by_radius(radius: f64, unit: GeoUnit) -> Self {
209 Self::ByRadius(radius, unit)
210 }
211
212 pub fn by_box(width: f64, height: f64, unit: GeoUnit) -> Self {
213 Self::ByBox(width, height, unit)
214 }
215}
216
217#[derive(Serialize)]
220#[serde(rename_all = "UPPERCASE")]
221pub enum GeoSearchOrder {
222 Asc,
224 Desc,
226}
227
228#[derive(Default, Serialize)]
230#[serde(rename_all(serialize = "UPPERCASE"))]
231pub struct GeoSearchOptions {
232 #[serde(rename = "", skip_serializing_if = "Option::is_none")]
233 order: Option<GeoSearchOrder>,
234 #[serde(skip_serializing_if = "Option::is_none")]
235 count: Option<u32>,
236 #[serde(
237 skip_serializing_if = "std::ops::Not::not",
238 serialize_with = "serialize_flag"
239 )]
240 any: bool,
241 #[serde(
242 skip_serializing_if = "std::ops::Not::not",
243 serialize_with = "serialize_flag"
244 )]
245 withcoord: bool,
246 #[serde(
247 skip_serializing_if = "std::ops::Not::not",
248 serialize_with = "serialize_flag"
249 )]
250 withdist: bool,
251 #[serde(
252 skip_serializing_if = "std::ops::Not::not",
253 serialize_with = "serialize_flag"
254 )]
255 withhash: bool,
256}
257
258impl GeoSearchOptions {
259 #[must_use]
260 pub fn order(mut self, order: GeoSearchOrder) -> Self {
261 self.order = Some(order);
262 self
263 }
264
265 #[must_use]
266 pub fn count(mut self, count: u32, any: bool) -> Self {
267 self.count = Some(count);
268 self.any = any;
269 self
270 }
271
272 #[must_use]
273 pub fn with_coord(mut self) -> Self {
274 self.withcoord = true;
275 self
276 }
277
278 #[must_use]
279 pub fn with_dist(mut self) -> Self {
280 self.withdist = true;
281 self
282 }
283
284 #[must_use]
285 pub fn with_hash(mut self) -> Self {
286 self.withhash = true;
287 self
288 }
289}
290
291#[derive(Debug)]
293pub struct GeoSearchResult<R: Response> {
294 pub member: R,
296
297 pub distance: Option<f64>,
299
300 pub geo_hash: Option<i64>,
302
303 pub coordinates: Option<(f64, f64)>,
305}
306
307impl<'de, R: Response + Deserialize<'de>> Deserialize<'de> for GeoSearchResult<R> {
308 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
309 where
310 D: Deserializer<'de>,
311 {
312 pub enum GeoSearchResultField {
313 Distance(f64),
314 GeoHash(i64),
315 Coordinates((f64, f64)),
316 }
317
318 impl<'de> Deserialize<'de> for GeoSearchResultField {
319 #[inline]
320 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
321 where
322 D: Deserializer<'de>,
323 {
324 struct GeoSearchResultFieldVisitor;
325
326 impl<'de> Visitor<'de> for GeoSearchResultFieldVisitor {
327 type Value = GeoSearchResultField;
328
329 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
330 formatter.write_str("GeoSearchResultField")
331 }
332
333 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
334 where
335 E: de::Error,
336 {
337 let Ok(distance) = std::str::from_utf8(v) else {
338 return Err(de::Error::invalid_value(
339 Unexpected::Bytes(v),
340 &"A valid f64 encoded in an UTF8 string",
341 ));
342 };
343
344 let Ok(distance) = distance.parse::<f64>() else {
345 return Err(de::Error::invalid_value(
346 Unexpected::Bytes(v),
347 &"A valid f64 encoded in an UTF8 string",
348 ));
349 };
350
351 Ok(GeoSearchResultField::Distance(distance))
352 }
353
354 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
355 where
356 E: de::Error,
357 {
358 Ok(GeoSearchResultField::GeoHash(v))
359 }
360
361 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
362 where
363 A: de::SeqAccess<'de>,
364 {
365 let coordinates =
366 <(f64, f64)>::deserialize(SeqAccessDeserializer::new(seq))?;
367 Ok(GeoSearchResultField::Coordinates(coordinates))
368 }
369 }
370
371 deserializer.deserialize_any(GeoSearchResultFieldVisitor)
372 }
373 }
374
375 pub struct GeoSearchResultVisitor<R: Response> {
376 phantom: PhantomData<R>,
377 }
378
379 impl<'de, R: Response + Deserialize<'de>> Visitor<'de> for GeoSearchResultVisitor<R> {
380 type Value = GeoSearchResult<R>;
381
382 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
383 formatter.write_str("GeoSearchResult<M>")
384 }
385
386 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
387 where
388 A: de::SeqAccess<'de>,
389 {
390 let Some(member) = seq.next_element::<R>().map_err(de::Error::custom)? else {
391 return Err(de::Error::invalid_length(0, &"more elements in sequence"));
392 };
393
394 let mut distance: Option<f64> = None;
395 let mut geo_hash: Option<i64> = None;
396 let mut coordinates: Option<(f64, f64)> = None;
397
398 while let Some(field) = seq.next_element::<GeoSearchResultField>()? {
399 match field {
400 GeoSearchResultField::Distance(d) => distance = Some(d),
401 GeoSearchResultField::GeoHash(gh) => geo_hash = Some(gh),
402 GeoSearchResultField::Coordinates(c) => coordinates = Some(c),
403 }
404 }
405
406 Ok(GeoSearchResult {
407 member,
408 distance,
409 geo_hash,
410 coordinates,
411 })
412 }
413
414 fn visit_borrowed_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
415 where
416 E: de::Error,
417 {
418 let member = R::deserialize(BytesDeserializer::new(v))?;
419
420 Ok(GeoSearchResult {
421 member,
422 distance: None,
423 geo_hash: None,
424 coordinates: None,
425 })
426 }
427 }
428
429 deserializer.deserialize_any(GeoSearchResultVisitor::<R> {
430 phantom: PhantomData,
431 })
432 }
433}
434
435#[derive(Default, Serialize)]
437#[serde(rename_all(serialize = "UPPERCASE"))]
438pub struct GeoSearchStoreOptions {
439 #[serde(rename = "", skip_serializing_if = "Option::is_none")]
440 order: Option<GeoSearchOrder>,
441 #[serde(skip_serializing_if = "Option::is_none")]
442 count: Option<u32>,
443 #[serde(
444 skip_serializing_if = "std::ops::Not::not",
445 serialize_with = "serialize_flag"
446 )]
447 any: bool,
448 #[serde(
449 skip_serializing_if = "std::ops::Not::not",
450 serialize_with = "serialize_flag"
451 )]
452 storedist: bool,
453}
454
455impl GeoSearchStoreOptions {
456 #[must_use]
457 pub fn order(mut self, order: GeoSearchOrder) -> Self {
458 self.order = Some(order);
459 self
460 }
461
462 #[must_use]
463 pub fn count(mut self, count: u32, any: bool) -> Self {
464 self.count = Some(count);
465 self.any = any;
466 self
467 }
468
469 #[must_use]
470 pub fn store_dist(mut self, store_dist: bool) -> Self {
471 self.storedist = store_dist;
472 self
473 }
474}