1use crate::errors::{invalid_type_error, ParsingError};
4use crate::types::{FromRedisValue, RedisWrite, ToRedisArgs, ToSingleRedisArg, Value};
5
6#[non_exhaustive]
11pub enum Unit {
12 Meters,
14 Kilometers,
16 Miles,
18 Feet,
20}
21
22impl ToRedisArgs for Unit {
23 fn write_redis_args<W>(&self, out: &mut W)
24 where
25 W: ?Sized + RedisWrite,
26 {
27 let unit = match *self {
28 Unit::Meters => "m",
29 Unit::Kilometers => "km",
30 Unit::Miles => "mi",
31 Unit::Feet => "ft",
32 };
33 out.write_arg(unit.as_bytes());
34 }
35}
36
37impl ToSingleRedisArg for Unit {}
38
39#[allow(clippy::derive_partial_eq_without_eq)] #[derive(Debug, PartialEq)]
51pub struct Coord<T> {
52 pub longitude: T,
54 pub latitude: T,
56}
57
58impl<T> Coord<T> {
59 pub fn lon_lat(longitude: T, latitude: T) -> Coord<T> {
61 Coord {
62 longitude,
63 latitude,
64 }
65 }
66}
67
68impl<T: FromRedisValue> FromRedisValue for Coord<T> {
69 fn from_redis_value_ref(v: &Value) -> Result<Self, ParsingError> {
70 let values: Vec<T> = FromRedisValue::from_redis_value_ref(v)?;
71 let mut values = values.into_iter();
72 let (longitude, latitude) = match (values.next(), values.next(), values.next()) {
73 (Some(longitude), Some(latitude), None) => (longitude, latitude),
74 _ => invalid_type_error!(v, "Expect a pair of numbers"),
75 };
76 Ok(Coord {
77 longitude,
78 latitude,
79 })
80 }
81
82 fn from_redis_value(v: Value) -> Result<Self, ParsingError> {
83 Self::from_redis_value_ref(&v)
84 }
85}
86
87impl<T: ToRedisArgs> ToRedisArgs for Coord<T> {
88 fn write_redis_args<W>(&self, out: &mut W)
89 where
90 W: ?Sized + RedisWrite,
91 {
92 ToRedisArgs::write_redis_args(&self.longitude, out);
93 ToRedisArgs::write_redis_args(&self.latitude, out);
94 }
95
96 fn num_of_args(&self) -> usize {
97 2
98 }
99}
100
101#[derive(Default)]
106#[non_exhaustive]
107pub enum RadiusOrder {
108 #[default]
110 Unsorted,
111
112 Asc,
114
115 Desc,
117}
118
119#[derive(Default)]
144pub struct RadiusOptions {
145 with_coord: bool,
146 with_dist: bool,
147 count: Option<usize>,
148 order: RadiusOrder,
149 store: Option<Vec<Vec<u8>>>,
150 store_dist: Option<Vec<Vec<u8>>>,
151}
152
153impl RadiusOptions {
154 pub fn limit(mut self, n: usize) -> Self {
156 self.count = Some(n);
157 self
158 }
159
160 pub fn with_dist(mut self) -> Self {
164 self.with_dist = true;
165 self
166 }
167
168 pub fn with_coord(mut self) -> Self {
170 self.with_coord = true;
171 self
172 }
173
174 pub fn order(mut self, o: RadiusOrder) -> Self {
176 self.order = o;
177 self
178 }
179
180 pub fn store<K: ToRedisArgs>(mut self, key: K) -> Self {
184 self.store = Some(ToRedisArgs::to_redis_args(&key));
185 self
186 }
187
188 pub fn store_dist<K: ToRedisArgs>(mut self, key: K) -> Self {
191 self.store_dist = Some(ToRedisArgs::to_redis_args(&key));
192 self
193 }
194}
195
196impl ToRedisArgs for RadiusOptions {
197 fn write_redis_args<W>(&self, out: &mut W)
198 where
199 W: ?Sized + RedisWrite,
200 {
201 if self.with_coord {
202 out.write_arg(b"WITHCOORD");
203 }
204
205 if self.with_dist {
206 out.write_arg(b"WITHDIST");
207 }
208
209 if let Some(n) = self.count {
210 out.write_arg(b"COUNT");
211 out.write_arg_fmt(n);
212 }
213
214 match self.order {
215 RadiusOrder::Asc => out.write_arg(b"ASC"),
216 RadiusOrder::Desc => out.write_arg(b"DESC"),
217 _ => (),
218 };
219
220 if let Some(ref store) = self.store {
221 out.write_arg(b"STORE");
222 for i in store {
223 out.write_arg(i);
224 }
225 }
226
227 if let Some(ref store_dist) = self.store_dist {
228 out.write_arg(b"STOREDIST");
229 for i in store_dist {
230 out.write_arg(i);
231 }
232 }
233 }
234
235 fn num_of_args(&self) -> usize {
236 let mut n: usize = 0;
237 if self.with_coord {
238 n += 1;
239 }
240 if self.with_dist {
241 n += 1;
242 }
243 if self.count.is_some() {
244 n += 2;
245 }
246 match self.order {
247 RadiusOrder::Asc => n += 1,
248 RadiusOrder::Desc => n += 1,
249 _ => {}
250 };
251 n += 1 + self.store.as_ref().map(|v| v.len()).unwrap_or(0);
252 n += 1 + self.store_dist.as_ref().map(|v| v.len()).unwrap_or(0);
253 n
254 }
255}
256
257pub struct RadiusSearchResult {
262 pub name: String,
264 pub coord: Option<Coord<f64>>,
266 pub dist: Option<f64>,
268}
269
270impl FromRedisValue for RadiusSearchResult {
271 fn from_redis_value(v: Value) -> Result<Self, ParsingError> {
272 match v {
273 Value::BulkString(b) => {
274 let s = String::from_utf8(b)?;
275 Ok(RadiusSearchResult {
276 name: s,
277 coord: None,
278 dist: None,
279 })
280 }
281 Value::Array(items) => RadiusSearchResult::parse_multi_values(items),
282 _ => invalid_type_error!(v, "Response type not RadiusSearchResult compatible."),
283 }
284 }
285}
286
287impl RadiusSearchResult {
288 fn parse_multi_values(items: Vec<Value>) -> Result<Self, ParsingError> {
289 let mut iter = items.into_iter();
290
291 let name: String = match iter.next().map(FromRedisValue::from_redis_value) {
293 Some(Ok(n)) => n,
294 _ => return Err(arcstr::literal!("Missing member name").into()),
295 };
296
297 let (dist, coord) = match (iter.next(), iter.next()) {
298 (None, None) => (None, None),
299 (Some(Value::Array(coords)), None) => {
300 (None, Some(Coord::from_redis_value(Value::Array(coords))?))
301 }
302 (Some(dist), coord) => {
303 let dist = FromRedisValue::from_redis_value(dist)?;
304
305 let coord = match coord.map(FromRedisValue::from_redis_value) {
306 Some(Ok(c)) => Some(c),
307 _ => None,
308 };
309
310 (dist, coord)
311 }
312 _ => invalid_type_error!("Response type not RadiusSearchResult compatible."),
313 };
314
315 Ok(RadiusSearchResult { name, coord, dist })
316 }
317}
318
319#[cfg(test)]
320mod tests {
321 use super::{Coord, RadiusOptions, RadiusOrder};
322 use crate::types::ToRedisArgs;
323 use std::str;
324
325 macro_rules! assert_args {
326 ($value:expr, $($args:expr),+) => {
327 let args = $value.to_redis_args();
328 let strings: Vec<_> = args.iter()
329 .map(|a| str::from_utf8(a.as_ref()).unwrap())
330 .collect();
331 assert_eq!(strings, vec![$($args),+]);
332 }
333 }
334
335 #[test]
336 fn test_coord_to_args() {
337 let member = ("Palermo", Coord::lon_lat("13.361389", "38.115556"));
338 assert_args!(&member, "Palermo", "13.361389", "38.115556");
339 }
340
341 #[test]
342 fn test_radius_options() {
343 let empty = RadiusOptions::default();
345 assert_eq!(ToRedisArgs::to_redis_args(&empty).len(), 0);
346
347 let opts = RadiusOptions::default;
349
350 assert_args!(opts().with_coord().with_dist(), "WITHCOORD", "WITHDIST");
351
352 assert_args!(opts().limit(50), "COUNT", "50");
353
354 assert_args!(opts().limit(50).store("x"), "COUNT", "50", "STORE", "x");
355
356 assert_args!(
357 opts().limit(100).store_dist("y"),
358 "COUNT",
359 "100",
360 "STOREDIST",
361 "y"
362 );
363
364 assert_args!(
365 opts().order(RadiusOrder::Asc).limit(10).with_dist(),
366 "WITHDIST",
367 "COUNT",
368 "10",
369 "ASC"
370 );
371 }
372}