Skip to content

Instantly share code, notes, and snippets.

@zew13
Created January 12, 2023 07:38
Show Gist options
  • Select an option

  • Save zew13/86c1845d0640afed439bd509bef39cf2 to your computer and use it in GitHub Desktop.

Select an option

Save zew13/86c1845d0640afed439bd509bef39cf2 to your computer and use it in GitHub Desktop.
use crate::{
error::{RedisError, RedisErrorKind},
types::{RedisKey, RedisValue, NIL, QUEUED},
utils,
};
use bytes::Bytes;
use bytes_utils::Str;
use std::{
collections::{BTreeMap, BTreeSet, HashMap, HashSet},
hash::{BuildHasher, Hash},
};
#[cfg(feature = "serde-json")]
use serde_json::{Map, Value};
macro_rules! debug_type(
($($arg:tt)*) => {
cfg_if::cfg_if! {
if #[cfg(feature="network-logs")] {
log::trace!($($arg)*);
}
}
}
);
macro_rules! to_signed_number(
($t:ty, $v:expr) => {
match $v {
RedisValue::Double(f) => Ok(f as $t),
RedisValue::Integer(i) => Ok(i as $t),
RedisValue::String(s) => s.parse::<$t>().map_err(|e| e.into()),
RedisValue::Null => Err(RedisError::new(RedisErrorKind::NotFound, "Cannot convert nil to number.")),
RedisValue::Array(mut a) => if a.len() == 1 {
match a.pop().unwrap() {
RedisValue::Integer(i) => Ok(i as $t),
RedisValue::String(s) => s.parse::<$t>().map_err(|e| e.into()),
RedisValue::Null => Err(RedisError::new(RedisErrorKind::NotFound, "Cannot convert nil to number.")),
_ => Err(RedisError::new_parse("Cannot convert to number."))
}
}else{
Err(RedisError::new_parse("Cannot convert array to number."))
}
_ => Err(RedisError::new_parse("Cannot convert to number.")),
}
}
);
macro_rules! to_unsigned_number(
($t:ty, $v:expr) => {
match $v {
RedisValue::Double(f) => if f.is_sign_negative() {
Err(RedisError::new_parse("Cannot convert from negative number."))
}else{
Ok(f as $t)
},
RedisValue::Integer(i) => if i < 0 {
Err(RedisError::new_parse("Cannot convert from negative number."))
}else{
Ok(i as $t)
},
RedisValue::String(s) => s.parse::<$t>().map_err(|e| e.into()),
RedisValue::Array(mut a) => if a.len() == 1 {
match a.pop().unwrap() {
RedisValue::Integer(i) => if i < 0 {
Err(RedisError::new_parse("Cannot convert from negative number."))
}else{
Ok(i as $t)
},
RedisValue::Null => Err(RedisError::new(RedisErrorKind::NotFound, "Cannot convert nil to number.")),
RedisValue::String(s) => s.parse::<$t>().map_err(|e| e.into()),
_ => Err(RedisError::new_parse("Cannot convert to number."))
}
}else{
Err(RedisError::new_parse("Cannot convert array to number."))
},
RedisValue::Null => Err(RedisError::new(RedisErrorKind::NotFound, "Cannot convert nil to number.")),
_ => Err(RedisError::new_parse("Cannot convert to number.")),
}
}
);
macro_rules! impl_signed_number (
($t:ty) => {
impl FromRedis for $t {
fn from_value(value: RedisValue) -> Result<$t, RedisError> {
to_signed_number!($t, value)
}
}
}
);
macro_rules! impl_unsigned_number (
($t:ty) => {
impl FromRedis for $t {
fn from_value(value: RedisValue) -> Result<$t, RedisError> {
to_unsigned_number!($t, value)
}
}
}
);
/// A trait used to convert various forms of [RedisValue](crate::types::RedisValue) into different types.
///
/// See the [convert](crate::types::RedisValue::convert) documentation for important information regarding performance
/// considerations and examples.
pub trait FromRedis: Sized {
fn from_value(value: RedisValue) -> Result<Self, RedisError>;
#[doc(hidden)]
fn from_values(values: Vec<RedisValue>) -> Result<Vec<Self>, RedisError> {
values.into_iter().map(|v| Self::from_value(v)).collect()
}
#[doc(hidden)]
// FIXME if/when specialization is stable
fn from_owned_bytes(_: Vec<u8>) -> Option<Vec<Self>> {
None
}
#[doc(hidden)]
fn is_tuple() -> bool {
false
}
}
impl FromRedis for RedisValue {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
Ok(value)
}
}
impl FromRedis for () {
fn from_value(_: RedisValue) -> Result<Self, RedisError> {
Ok(())
}
}
impl_signed_number!(i8);
impl_signed_number!(i16);
impl_signed_number!(i32);
impl_signed_number!(i64);
impl_signed_number!(i128);
impl_signed_number!(isize);
impl FromRedis for u8 {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
to_unsigned_number!(u8, value)
}
fn from_owned_bytes(d: Vec<u8>) -> Option<Vec<Self>> {
Some(d)
}
}
impl_unsigned_number!(u16);
impl_unsigned_number!(u32);
impl_unsigned_number!(u64);
impl_unsigned_number!(u128);
impl_unsigned_number!(usize);
impl FromRedis for String {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(String): {:?}", value);
if value.is_null() {
Ok(NIL.to_owned())
} else {
value
.into_string()
.ok_or(RedisError::new_parse("Could not convert to string."))
}
}
}
impl FromRedis for Str {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(Str): {:?}", value);
if value.is_null() {
Ok(utils::static_str(NIL))
} else {
value
.into_bytes_str()
.ok_or(RedisError::new_parse("Could not convert to string."))
}
}
}
impl FromRedis for f64 {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(f64): {:?}", value);
if value.is_null() {
Err(RedisError::new(
RedisErrorKind::NotFound,
"Cannot convert nil response to double.",
))
} else {
value
.as_f64()
.ok_or(RedisError::new_parse("Could not convert to double."))
}
}
}
impl FromRedis for f32 {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(f32): {:?}", value);
if value.is_null() {
Err(RedisError::new(
RedisErrorKind::NotFound,
"Cannot convert nil response to float.",
))
} else {
value
.as_f64()
.map(|f| f as f32)
.ok_or(RedisError::new_parse("Could not convert to float."))
}
}
}
impl FromRedis for bool {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(bool): {:?}", value);
if value.is_null() {
Err(RedisError::new(
RedisErrorKind::NotFound,
"Cannot convert nil response to bool.",
))
} else {
value
.as_bool()
.ok_or(RedisError::new_parse("Could not convert to bool."))
}
}
}
impl<T> FromRedis for Option<T>
where
T: FromRedis,
{
fn from_value(value: RedisValue) -> Result<Option<T>, RedisError> {
debug_type!("FromRedis(Option<T>): {:?}", value);
if value.is_null() {
Ok(None)
} else {
Ok(Some(T::from_value(value)?))
}
}
}
impl FromRedis for Bytes {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(Bytes): {:?}", value);
value
.into_bytes()
.ok_or(RedisError::new_parse("Cannot parse into bytes."))
}
}
impl<T> FromRedis for Vec<T>
where
T: FromRedis,
{
fn from_value(value: RedisValue) -> Result<Vec<T>, RedisError> {
debug_type!("FromRedis(Vec<T>): {:?}", value);
match value {
RedisValue::Bytes(bytes) => {
T::from_owned_bytes(bytes.to_vec()).ok_or(RedisError::new_parse("Cannot convert from bytes"))
},
RedisValue::String(string) => {
// hacky way to check if T is bytes without consuming `string`
if T::from_owned_bytes(vec![]).is_some() {
T::from_owned_bytes(string.into_inner().to_vec())
.ok_or(RedisError::new_parse("Could not convert string to bytes."))
} else {
Ok(vec![T::from_value(RedisValue::String(string))?])
}
},
RedisValue::Array(values) => {
if values.len() > 0 {
if let RedisValue::Array(_) = &values[0] {
values.into_iter().map(|x| T::from_value(x)).collect()
} else {
T::from_values(values)
}
} else {
Ok(vec![])
}
},
RedisValue::Map(map) => {
// not being able to use collect() here is unfortunate
let out = Vec::with_capacity(map.len() * 2);
map.inner().into_iter().fold(Ok(out), |out, (key, value)| {
out.and_then(|mut out| {
if T::is_tuple() {
// try to convert to a 2-element tuple since that's a common use case from `HGETALL`, etc
out.push(T::from_value(RedisValue::Array(vec![key.into(), value]))?);
} else {
out.push(T::from_value(key.into())?);
out.push(T::from_value(value)?);
}
Ok(out)
})
})
},
RedisValue::Null => Ok(vec![]),
RedisValue::Integer(i) => Ok(vec![T::from_value(RedisValue::Integer(i))?]),
RedisValue::Double(f) => Ok(vec![T::from_value(RedisValue::Double(f))?]),
RedisValue::Boolean(b) => Ok(vec![T::from_value(RedisValue::Boolean(b))?]),
RedisValue::Queued => Ok(vec![T::from_value(RedisValue::from_static_str(QUEUED))?]),
}
}
}
impl<K, V, S> FromRedis for HashMap<K, V, S>
where
K: FromRedisKey + Eq + Hash,
V: FromRedis,
S: BuildHasher + Default,
{
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(HashMap<K,V>): {:?}", value);
if value.is_null() {
return Err(RedisError::new(RedisErrorKind::NotFound, "Cannot convert nil to map."));
}
let as_map = if value.is_array() || value.is_map() {
value
.into_map()
.map_err(|_| RedisError::new_parse("Cannot convert to map."))?
} else {
return Err(RedisError::new_parse("Cannot convert to map."));
};
debug_type!("FromRedis(HashMap<K,V>) Map: {:?}", as_map);
as_map
.inner()
.into_iter()
.map(|(k, v)| Ok((K::from_key(k)?, V::from_value(v)?)))
.collect()
}
}
impl<V, S> FromRedis for HashSet<V, S>
where
V: FromRedis + Hash + Eq,
S: BuildHasher + Default,
{
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(HashSet<V>): {:?}", value);
value.into_array().into_iter().map(|v| V::from_value(v)).collect()
}
}
impl<K, V> FromRedis for BTreeMap<K, V>
where
K: FromRedisKey + Ord,
V: FromRedis,
{
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(BTreeMap<K,V>): {:?}", value);
let as_map = if value.is_array() || value.is_map() {
value
.into_map()
.map_err(|_| RedisError::new_parse("Cannot convert to map."))?
} else {
return Err(RedisError::new_parse("Cannot convert to map."));
};
as_map
.inner()
.into_iter()
.map(|(k, v)| Ok((K::from_key(k)?, V::from_value(v)?)))
.collect()
}
}
impl<V> FromRedis for BTreeSet<V>
where
V: FromRedis + Ord,
{
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
debug_type!("FromRedis(BTreeSet<V>): {:?}", value);
value.into_array().into_iter().map(|v| V::from_value(v)).collect()
}
}
// adapted from mitsuhiko
macro_rules! impl_from_redis_tuple {
() => ();
($($name:ident,)+) => (
#[doc(hidden)]
impl<$($name: FromRedis),*> FromRedis for ($($name,)*) {
fn is_tuple() -> bool {
true
}
#[allow(non_snake_case, unused_variables)]
fn from_value(v: RedisValue) -> Result<($($name,)*), RedisError> {
if let RedisValue::Array(mut values) = v {
let mut n = 0;
$(let $name = (); n += 1;)*
debug_type!("FromRedis({}-tuple): {:?}", n, values);
if values.len() != n {
return Err(RedisError::new_parse("Invalid tuple dimension."));
}
// since we have ownership over the values we have some freedom in how to implement this
values.reverse();
Ok(($({let $name = (); values
.pop()
.ok_or(RedisError::new_parse("Expected value, found none."))?
.convert()?
},)*))
}else{
Err(RedisError::new_parse("Could not convert to tuple."))
}
}
#[allow(non_snake_case, unused_variables)]
fn from_values(mut values: Vec<RedisValue>) -> Result<Vec<($($name,)*)>, RedisError> {
let mut n = 0;
$(let $name = (); n += 1;)*
debug_type!("FromRedis({}-tuple): {:?}", n, values);
if values.len() % n != 0 {
return Err(RedisError::new_parse("Invalid tuple dimension."))
}
let mut out = Vec::with_capacity(values.len() / n);
// this would be cleaner if there were an owned `chunks` variant
for chunk in values.chunks_exact_mut(n) {
match chunk {
[$($name),*] => out.push(($($name.take().convert()?),*),),
_ => unreachable!(),
}
}
Ok(out)
}
}
impl_from_redis_peel!($($name,)*);
)
}
macro_rules! impl_from_redis_peel {
($name:ident, $($other:ident,)*) => (impl_from_redis_tuple!($($other,)*);)
}
impl_from_redis_tuple! { T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, }
macro_rules! impl_from_str_from_redis_key (
($t:ty) => {
impl FromRedisKey for $t {
fn from_key(value: RedisKey) -> Result<$t, RedisError> {
value
.as_str()
.and_then(|k| k.parse::<$t>().ok())
.ok_or(RedisError::new_parse("Cannot parse key from bytes."))
}
}
}
);
#[cfg(feature = "serde-json")]
#[cfg_attr(docsrs, doc(cfg(feature = "serde-json")))]
impl FromRedis for Value {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
let value = match value {
RedisValue::Null => Value::Null,
RedisValue::Queued => QUEUED.into(),
RedisValue::String(s) => {
if let Some(parsed) = utils::parse_nested_json(&s) {
parsed
} else {
s.to_string().into()
}
},
RedisValue::Bytes(b) => String::from_utf8(b.to_vec())?.into(),
RedisValue::Integer(i) => i.into(),
RedisValue::Double(f) => f.into(),
RedisValue::Boolean(b) => b.into(),
RedisValue::Array(v) => {
let mut out = Vec::with_capacity(v.len());
for value in v.into_iter() {
out.push(Self::from_value(value)?);
}
Value::Array(out)
},
RedisValue::Map(v) => {
let mut out = Map::with_capacity(v.len());
for (key, value) in v.inner().into_iter() {
let key = key
.into_string()
.ok_or(RedisError::new_parse("Cannot convert key to string."))?;
let value = Self::from_value(value)?;
out.insert(key, value);
}
Value::Object(out)
},
};
Ok(value)
}
}
impl FromRedis for RedisKey {
fn from_value(value: RedisValue) -> Result<Self, RedisError> {
let key = match value {
RedisValue::Boolean(b) => b.into(),
RedisValue::Integer(i) => i.into(),
RedisValue::Double(f) => f.into(),
RedisValue::String(s) => s.into(),
RedisValue::Bytes(b) => b.into(),
RedisValue::Queued => RedisKey::from_static_str(QUEUED),
RedisValue::Map(_) | RedisValue::Array(_) => {
return Err(RedisError::new_parse("Cannot convert aggregate type to key."))
},
RedisValue::Null => return Err(RedisError::new(RedisErrorKind::NotFound, "Cannot convert nil to key.")),
};
Ok(key)
}
}
/// A trait used to convert [RedisKey](crate::types::RedisKey) values to various types.
///
/// See the [convert](crate::types::RedisKey::convert) documentation for more information.
pub trait FromRedisKey: Sized {
fn from_key(value: RedisKey) -> Result<Self, RedisError>;
}
impl_from_str_from_redis_key!(u8);
impl_from_str_from_redis_key!(u16);
impl_from_str_from_redis_key!(u32);
impl_from_str_from_redis_key!(u64);
impl_from_str_from_redis_key!(u128);
impl_from_str_from_redis_key!(usize);
impl_from_str_from_redis_key!(i8);
impl_from_str_from_redis_key!(i16);
impl_from_str_from_redis_key!(i32);
impl_from_str_from_redis_key!(i64);
impl_from_str_from_redis_key!(i128);
impl_from_str_from_redis_key!(isize);
impl_from_str_from_redis_key!(f32);
impl_from_str_from_redis_key!(f64);
impl FromRedisKey for () {
fn from_key(_: RedisKey) -> Result<Self, RedisError> {
Ok(())
}
}
impl FromRedisKey for RedisValue {
fn from_key(value: RedisKey) -> Result<Self, RedisError> {
Ok(RedisValue::Bytes(value.into_bytes()))
}
}
impl FromRedisKey for RedisKey {
fn from_key(value: RedisKey) -> Result<Self, RedisError> {
Ok(value)
}
}
impl FromRedisKey for String {
fn from_key(value: RedisKey) -> Result<Self, RedisError> {
value
.into_string()
.ok_or(RedisError::new_parse("Cannot parse key as string."))
}
}
impl FromRedisKey for Str {
fn from_key(value: RedisKey) -> Result<Self, RedisError> {
Ok(Str::from_inner(value.into_bytes())?)
}
}
impl FromRedisKey for Vec<u8> {
fn from_key(value: RedisKey) -> Result<Self, RedisError> {
Ok(value.into_bytes().to_vec())
}
}
impl FromRedisKey for Bytes {
fn from_key(value: RedisKey) -> Result<Self, RedisError> {
Ok(value.into_bytes())
}
}
#[cfg(test)]
mod tests {
use crate::{error::RedisError, types::RedisValue};
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
#[test]
fn should_convert_null() {
let _foo: () = RedisValue::Null.convert().unwrap();
}
#[test]
fn should_convert_signed_numeric_types() {
let _foo: i8 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i8 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i16 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i16 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i32 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i32 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i64 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i64 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i128 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: i128 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: isize = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: isize = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: f32 = RedisValue::String("123.5".into()).convert().unwrap();
assert_eq!(_foo, 123.5);
let _foo: f64 = RedisValue::String("123.5".into()).convert().unwrap();
assert_eq!(_foo, 123.5);
}
#[test]
fn should_convert_unsigned_numeric_types() {
let _foo: u8 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u8 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u16 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u16 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u32 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u32 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u64 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u64 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u128 = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: u128 = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: usize = RedisValue::String("123".into()).convert().unwrap();
assert_eq!(_foo, 123);
let _foo: usize = RedisValue::Integer(123).convert().unwrap();
assert_eq!(_foo, 123);
}
#[test]
fn should_return_not_found_with_null_scalar_values() {
let result: Result<u8, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<u16, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<u32, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<u64, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<u128, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<usize, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<i8, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<i16, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<i32, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<i64, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<i128, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
let result: Result<isize, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
}
#[test]
fn should_return_not_found_with_null_strings_and_bools() {
let result: Result<bool, RedisError> = RedisValue::Null.convert();
assert!(result.unwrap_err().is_not_found());
}
#[test]
fn should_convert_strings() {
let _foo: String = RedisValue::String("foo".into()).convert().unwrap();
assert_eq!(_foo, "foo".to_owned());
}
#[test]
fn should_convert_bools() {
let _foo: bool = RedisValue::Integer(0).convert().unwrap();
assert_eq!(_foo, false);
let _foo: bool = RedisValue::Integer(1).convert().unwrap();
assert_eq!(_foo, true);
let _foo: bool = RedisValue::String("0".into()).convert().unwrap();
assert_eq!(_foo, false);
let _foo: bool = RedisValue::String("1".into()).convert().unwrap();
assert_eq!(_foo, true);
}
#[test]
fn should_convert_bytes() {
let _foo: Vec<u8> = RedisValue::Bytes("foo".as_bytes().to_vec().into()).convert().unwrap();
assert_eq!(_foo, "foo".as_bytes().to_vec());
let _foo: Vec<u8> = RedisValue::String("foo".into()).convert().unwrap();
assert_eq!(_foo, "foo".as_bytes().to_vec());
let _foo: Vec<u8> = RedisValue::Array(vec![102.into(), 111.into(), 111.into()])
.convert()
.unwrap();
assert_eq!(_foo, "foo".as_bytes().to_vec());
}
#[test]
fn should_convert_arrays() {
let foo: Vec<String> = RedisValue::Array(vec!["a".into(), "b".into()]).convert().unwrap();
assert_eq!(foo, vec!["a".to_owned(), "b".to_owned()]);
}
#[test]
fn should_convert_hash_maps() {
let foo: HashMap<String, u16> = RedisValue::Array(vec!["a".into(), 1.into(), "b".into(), 2.into()])
.convert()
.unwrap();
let mut expected = HashMap::new();
expected.insert("a".to_owned(), 1);
expected.insert("b".to_owned(), 2);
assert_eq!(foo, expected);
}
#[test]
fn should_convert_hash_sets() {
let foo: HashSet<String> = RedisValue::Array(vec!["a".into(), "b".into()]).convert().unwrap();
let mut expected = HashSet::new();
expected.insert("a".to_owned());
expected.insert("b".to_owned());
assert_eq!(foo, expected);
}
#[test]
fn should_convert_btree_maps() {
let foo: BTreeMap<String, u16> = RedisValue::Array(vec!["a".into(), 1.into(), "b".into(), 2.into()])
.convert()
.unwrap();
let mut expected = BTreeMap::new();
expected.insert("a".to_owned(), 1);
expected.insert("b".to_owned(), 2);
assert_eq!(foo, expected);
}
#[test]
fn should_convert_btree_sets() {
let foo: BTreeSet<String> = RedisValue::Array(vec!["a".into(), "b".into()]).convert().unwrap();
let mut expected = BTreeSet::new();
expected.insert("a".to_owned());
expected.insert("b".to_owned());
assert_eq!(foo, expected);
}
#[test]
fn should_convert_tuples() {
let foo: (String, i64) = RedisValue::Array(vec!["a".into(), 1.into()]).convert().unwrap();
assert_eq!(foo, ("a".to_owned(), 1));
}
#[test]
fn should_convert_array_tuples() {
let foo: Vec<(String, i64)> = RedisValue::Array(vec!["a".into(), 1.into(), "b".into(), 2.into()])
.convert()
.unwrap();
assert_eq!(foo, vec![("a".to_owned(), 1), ("b".to_owned(), 2)]);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment