1use crate::{
2 client::{prepare_command, PreparedCommand},
3 resp::{
4 cmd, CollectionResponse, CommandArgs, MultipleArgsCollection, PrimitiveResponse, SingleArg,
5 SingleArgCollection, ToArgs,
6 },
7};
8use serde::{
9 de::{
10 self,
11 value::{BytesDeserializer, SeqAccessDeserializer},
12 DeserializeOwned, Unexpected, Visitor,
13 },
14 Deserialize, Deserializer,
15};
16use std::{fmt, marker::PhantomData};
17
18pub trait GeoCommands<'a> {
23 #[must_use]
32 fn geoadd<K, M, I>(
33 self,
34 key: K,
35 condition: GeoAddCondition,
36 change: bool,
37 items: I,
38 ) -> PreparedCommand<'a, Self, usize>
39 where
40 Self: Sized,
41 K: SingleArg,
42 M: SingleArg,
43 I: MultipleArgsCollection<(f64, f64, M)>,
44 {
45 prepare_command(
46 self,
47 cmd("GEOADD")
48 .arg(key)
49 .arg(condition)
50 .arg_if(change, "CH")
51 .arg(items),
52 )
53 }
54
55 #[must_use]
64 fn geodist<K, M>(
65 self,
66 key: K,
67 member1: M,
68 member2: M,
69 unit: GeoUnit,
70 ) -> PreparedCommand<'a, Self, Option<f64>>
71 where
72 Self: Sized,
73 K: SingleArg,
74 M: SingleArg,
75 {
76 prepare_command(
77 self,
78 cmd("GEODIST").arg(key).arg(member1).arg(member2).arg(unit),
79 )
80 }
81
82 #[must_use]
91 fn geohash<K, M, C>(self, key: K, members: C) -> PreparedCommand<'a, Self, Vec<String>>
92 where
93 Self: Sized,
94 K: SingleArg,
95 M: SingleArg,
96 C: SingleArgCollection<M>,
97 {
98 prepare_command(self, cmd("GEOHASH").arg(key).arg(members))
99 }
100
101 #[must_use]
112 fn geopos<K, M, C>(
113 self,
114 key: K,
115 members: C,
116 ) -> PreparedCommand<'a, Self, Vec<Option<(f64, f64)>>>
117 where
118 Self: Sized,
119 K: SingleArg,
120 M: SingleArg,
121 C: SingleArgCollection<M>,
122 {
123 prepare_command(self, cmd("GEOPOS").arg(key).arg(members))
124 }
125
126 #[must_use]
136 fn geosearch<K, M1, M2, A>(
137 self,
138 key: K,
139 from: GeoSearchFrom<M1>,
140 by: GeoSearchBy,
141 options: GeoSearchOptions,
142 ) -> PreparedCommand<'a, Self, A>
143 where
144 Self: Sized,
145 K: SingleArg,
146 M1: SingleArg,
147 M2: PrimitiveResponse + DeserializeOwned,
148 A: CollectionResponse<GeoSearchResult<M2>> + DeserializeOwned,
149 {
150 prepare_command(
151 self,
152 cmd("GEOSEARCH").arg(key).arg(from).arg(by).arg(options),
153 )
154 }
155
156 #[must_use]
164 fn geosearchstore<D, S, M>(
165 self,
166 destination: D,
167 source: S,
168 from: GeoSearchFrom<M>,
169 by: GeoSearchBy,
170 options: GeoSearchStoreOptions,
171 ) -> PreparedCommand<'a, Self, usize>
172 where
173 Self: Sized,
174 D: SingleArg,
175 S: SingleArg,
176 M: SingleArg,
177 {
178 prepare_command(
179 self,
180 cmd("GEOSEARCHSTORE")
181 .arg(destination)
182 .arg(source)
183 .arg(from)
184 .arg(by)
185 .arg(options),
186 )
187 }
188}
189
190#[derive(Default)]
192pub enum GeoAddCondition {
193 #[default]
195 None,
196 NX,
198 XX,
200}
201
202impl ToArgs for GeoAddCondition {
203 fn write_args(&self, args: &mut CommandArgs) {
204 match self {
205 GeoAddCondition::None => {}
206 GeoAddCondition::NX => {
207 args.arg("NX");
208 }
209 GeoAddCondition::XX => {
210 args.arg("XX");
211 }
212 }
213 }
214}
215
216pub enum GeoUnit {
218 Meters,
219 Kilometers,
220 Miles,
221 Feet,
222}
223
224impl ToArgs for GeoUnit {
225 fn write_args(&self, args: &mut CommandArgs) {
226 args.arg(match self {
227 GeoUnit::Meters => "m",
228 GeoUnit::Kilometers => "km",
229 GeoUnit::Miles => "mi",
230 GeoUnit::Feet => "ft",
231 });
232 }
233}
234
235pub enum GeoSearchFrom<M>
237where
238 M: SingleArg,
239{
240 FromMember { member: M },
242 FromLonLat { longitude: f64, latitude: f64 },
244}
245
246impl<M> ToArgs for GeoSearchFrom<M>
247where
248 M: SingleArg,
249{
250 fn write_args(&self, args: &mut CommandArgs) {
251 match self {
252 GeoSearchFrom::FromMember { member } => args.arg("FROMMEMBER").arg_ref(member),
253 GeoSearchFrom::FromLonLat {
254 longitude,
255 latitude,
256 } => args.arg("FROMLONLAT").arg(*longitude).arg(*latitude),
257 };
258 }
259}
260
261pub enum GeoSearchBy {
263 ByRadius { radius: f64, unit: GeoUnit },
265 ByBox {
267 width: f64,
268 height: f64,
269 unit: GeoUnit,
270 },
271}
272
273impl ToArgs for GeoSearchBy {
274 fn write_args(&self, args: &mut CommandArgs) {
275 match self {
276 GeoSearchBy::ByRadius { radius, unit } => {
277 args.arg("BYRADIUS").arg_ref(radius).arg_ref(unit)
278 }
279 GeoSearchBy::ByBox {
280 width,
281 height,
282 unit,
283 } => args
284 .arg("BYBOX")
285 .arg_ref(width)
286 .arg_ref(height)
287 .arg_ref(unit),
288 };
289 }
290}
291
292pub enum GeoSearchOrder {
295 Asc,
297 Desc,
299}
300
301impl ToArgs for GeoSearchOrder {
302 fn write_args(&self, args: &mut CommandArgs) {
303 match self {
304 GeoSearchOrder::Asc => args.arg("ASC"),
305 GeoSearchOrder::Desc => args.arg("DESC"),
306 };
307 }
308}
309
310#[derive(Default)]
312pub struct GeoSearchOptions {
313 command_args: CommandArgs,
314}
315
316impl GeoSearchOptions {
317 #[must_use]
318 pub fn order(mut self, order: GeoSearchOrder) -> Self {
319 Self {
320 command_args: self.command_args.arg(order).build(),
321 }
322 }
323
324 #[must_use]
325 pub fn count(mut self, count: usize, any: bool) -> Self {
326 Self {
327 command_args: self
328 .command_args
329 .arg("COUNT")
330 .arg(count)
331 .arg_if(any, "ANY")
332 .build(),
333 }
334 }
335
336 #[must_use]
337 pub fn with_coord(mut self) -> Self {
338 Self {
339 command_args: self.command_args.arg("WITHCOORD").build(),
340 }
341 }
342
343 #[must_use]
344 pub fn with_dist(mut self) -> Self {
345 Self {
346 command_args: self.command_args.arg("WITHDIST").build(),
347 }
348 }
349
350 #[must_use]
351 pub fn with_hash(mut self) -> Self {
352 Self {
353 command_args: self.command_args.arg("WITHHASH").build(),
354 }
355 }
356}
357
358impl ToArgs for GeoSearchOptions {
359 fn write_args(&self, args: &mut CommandArgs) {
360 args.arg(&self.command_args);
361 }
362}
363
364#[derive(Debug)]
366pub struct GeoSearchResult<M>
367where
368 M: PrimitiveResponse,
369{
370 pub member: M,
372
373 pub distance: Option<f64>,
375
376 pub geo_hash: Option<i64>,
378
379 pub coordinates: Option<(f64, f64)>,
381}
382
383impl<'de, M> Deserialize<'de> for GeoSearchResult<M>
384where
385 M: PrimitiveResponse + DeserializeOwned,
386{
387 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
388 where
389 D: Deserializer<'de>,
390 {
391 pub enum GeoSearchResultField {
392 Distance(f64),
393 GeoHash(i64),
394 Coordinates((f64, f64)),
395 }
396
397 impl<'de> Deserialize<'de> for GeoSearchResultField {
398 #[inline]
399 fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
400 where
401 D: Deserializer<'de>,
402 {
403 struct GeoSearchResultFieldVisitor;
404
405 impl<'de> Visitor<'de> for GeoSearchResultFieldVisitor {
406 type Value = GeoSearchResultField;
407
408 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
409 formatter.write_str("GeoSearchResultField")
410 }
411
412 fn visit_borrowed_bytes<E>(self, v: &'de [u8]) -> Result<Self::Value, E>
413 where
414 E: de::Error,
415 {
416 let Ok(distance) = std::str::from_utf8(v) else {
417 return Err(de::Error::invalid_value(
418 Unexpected::Bytes(v),
419 &"A valid f64 encoded in an UTF8 string",
420 ));
421 };
422
423 let Ok(distance) = distance.parse::<f64>() else {
424 return Err(de::Error::invalid_value(
425 Unexpected::Bytes(v),
426 &"A valid f64 encoded in an UTF8 string",
427 ));
428 };
429
430 Ok(GeoSearchResultField::Distance(distance))
431 }
432
433 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
434 where
435 E: de::Error,
436 {
437 Ok(GeoSearchResultField::GeoHash(v))
438 }
439
440 fn visit_seq<A>(self, seq: A) -> Result<Self::Value, A::Error>
441 where
442 A: de::SeqAccess<'de>,
443 {
444 let coordinates =
445 <(f64, f64)>::deserialize(SeqAccessDeserializer::new(seq))?;
446 Ok(GeoSearchResultField::Coordinates(coordinates))
447 }
448 }
449
450 deserializer.deserialize_any(GeoSearchResultFieldVisitor)
451 }
452 }
453
454 pub struct GeoSearchResultVisitor<M>
455 where
456 M: PrimitiveResponse,
457 {
458 phantom: PhantomData<M>,
459 }
460
461 impl<'de, M> Visitor<'de> for GeoSearchResultVisitor<M>
462 where
463 M: PrimitiveResponse + DeserializeOwned,
464 {
465 type Value = GeoSearchResult<M>;
466
467 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
468 formatter.write_str("GeoSearchResult<M>")
469 }
470
471 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
472 where
473 A: de::SeqAccess<'de>,
474 {
475 let Some(member) = seq.next_element::<M>().map_err(de::Error::custom)? else {
476 return Err(de::Error::invalid_length(0, &"more elements in sequence"));
477 };
478
479 let mut distance: Option<f64> = None;
480 let mut geo_hash: Option<i64> = None;
481 let mut coordinates: Option<(f64, f64)> = None;
482
483 while let Some(field) = seq.next_element::<GeoSearchResultField>()? {
484 match field {
485 GeoSearchResultField::Distance(d) => distance = Some(d),
486 GeoSearchResultField::GeoHash(gh) => geo_hash = Some(gh),
487 GeoSearchResultField::Coordinates(c) => coordinates = Some(c),
488 }
489 }
490
491 Ok(GeoSearchResult {
492 member,
493 distance,
494 geo_hash,
495 coordinates,
496 })
497 }
498
499 fn visit_borrowed_bytes<E>(self, v: &[u8]) -> Result<Self::Value, E>
500 where
501 E: de::Error,
502 {
503 let member = M::deserialize(BytesDeserializer::new(v))?;
504
505 Ok(GeoSearchResult {
506 member,
507 distance: None,
508 geo_hash: None,
509 coordinates: None,
510 })
511 }
512 }
513
514 deserializer.deserialize_any(GeoSearchResultVisitor::<M> {
515 phantom: PhantomData,
516 })
517 }
518}
519
520#[derive(Default)]
522pub struct GeoSearchStoreOptions {
523 command_args: CommandArgs,
524}
525
526impl GeoSearchStoreOptions {
527 #[must_use]
528 pub fn order(mut self, order: GeoSearchOrder) -> Self {
529 Self {
530 command_args: self.command_args.arg(order).build(),
531 }
532 }
533
534 #[must_use]
535 pub fn count(mut self, count: usize, any: bool) -> Self {
536 Self {
537 command_args: self
538 .command_args
539 .arg("COUNT")
540 .arg(count)
541 .arg_if(any, "ANY")
542 .build(),
543 }
544 }
545
546 #[must_use]
547 pub fn store_dist(mut self, store_dist: bool) -> Self {
548 Self {
549 command_args: self.command_args.arg_if(store_dist, "STOREDIST").build(),
550 }
551 }
552}
553
554impl ToArgs for GeoSearchStoreOptions {
555 fn write_args(&self, args: &mut CommandArgs) {
556 args.arg(&self.command_args);
557 }
558}