1use crate::{
2 prepare_command,
3 resp::{
4 cmd, ArgsOrCollection, CommandArg, CommandArgs, FromSingleValueArray, FromValue, IntoArgs,
5 SingleArgOrCollection, Value,
6 },
7 Error, PreparedCommand, Result,
8};
9
10pub trait GeoCommands {
15 #[must_use]
24 fn geoadd<K, M, I>(
25 &mut self,
26 key: K,
27 condition: GeoAddCondition,
28 change: bool,
29 items: I,
30 ) -> PreparedCommand<Self, usize>
31 where
32 Self: Sized,
33 K: Into<CommandArg>,
34 M: Into<CommandArg>,
35 I: ArgsOrCollection<(f64, f64, M)>,
36 {
37 prepare_command(
38 self,
39 cmd("GEOADD")
40 .arg(key)
41 .arg(condition)
42 .arg_if(change, "CH")
43 .arg(items),
44 )
45 }
46
47 #[must_use]
56 fn geodist<K, M>(
57 &mut self,
58 key: K,
59 member1: M,
60 member2: M,
61 unit: GeoUnit,
62 ) -> PreparedCommand<Self, Option<f64>>
63 where
64 Self: Sized,
65 K: Into<CommandArg>,
66 M: Into<CommandArg>,
67 {
68 prepare_command(
69 self,
70 cmd("GEODIST").arg(key).arg(member1).arg(member2).arg(unit),
71 )
72 }
73
74 #[must_use]
83 fn geohash<K, M, C>(&mut self, key: K, members: C) -> PreparedCommand<Self, Vec<String>>
84 where
85 Self: Sized,
86 K: Into<CommandArg>,
87 M: Into<CommandArg>,
88 C: SingleArgOrCollection<M>,
89 {
90 prepare_command(self, cmd("GEOHASH").arg(key).arg(members))
91 }
92
93 #[must_use]
104 fn geopos<K, M, C>(
105 &mut self,
106 key: K,
107 members: C,
108 ) -> PreparedCommand<Self, Vec<Option<(f64, f64)>>>
109 where
110 Self: Sized,
111 K: Into<CommandArg>,
112 M: Into<CommandArg>,
113 C: SingleArgOrCollection<M>,
114 {
115 prepare_command(self, cmd("GEOPOS").arg(key).arg(members))
116 }
117
118 #[must_use]
128 fn geosearch<K, M1, M2, A>(
129 &mut self,
130 key: K,
131 from: GeoSearchFrom<M1>,
132 by: GeoSearchBy,
133 options: GeoSearchOptions,
134 ) -> PreparedCommand<Self, A>
135 where
136 Self: Sized,
137 K: Into<CommandArg>,
138 M1: Into<CommandArg>,
139 M2: FromValue,
140 A: FromSingleValueArray<GeoSearchResult<M2>>,
141 {
142 prepare_command(
143 self,
144 cmd("GEOSEARCH").arg(key).arg(from).arg(by).arg(options),
145 )
146 }
147
148 #[must_use]
156 fn geosearchstore<D, S, M>(
157 &mut self,
158 destination: D,
159 source: S,
160 from: GeoSearchFrom<M>,
161 by: GeoSearchBy,
162 options: GeoSearchStoreOptions,
163 ) -> PreparedCommand<Self, usize>
164 where
165 Self: Sized,
166 D: Into<CommandArg>,
167 S: Into<CommandArg>,
168 M: Into<CommandArg>,
169 {
170 prepare_command(
171 self,
172 cmd("GEOSEARCHSTORE")
173 .arg(destination)
174 .arg(source)
175 .arg(from)
176 .arg(by)
177 .arg(options),
178 )
179 }
180}
181
182pub enum GeoAddCondition {
184 None,
186 NX,
188 XX,
190}
191
192impl Default for GeoAddCondition {
193 fn default() -> Self {
194 GeoAddCondition::None
195 }
196}
197
198impl IntoArgs for GeoAddCondition {
199 fn into_args(self, args: CommandArgs) -> CommandArgs {
200 match self {
201 GeoAddCondition::None => args,
202 GeoAddCondition::NX => args.arg("NX"),
203 GeoAddCondition::XX => args.arg("XX"),
204 }
205 }
206}
207
208pub enum GeoUnit {
210 Meters,
211 Kilometers,
212 Miles,
213 Feet,
214}
215
216impl IntoArgs for GeoUnit {
217 fn into_args(self, args: CommandArgs) -> CommandArgs {
218 args.arg(match self {
219 GeoUnit::Meters => CommandArg::Str("m"),
220 GeoUnit::Kilometers => CommandArg::Str("km"),
221 GeoUnit::Miles => CommandArg::Str("mi"),
222 GeoUnit::Feet => CommandArg::Str("ft"),
223 })
224 }
225}
226
227pub enum GeoSearchFrom<M>
229where
230 M: Into<CommandArg>,
231{
232 FromMember { member: M },
234 FromLonLat { longitude: f64, latitude: f64 },
236}
237
238impl<M> IntoArgs for GeoSearchFrom<M>
239where
240 M: Into<CommandArg>,
241{
242 fn into_args(self, args: CommandArgs) -> CommandArgs {
243 match self {
244 GeoSearchFrom::FromMember { member } => args.arg("FROMMEMBER").arg(member),
245 GeoSearchFrom::FromLonLat {
246 longitude,
247 latitude,
248 } => args.arg("FROMLONLAT").arg(longitude).arg(latitude),
249 }
250 }
251}
252
253pub enum GeoSearchBy {
255 ByRadius { radius: f64, unit: GeoUnit },
257 ByBox {
259 width: f64,
260 height: f64,
261 unit: GeoUnit,
262 },
263}
264
265impl IntoArgs for GeoSearchBy {
266 fn into_args(self, args: CommandArgs) -> CommandArgs {
267 match self {
268 GeoSearchBy::ByRadius { radius, unit } => args.arg("BYRADIUS").arg(radius).arg(unit),
269 GeoSearchBy::ByBox {
270 width,
271 height,
272 unit,
273 } => args.arg("BYBOX").arg(width).arg(height).arg(unit),
274 }
275 }
276}
277
278pub enum GeoSearchOrder {
281 Asc,
283 Desc,
285}
286
287impl IntoArgs for GeoSearchOrder {
288 fn into_args(self, args: CommandArgs) -> CommandArgs {
289 match self {
290 GeoSearchOrder::Asc => args.arg("ASC"),
291 GeoSearchOrder::Desc => args.arg("DESC"),
292 }
293 }
294}
295
296#[derive(Default)]
298pub struct GeoSearchOptions {
299 command_args: CommandArgs,
300}
301
302impl GeoSearchOptions {
303 #[must_use]
304 pub fn order(self, order: GeoSearchOrder) -> Self {
305 Self {
306 command_args: self.command_args.arg(order),
307 }
308 }
309
310 #[must_use]
311 pub fn count(self, count: usize, any: bool) -> Self {
312 Self {
313 command_args: self.command_args.arg("COUNT").arg(count).arg_if(any, "ANY"),
314 }
315 }
316
317 #[must_use]
318 pub fn with_coord(self) -> Self {
319 Self {
320 command_args: self.command_args.arg("WITHCOORD"),
321 }
322 }
323
324 #[must_use]
325 pub fn with_dist(self) -> Self {
326 Self {
327 command_args: self.command_args.arg("WITHDIST"),
328 }
329 }
330
331 #[must_use]
332 pub fn with_hash(self) -> Self {
333 Self {
334 command_args: self.command_args.arg("WITHHASH"),
335 }
336 }
337}
338
339impl IntoArgs for GeoSearchOptions {
340 fn into_args(self, args: CommandArgs) -> CommandArgs {
341 args.arg(self.command_args)
342 }
343}
344
345#[derive(Debug)]
347pub struct GeoSearchResult<M>
348where
349 M: FromValue,
350{
351 pub member: M,
353
354 pub distance: Option<f64>,
356
357 pub geo_hash: Option<i64>,
359
360 pub coordinates: Option<(f64, f64)>,
362}
363
364impl<M> FromValue for GeoSearchResult<M>
365where
366 M: FromValue,
367{
368 fn from_value(value: Value) -> Result<Self> {
369 match value {
370 Value::BulkString(_) => Ok(GeoSearchResult {
371 member: value.into()?,
372 distance: None,
373 geo_hash: None,
374 coordinates: None,
375 }),
376 Value::Array(_) => {
377 let values: Vec<Value> = value.into()?;
378 let mut it = values.into_iter();
379 let mut distance: Option<f64> = None;
380 let mut geo_hash: Option<i64> = None;
381 let mut coordinates: Option<(f64, f64)> = None;
382
383 let member = match it.next() {
384 Some(value) => value.into()?,
385 None => {
386 return Err(Error::Client("Unexpected geo search result".to_owned()));
387 }
388 };
389
390 for value in it {
391 match value {
392 Value::BulkString(Some(_)) => distance = Some(value.into()?),
393 Value::Integer(h) => geo_hash = Some(h),
394 Value::Array(_) => coordinates = Some(value.into()?),
395 _ => return Err(Error::Client("Unexpected geo search result".to_owned())),
396 }
397 }
398
399 Ok(GeoSearchResult {
400 member,
401 distance,
402 geo_hash,
403 coordinates,
404 })
405 }
406 _ => Err(Error::Client("Unexpected geo search result".to_owned())),
407 }
408 }
409}
410
411#[derive(Default)]
413pub struct GeoSearchStoreOptions {
414 command_args: CommandArgs,
415}
416
417impl GeoSearchStoreOptions {
418 #[must_use]
419 pub fn order(self, order: GeoSearchOrder) -> Self {
420 Self {
421 command_args: self.command_args.arg(order),
422 }
423 }
424
425 #[must_use]
426 pub fn count(self, count: usize, any: bool) -> Self {
427 Self {
428 command_args: self.command_args.arg("COUNT").arg(count).arg_if(any, "ANY"),
429 }
430 }
431
432 #[must_use]
433 pub fn store_dist(self, store_dist: bool) -> Self {
434 Self {
435 command_args: self.command_args.arg_if(store_dist, "STOREDIST"),
436 }
437 }
438}
439
440impl IntoArgs for GeoSearchStoreOptions {
441 fn into_args(self, args: CommandArgs) -> CommandArgs {
442 args.arg(self.command_args)
443 }
444}