use super::{ErrorKind, RedisResult};
use crate::types::{FromRedisValue, RedisWrite, ToRedisArgs, Value};
macro_rules! invalid_type_error {
($v:expr, $det:expr) => {{
fail!((
ErrorKind::TypeError,
"Response was of incompatible type",
format!("{:?} (response was {:?})", $det, $v)
));
}};
}
pub enum Unit {
Meters,
Kilometers,
Miles,
Feet,
}
impl ToRedisArgs for Unit {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
let unit = match *self {
Unit::Meters => "m",
Unit::Kilometers => "km",
Unit::Miles => "mi",
Unit::Feet => "ft",
};
out.write_arg(unit.as_bytes());
}
}
#[derive(Debug, PartialEq)]
pub struct Coord<T> {
pub longitude: T,
pub latitude: T,
}
impl<T> Coord<T> {
pub fn lon_lat(longitude: T, latitude: T) -> Coord<T> {
Coord {
longitude,
latitude,
}
}
}
impl<T: FromRedisValue> FromRedisValue for Coord<T> {
fn from_redis_value(v: &Value) -> RedisResult<Self> {
let values: Vec<T> = FromRedisValue::from_redis_value(v)?;
let mut values = values.into_iter();
let (longitude, latitude) = match (values.next(), values.next(), values.next()) {
(Some(longitude), Some(latitude), None) => (longitude, latitude),
_ => invalid_type_error!(v, "Expect a pair of numbers"),
};
Ok(Coord {
longitude,
latitude,
})
}
}
impl<T: ToRedisArgs> ToRedisArgs for Coord<T> {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
ToRedisArgs::write_redis_args(&self.longitude, out);
ToRedisArgs::write_redis_args(&self.latitude, out);
}
fn is_single_arg(&self) -> bool {
false
}
}
pub enum RadiusOrder {
Unsorted,
Asc,
Desc,
}
impl Default for RadiusOrder {
fn default() -> RadiusOrder {
RadiusOrder::Unsorted
}
}
#[derive(Default)]
pub struct RadiusOptions {
with_coord: bool,
with_dist: bool,
count: Option<usize>,
order: RadiusOrder,
store: Option<Vec<Vec<u8>>>,
store_dist: Option<Vec<Vec<u8>>>,
}
impl RadiusOptions {
pub fn limit(mut self, n: usize) -> Self {
self.count = Some(n);
self
}
pub fn with_dist(mut self) -> Self {
self.with_dist = true;
self
}
pub fn with_coord(mut self) -> Self {
self.with_coord = true;
self
}
pub fn order(mut self, o: RadiusOrder) -> Self {
self.order = o;
self
}
pub fn store<K: ToRedisArgs>(mut self, key: K) -> Self {
self.store = Some(ToRedisArgs::to_redis_args(&key));
self
}
pub fn store_dist<K: ToRedisArgs>(mut self, key: K) -> Self {
self.store_dist = Some(ToRedisArgs::to_redis_args(&key));
self
}
}
impl ToRedisArgs for RadiusOptions {
fn write_redis_args<W>(&self, out: &mut W)
where
W: ?Sized + RedisWrite,
{
if self.with_coord {
out.write_arg(b"WITHCOORD");
}
if self.with_dist {
out.write_arg(b"WITHDIST");
}
if let Some(n) = self.count {
out.write_arg(b"COUNT");
out.write_arg(format!("{}", n).as_bytes());
}
match self.order {
RadiusOrder::Asc => out.write_arg(b"ASC"),
RadiusOrder::Desc => out.write_arg(b"DESC"),
_ => (),
};
if let Some(ref store) = self.store {
out.write_arg(b"STORE");
for i in store {
out.write_arg(i);
}
}
if let Some(ref store_dist) = self.store_dist {
out.write_arg(b"STOREDIST");
for i in store_dist {
out.write_arg(i);
}
}
}
fn is_single_arg(&self) -> bool {
false
}
}
pub struct RadiusSearchResult {
pub name: String,
pub coord: Option<Coord<f64>>,
pub dist: Option<f64>,
}
impl FromRedisValue for RadiusSearchResult {
fn from_redis_value(v: &Value) -> RedisResult<Self> {
if let Ok(name) = FromRedisValue::from_redis_value(v) {
return Ok(RadiusSearchResult {
name,
coord: None,
dist: None,
});
}
if let Value::Bulk(ref items) = *v {
if let Some(result) = RadiusSearchResult::parse_multi_values(items) {
return Ok(result);
}
}
invalid_type_error!(v, "Response type not RadiusSearchResult compatible.");
}
}
impl RadiusSearchResult {
fn parse_multi_values(items: &[Value]) -> Option<Self> {
let mut iter = items.iter();
let name: String = match iter.next().map(FromRedisValue::from_redis_value) {
Some(Ok(n)) => n,
_ => return None,
};
let mut next = iter.next();
let dist = match next.map(FromRedisValue::from_redis_value) {
Some(Ok(c)) => {
next = iter.next();
Some(c)
}
_ => None,
};
let coord = match next.map(FromRedisValue::from_redis_value) {
Some(Ok(c)) => Some(c),
_ => None,
};
Some(RadiusSearchResult { name, coord, dist })
}
}
#[cfg(test)]
mod tests {
use super::{Coord, RadiusOptions, RadiusOrder};
use crate::types::ToRedisArgs;
use std::str;
macro_rules! assert_args {
($value:expr, $($args:expr),+) => {
let args = $value.to_redis_args();
let strings: Vec<_> = args.iter()
.map(|a| str::from_utf8(a.as_ref()).unwrap())
.collect();
assert_eq!(strings, vec![$($args),+]);
}
}
#[test]
fn test_coord_to_args() {
let member = ("Palermo", Coord::lon_lat("13.361389", "38.115556"));
assert_args!(&member, "Palermo", "13.361389", "38.115556");
}
#[test]
fn test_radius_options() {
let empty = RadiusOptions::default();
assert_eq!(ToRedisArgs::to_redis_args(&empty).len(), 0);
let opts = RadiusOptions::default;
assert_args!(opts().with_coord().with_dist(), "WITHCOORD", "WITHDIST");
assert_args!(opts().limit(50), "COUNT", "50");
assert_args!(opts().limit(50).store("x"), "COUNT", "50", "STORE", "x");
assert_args!(
opts().limit(100).store_dist("y"),
"COUNT",
"100",
"STOREDIST",
"y"
);
assert_args!(
opts().order(RadiusOrder::Asc).limit(10).with_dist(),
"WITHDIST",
"COUNT",
"10",
"ASC"
);
}
}