1use super::{ErrorKind, RedisResult};
4use crate::types::{FromRedisValue, RedisWrite, ToRedisArgs, Value};
5
6macro_rules! invalid_type_error {
7 ($v:expr, $det:expr) => {{
8 fail!((
9 ErrorKind::TypeError,
10 "Response was of incompatible type",
11 format!("{:?} (response was {:?})", $det, $v)
12 ));
13 }};
14}
15
16pub enum Unit {
21 Meters,
23 Kilometers,
25 Miles,
27 Feet,
29}
30
31impl ToRedisArgs for Unit {
32 fn write_redis_args<W>(&self, out: &mut W)
33 where
34 W: ?Sized + RedisWrite,
35 {
36 let unit = match *self {
37 Unit::Meters => "m",
38 Unit::Kilometers => "km",
39 Unit::Miles => "mi",
40 Unit::Feet => "ft",
41 };
42 out.write_arg(unit.as_bytes());
43 }
44}
45
46#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Debug, PartialEq)]
58pub struct Coord<T> {
59 pub longitude: T,
61 pub latitude: T,
63}
64
65impl<T> Coord<T> {
66 pub fn lon_lat(longitude: T, latitude: T) -> Coord<T> {
68 Coord {
69 longitude,
70 latitude,
71 }
72 }
73}
74
75impl<T: FromRedisValue> FromRedisValue for Coord<T> {
76 fn from_redis_value(v: &Value) -> RedisResult<Self> {
77 let values: Vec<T> = FromRedisValue::from_redis_value(v)?;
78 let mut values = values.into_iter();
79 let (longitude, latitude) = match (values.next(), values.next(), values.next()) {
80 (Some(longitude), Some(latitude), None) => (longitude, latitude),
81 _ => invalid_type_error!(v, "Expect a pair of numbers"),
82 };
83 Ok(Coord {
84 longitude,
85 latitude,
86 })
87 }
88}
89
90impl<T: ToRedisArgs> ToRedisArgs for Coord<T> {
91 fn write_redis_args<W>(&self, out: &mut W)
92 where
93 W: ?Sized + RedisWrite,
94 {
95 ToRedisArgs::write_redis_args(&self.longitude, out);
96 ToRedisArgs::write_redis_args(&self.latitude, out);
97 }
98
99 fn num_of_args(&self) -> usize {
100 2
101 }
102}
103
104#[derive(Default)]
109pub enum RadiusOrder {
110 #[default]
112 Unsorted,
113
114 Asc,
116
117 Desc,
119}
120
121#[derive(Default)]
146pub struct RadiusOptions {
147 with_coord: bool,
148 with_dist: bool,
149 count: Option<usize>,
150 order: RadiusOrder,
151 store: Option<Vec<Vec<u8>>>,
152 store_dist: Option<Vec<Vec<u8>>>,
153}
154
155impl RadiusOptions {
156 pub fn limit(mut self, n: usize) -> Self {
158 self.count = Some(n);
159 self
160 }
161
162 pub fn with_dist(mut self) -> Self {
166 self.with_dist = true;
167 self
168 }
169
170 pub fn with_coord(mut self) -> Self {
172 self.with_coord = true;
173 self
174 }
175
176 pub fn order(mut self, o: RadiusOrder) -> Self {
178 self.order = o;
179 self
180 }
181
182 pub fn store<K: ToRedisArgs>(mut self, key: K) -> Self {
186 self.store = Some(ToRedisArgs::to_redis_args(&key));
187 self
188 }
189
190 pub fn store_dist<K: ToRedisArgs>(mut self, key: K) -> Self {
193 self.store_dist = Some(ToRedisArgs::to_redis_args(&key));
194 self
195 }
196}
197
198impl ToRedisArgs for RadiusOptions {
199 fn write_redis_args<W>(&self, out: &mut W)
200 where
201 W: ?Sized + RedisWrite,
202 {
203 if self.with_coord {
204 out.write_arg(b"WITHCOORD");
205 }
206
207 if self.with_dist {
208 out.write_arg(b"WITHDIST");
209 }
210
211 if let Some(n) = self.count {
212 out.write_arg(b"COUNT");
213 out.write_arg_fmt(n);
214 }
215
216 match self.order {
217 RadiusOrder::Asc => out.write_arg(b"ASC"),
218 RadiusOrder::Desc => out.write_arg(b"DESC"),
219 _ => (),
220 };
221
222 if let Some(ref store) = self.store {
223 out.write_arg(b"STORE");
224 for i in store {
225 out.write_arg(i);
226 }
227 }
228
229 if let Some(ref store_dist) = self.store_dist {
230 out.write_arg(b"STOREDIST");
231 for i in store_dist {
232 out.write_arg(i);
233 }
234 }
235 }
236
237 fn num_of_args(&self) -> usize {
238 let mut n: usize = 0;
239 if self.with_coord {
240 n += 1;
241 }
242 if self.with_dist {
243 n += 1;
244 }
245 if self.count.is_some() {
246 n += 2;
247 }
248 match self.order {
249 RadiusOrder::Asc => n += 1,
250 RadiusOrder::Desc => n += 1,
251 _ => {}
252 };
253 if self.store.is_some() {
254 n += 1 + self.store.as_ref().unwrap().len();
255 }
256 if self.store_dist.is_some() {
257 n += 1 + self.store_dist.as_ref().unwrap().len();
258 }
259 n
260 }
261}
262
263pub struct RadiusSearchResult {
268 pub name: String,
270 pub coord: Option<Coord<f64>>,
272 pub dist: Option<f64>,
274}
275
276impl FromRedisValue for RadiusSearchResult {
277 fn from_redis_value(v: &Value) -> RedisResult<Self> {
278 if let Ok(name) = FromRedisValue::from_redis_value(v) {
280 return Ok(RadiusSearchResult {
281 name,
282 coord: None,
283 dist: None,
284 });
285 }
286
287 if let Value::Array(ref items) = *v {
289 if let Some(result) = RadiusSearchResult::parse_multi_values(items) {
290 return Ok(result);
291 }
292 }
293
294 invalid_type_error!(v, "Response type not RadiusSearchResult compatible.");
295 }
296}
297
298impl RadiusSearchResult {
299 fn parse_multi_values(items: &[Value]) -> Option<Self> {
300 let mut iter = items.iter();
301
302 let name: String = match iter.next().map(FromRedisValue::from_redis_value) {
304 Some(Ok(n)) => n,
305 _ => return None,
306 };
307
308 let mut next = iter.next();
309
310 let dist = match next.map(FromRedisValue::from_redis_value) {
312 Some(Ok(c)) => {
313 next = iter.next();
314 Some(c)
315 }
316 _ => None,
317 };
318
319 let coord = match next.map(FromRedisValue::from_redis_value) {
322 Some(Ok(c)) => Some(c),
323 _ => None,
324 };
325
326 Some(RadiusSearchResult { name, coord, dist })
327 }
328}
329
330#[cfg(test)]
331mod tests {
332 use super::{Coord, RadiusOptions, RadiusOrder};
333 use crate::types::ToRedisArgs;
334 use std::str;
335
336 macro_rules! assert_args {
337 ($value:expr, $($args:expr),+) => {
338 let args = $value.to_redis_args();
339 let strings: Vec<_> = args.iter()
340 .map(|a| str::from_utf8(a.as_ref()).unwrap())
341 .collect();
342 assert_eq!(strings, vec![$($args),+]);
343 }
344 }
345
346 #[test]
347 fn test_coord_to_args() {
348 let member = ("Palermo", Coord::lon_lat("13.361389", "38.115556"));
349 assert_args!(&member, "Palermo", "13.361389", "38.115556");
350 }
351
352 #[test]
353 fn test_radius_options() {
354 let empty = RadiusOptions::default();
356 assert_eq!(ToRedisArgs::to_redis_args(&empty).len(), 0);
357
358 let opts = RadiusOptions::default;
360
361 assert_args!(opts().with_coord().with_dist(), "WITHCOORD", "WITHDIST");
362
363 assert_args!(opts().limit(50), "COUNT", "50");
364
365 assert_args!(opts().limit(50).store("x"), "COUNT", "50", "STORE", "x");
366
367 assert_args!(
368 opts().limit(100).store_dist("y"),
369 "COUNT",
370 "100",
371 "STOREDIST",
372 "y"
373 );
374
375 assert_args!(
376 opts().order(RadiusOrder::Asc).limit(10).with_dist(),
377 "WITHDIST",
378 "COUNT",
379 "10",
380 "ASC"
381 );
382 }
383}