use bytes::Bytes;
pub trait ValkeyWrite {
fn write_arg(&mut self, arg: &[u8]);
fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
self.write_arg(arg.to_string().as_bytes())
}
}
impl ValkeyWrite for Vec<Vec<u8>> {
fn write_arg(&mut self, arg: &[u8]) {
self.push(arg.to_owned());
}
fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
self.push(arg.to_string().into_bytes())
}
}
impl ValkeyWrite for crate::cmd::Cmd {
fn write_arg(&mut self, arg: &[u8]) {
redis::RedisWrite::write_arg(self.as_redis_mut(), arg);
}
fn write_arg_fmt(&mut self, arg: impl std::fmt::Display) {
redis::RedisWrite::write_arg_fmt(self.as_redis_mut(), arg);
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ValkeyNumericBehavior {
NonNumeric,
NumberIsInteger,
NumberIsFloat,
}
pub trait ToValkeyArgs {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W);
fn to_valkey_args(&self) -> Vec<Vec<u8>> {
let mut out = Vec::new();
self.write_valkey_args(&mut out);
out
}
fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
ValkeyNumericBehavior::NonNumeric
}
fn num_of_args(&self) -> usize {
let mut counter = ArgCounter(0);
self.write_valkey_args(&mut counter);
counter.0
}
#[doc(hidden)]
fn num_of_args_from_slice(items: &[Self]) -> usize
where
Self: Sized,
{
items.iter().map(Self::num_of_args).sum()
}
#[doc(hidden)]
fn write_valkey_args_from_slice<W: ?Sized + ValkeyWrite>(items: &[Self], out: &mut W)
where
Self: Sized,
{
Self::write_valkey_args_from_iter(items.iter(), out);
}
#[doc(hidden)]
fn write_valkey_args_from_iter<'a, I, W: ?Sized + ValkeyWrite>(items: I, out: &mut W)
where
I: Iterator<Item = &'a Self>,
Self: Sized + 'a,
{
for item in items {
item.write_valkey_args(out);
}
}
}
macro_rules! impl_to_valkey_args_int {
($($t:ty),* $(,)?) => {$(
impl ToValkeyArgs for $t {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg_fmt(self);
}
fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
ValkeyNumericBehavior::NumberIsInteger
}
}
)*};
}
impl_to_valkey_args_int!(i8, i16, u16, i32, u32, i64, u64, isize, usize);
macro_rules! impl_to_valkey_args_nonzero {
($($t:ty),* $(,)?) => {$(
impl ToValkeyArgs for $t {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg_fmt(self);
}
fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
ValkeyNumericBehavior::NumberIsInteger
}
}
)*};
}
impl_to_valkey_args_nonzero!(
core::num::NonZeroU8,
core::num::NonZeroI8,
core::num::NonZeroU16,
core::num::NonZeroI16,
core::num::NonZeroU32,
core::num::NonZeroI32,
core::num::NonZeroU64,
core::num::NonZeroI64,
core::num::NonZeroUsize,
core::num::NonZeroIsize,
);
macro_rules! impl_to_valkey_args_float {
($($t:ty),* $(,)?) => {$(
impl ToValkeyArgs for $t {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
let mut buf = ryu::Buffer::new();
out.write_arg(buf.format(*self).as_bytes());
}
fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
ValkeyNumericBehavior::NumberIsFloat
}
}
)*};
}
impl_to_valkey_args_float!(f32, f64);
impl ToValkeyArgs for u8 {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg_fmt(self);
}
fn write_valkey_args_from_slice<W: ?Sized + ValkeyWrite>(items: &[u8], out: &mut W) {
out.write_arg(items);
}
fn num_of_args_from_slice(_items: &[u8]) -> usize {
1
}
}
impl ToValkeyArgs for bool {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg(if *self { b"1" } else { b"0" });
}
}
impl ToValkeyArgs for String {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg(self.as_bytes());
}
}
impl ToValkeyArgs for &str {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg(self.as_bytes());
}
}
impl ToValkeyArgs for Bytes {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
out.write_arg(self.as_ref());
}
}
impl<T: ToValkeyArgs> ToValkeyArgs for Vec<T> {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
T::write_valkey_args_from_slice(self, out);
}
fn num_of_args(&self) -> usize {
T::num_of_args_from_slice(self)
}
}
impl<T: ToValkeyArgs> ToValkeyArgs for &[T] {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
T::write_valkey_args_from_slice(self, out);
}
fn num_of_args(&self) -> usize {
T::num_of_args_from_slice(self)
}
}
impl<T: ToValkeyArgs, const N: usize> ToValkeyArgs for &[T; N] {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
T::write_valkey_args_from_slice(self.as_slice(), out);
}
fn num_of_args(&self) -> usize {
T::num_of_args_from_slice(self.as_slice())
}
}
impl<T: ToValkeyArgs> ToValkeyArgs for Option<T> {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
if let Some(x) = self {
x.write_valkey_args(out);
}
}
fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
match self {
Some(x) => x.describe_numeric_behavior(),
None => ValkeyNumericBehavior::NonNumeric,
}
}
fn num_of_args(&self) -> usize {
self.as_ref().map_or(0, ToValkeyArgs::num_of_args)
}
}
impl<T: ToValkeyArgs> ToValkeyArgs for &T {
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
(*self).write_valkey_args(out);
}
fn describe_numeric_behavior(&self) -> ValkeyNumericBehavior {
(*self).describe_numeric_behavior()
}
fn num_of_args(&self) -> usize {
(*self).num_of_args()
}
}
impl<T: ToValkeyArgs + std::cmp::Eq + std::hash::Hash, S: std::hash::BuildHasher> ToValkeyArgs
for std::collections::HashSet<T, S>
{
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
T::write_valkey_args_from_iter(self.iter(), out);
}
}
impl<T: ToValkeyArgs + std::cmp::Eq + std::hash::Hash + Ord> ToValkeyArgs
for std::collections::BTreeSet<T>
{
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
T::write_valkey_args_from_iter(self.iter(), out);
}
}
impl<K: ToValkeyArgs, V: ToValkeyArgs> ToValkeyArgs for std::collections::BTreeMap<K, V>
where
K: std::cmp::Eq + std::hash::Hash + Ord,
{
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
for (key, value) in self {
assert!(key.num_of_args() == 1 && value.num_of_args() == 1);
key.write_valkey_args(out);
value.write_valkey_args(out);
}
}
}
impl<K: ToValkeyArgs, V: ToValkeyArgs, S: std::hash::BuildHasher> ToValkeyArgs
for std::collections::HashMap<K, V, S>
where
K: std::cmp::Eq + std::hash::Hash,
{
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
for (key, value) in self {
assert!(key.num_of_args() == 1 && value.num_of_args() == 1);
key.write_valkey_args(out);
value.write_valkey_args(out);
}
}
}
macro_rules! impl_to_valkey_args_tuple {
($( ($($name:ident),+) ),+ $(,)?) => {$(
#[doc(hidden)]
impl<$($name: ToValkeyArgs),*> ToValkeyArgs for ($($name,)*) {
#[allow(non_snake_case, unused_variables)]
fn write_valkey_args<W: ?Sized + ValkeyWrite>(&self, out: &mut W) {
let ($(ref $name,)*) = *self;
$($name.write_valkey_args(out);)*
}
}
)+};
}
impl_to_valkey_args_tuple! {
(T1),
(T1, T2),
(T1, T2, T3),
(T1, T2, T3, T4),
(T1, T2, T3, T4, T5),
(T1, T2, T3, T4, T5, T6),
(T1, T2, T3, T4, T5, T6, T7),
(T1, T2, T3, T4, T5, T6, T7, T8),
(T1, T2, T3, T4, T5, T6, T7, T8, T9),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11),
(T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12),
}
pub trait ToSingleValkeyArg: ToValkeyArgs {}
macro_rules! impl_to_single_valkey_arg {
($($t:ty),* $(,)?) => {$(
impl ToSingleValkeyArg for $t {}
)*};
}
impl_to_single_valkey_arg!(
i8,
i16,
u16,
i32,
u32,
i64,
u64,
isize,
usize,
core::num::NonZeroU8,
core::num::NonZeroI8,
core::num::NonZeroU16,
core::num::NonZeroI16,
core::num::NonZeroU32,
core::num::NonZeroI32,
core::num::NonZeroU64,
core::num::NonZeroI64,
core::num::NonZeroUsize,
core::num::NonZeroIsize,
f32,
f64,
u8,
bool,
String,
&str,
Bytes,
Vec<u8>,
&[u8],
);
impl<const N: usize> ToSingleValkeyArg for &[u8; N] {}
impl<T: ToSingleValkeyArg> ToSingleValkeyArg for &T {}
struct ArgCounter(usize);
impl ValkeyWrite for ArgCounter {
fn write_arg(&mut self, _arg: &[u8]) {
self.0 += 1;
}
fn write_arg_fmt(&mut self, _arg: impl std::fmt::Display) {
self.0 += 1;
}
}
#[cfg(test)]
mod tests {
use super::*;
const TEXT: &[u8] = b"key";
const BINARY: &[u8] = &[0u8, 1, 2, 255, 0, 42];
const NUMBER: i64 = -7;
#[test]
fn vec_writer() {
let mut out: Vec<Vec<u8>> = Vec::new();
out.write_arg(TEXT);
out.write_arg(BINARY);
out.write_arg_fmt(NUMBER);
assert_eq!(out, vec![TEXT.to_vec(), BINARY.to_vec(), b"-7".to_vec()]);
}
#[test]
fn cmd_writer() {
let mut out = crate::cmd::Cmd::new();
out.write_arg(TEXT);
out.write_arg(BINARY);
out.write_arg_fmt(NUMBER);
assert_eq!(
out.as_redis().get_packed_command(),
crate::cmd::Cmd::new()
.arg(TEXT)
.arg(BINARY)
.arg(NUMBER)
.as_redis()
.get_packed_command()
);
}
}
#[cfg(test)]
mod to_valkey_args_tests {
use super::*;
use crate::test_utils::assert_args;
use crate::test_utils::assert_args_empty;
use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet};
use std::sync::LazyLock;
static HASH_SET_0: LazyLock<HashSet<&str>> = LazyLock::new(HashSet::new);
static HASH_SET_1: LazyLock<HashSet<&str>> = LazyLock::new(|| HashSet::from(["x"]));
static HASH_SET_2: LazyLock<HashSet<&str>> = LazyLock::new(|| HashSet::from(["a", "b", "c"]));
static BTREE_SET_0: LazyLock<BTreeSet<&str>> = LazyLock::new(BTreeSet::new);
static BTREE_SET_1: LazyLock<BTreeSet<&str>> = LazyLock::new(|| BTreeSet::from(["x"]));
static BTREE_SET_2: LazyLock<BTreeSet<&str>> =
LazyLock::new(|| BTreeSet::from(["a", "b", "c"]));
static HASH_MAP_0: LazyLock<HashMap<&str, i64>> = LazyLock::new(HashMap::new);
static HASH_MAP_1: LazyLock<HashMap<&str, i64>> = LazyLock::new(|| HashMap::from([("k", 1)]));
static HASH_MAP_2: LazyLock<HashMap<&str, i64>> =
LazyLock::new(|| HashMap::from([("f1", 1), ("f2", 2)]));
static BTREE_MAP_0: LazyLock<BTreeMap<&str, i64>> = LazyLock::new(BTreeMap::new);
static BTREE_MAP_1: LazyLock<BTreeMap<&str, i64>> =
LazyLock::new(|| BTreeMap::from([("k", 1)]));
static BTREE_MAP_2: LazyLock<BTreeMap<&str, i64>> =
LazyLock::new(|| BTreeMap::from([("f1", 1), ("f2", 2)]));
#[test]
fn to_valkey_args_matches_redis_encoding() {
use redis::ToRedisArgs;
macro_rules! same {
($v:expr) => {
assert_eq!(
$v.to_valkey_args(),
$v.to_redis_args(),
"encoding of {:?}",
$v
);
};
}
same!(1i8);
same!(2i16);
same!(3i32);
same!(4i64);
same!(5isize);
same!(0u8);
same!(255u8);
same!(6u16);
same!(7u32);
same!(8u64);
same!(9usize);
same!(i64::MAX);
same!(i64::MIN);
same!(u64::MAX);
same!(core::num::NonZeroI8::new(-1).unwrap());
same!(core::num::NonZeroI16::new(-2).unwrap());
same!(core::num::NonZeroI32::new(-3).unwrap());
same!(core::num::NonZeroI64::new(-4).unwrap());
same!(core::num::NonZeroIsize::new(-5).unwrap());
same!(core::num::NonZeroU8::new(1).unwrap());
same!(core::num::NonZeroU16::new(2).unwrap());
same!(core::num::NonZeroU32::new(3).unwrap());
same!(core::num::NonZeroU64::new(4).unwrap());
same!(core::num::NonZeroUsize::new(5).unwrap());
same!(1.5f32);
same!(0.0f64);
same!(1.5f64);
same!(-2.25f64);
same!(0.1f64);
same!(1e20f64);
same!(-1e-20f64);
same!(std::f64::consts::PI);
same!(b"raw".to_vec());
same!(&b"raw"[..]);
same!(true);
same!(false);
same!("hello");
same!(String::from("world"));
same!(vec!["a", "b", "c"]);
same!(&["a", "b"][..]);
same!(&["a", "b"]);
same!(&[("f1", 1i64), ("f2", 2i64)][..]);
same!(Some(5i64));
same!(Option::<i64>::None);
same!(&5i64);
same!(&"x");
same!(&*HASH_SET_0);
same!(&*BTREE_SET_0);
same!(&*HASH_MAP_0);
same!(&*BTREE_MAP_0);
same!(&*HASH_SET_1);
same!(&*BTREE_SET_1);
same!(&*HASH_MAP_1);
same!(&*BTREE_MAP_1);
same!(&*HASH_SET_2);
same!(&*BTREE_SET_2);
same!(&*HASH_MAP_2);
same!(&*BTREE_MAP_2);
same!((1,));
same!((1, 2));
same!((1, 2, 3));
same!((1, 2, 3, 4));
same!((1, 2, 3, 4, 5));
same!((1, 2, 3, 4, 5, 6));
same!((1, 2, 3, 4, 5, 6, 7));
same!((1, 2, 3, 4, 5, 6, 7, 8));
same!((1, 2, 3, 4, 5, 6, 7, 8, 9));
same!((1, 2, 3, 4, 5, 6, 7, 8, 9, 10));
same!((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
same!((1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
}
#[test]
fn to_valkey_args_bytes() {
assert_eq!(
Bytes::from_static(b"\x00\xff data").to_valkey_args(),
vec![b"\x00\xff data".to_vec()]
);
}
#[test]
fn num_of_args() {
assert_args(1i8, &["1"]);
assert_args(-1i64, &["-1"]);
assert_args(1usize, &["1"]);
assert_args(core::num::NonZeroU8::new(1).unwrap(), &["1"]);
assert_args(1.5f64, &["1.5"]);
assert_args(b"bytes".to_vec(), &["bytes"]);
assert_args(Vec::<u8>::new(), &[""]);
assert_args(&b"bytes"[..], &["bytes"]);
assert_args(b"bytes", &["bytes"]);
assert_args(vec![0xFFu8, 0x00], &[[0xFFu8, 0x00].as_slice()]);
assert_args(true, &["1"]);
assert_args("k", &["k"]);
assert_args(String::from("k"), &["k"]);
assert_args(Bytes::from_static(b"b"), &["b"]);
assert_args(vec!["one"], &["one"]);
assert_args(vec!["a", "b", "c"], &["a", "b", "c"]);
assert_args_empty(Vec::<&str>::new());
assert_args(&["a", "b"][..], &["a", "b"]);
assert_args(&["a", "b"], &["a", "b"]);
assert_args(vec![vec!["a", "b"], vec!["c"]], &["a", "b", "c"]);
assert_args(&[("f1", 1i64), ("f2", 2i64)][..], &["f1", "1", "f2", "2"]);
assert_args_empty(Option::<i64>::None);
assert_args(Some(1i64), &["1"]);
assert_args(Some(vec!["a", "b"]), &["a", "b"]);
#[allow(clippy::needless_borrows_for_generic_args)]
{
assert_args(&1i64, &["1"]);
assert_args(&&vec!["a", "b"], &["a", "b"]);
}
assert_args_empty(&*HASH_SET_0);
assert_args_empty(&*BTREE_SET_0);
assert_args_empty(&*HASH_MAP_0);
assert_args_empty(&*BTREE_MAP_0);
assert_args(&*HASH_SET_1, &["x"]);
assert_args(&*BTREE_SET_1, &["x"]);
assert_args(&*HASH_MAP_1, &["k", "1"]);
assert_args(&*BTREE_MAP_1, &["k", "1"]);
assert_args(&*BTREE_SET_2, &["a", "b", "c"]);
assert_args(&*BTREE_MAP_2, &["f1", "1", "f2", "2"]);
assert_eq!(HASH_SET_2.to_valkey_args().len(), 3);
assert_eq!(HASH_SET_2.num_of_args(), 3);
assert_eq!(HASH_MAP_2.to_valkey_args().len(), 4);
assert_eq!(HASH_MAP_2.num_of_args(), 4);
assert_args((1i64,), &["1"]);
assert_args((1i64, 2i64), &["1", "2"]);
assert_args(("a", 1, 2.5), &["a", "1", "2.5"]);
assert_args((vec!["a", "b"], "c"), &["a", "b", "c"]);
}
#[test]
fn describe_numeric_behavior() {
use ValkeyNumericBehavior::{NonNumeric, NumberIsFloat, NumberIsInteger};
macro_rules! num {
($v:expr, $b:expr) => {
assert_eq!(
$v.describe_numeric_behavior(),
$b,
"numeric behavior of {:?}",
$v
);
};
}
num!(1i8, NumberIsInteger);
num!(2i16, NumberIsInteger);
num!(3i32, NumberIsInteger);
num!(4i64, NumberIsInteger);
num!(5isize, NumberIsInteger);
num!(6u16, NumberIsInteger);
num!(7u32, NumberIsInteger);
num!(8u64, NumberIsInteger);
num!(9usize, NumberIsInteger);
num!(core::num::NonZeroI8::new(-1).unwrap(), NumberIsInteger);
num!(core::num::NonZeroI16::new(-2).unwrap(), NumberIsInteger);
num!(core::num::NonZeroI32::new(-3).unwrap(), NumberIsInteger);
num!(core::num::NonZeroI64::new(-4).unwrap(), NumberIsInteger);
num!(core::num::NonZeroIsize::new(-5).unwrap(), NumberIsInteger);
num!(core::num::NonZeroU8::new(1).unwrap(), NumberIsInteger);
num!(core::num::NonZeroU16::new(2).unwrap(), NumberIsInteger);
num!(core::num::NonZeroU32::new(3).unwrap(), NumberIsInteger);
num!(core::num::NonZeroU64::new(4).unwrap(), NumberIsInteger);
num!(core::num::NonZeroUsize::new(5).unwrap(), NumberIsInteger);
num!(1.5f32, NumberIsFloat);
num!(1.5f64, NumberIsFloat);
num!(0u8, NonNumeric);
num!(b"raw".to_vec(), NonNumeric);
num!(&b"raw"[..], NonNumeric);
num!(true, NonNumeric);
num!("hello", NonNumeric);
num!(String::from("world"), NonNumeric);
num!(Bytes::from_static(b"b"), NonNumeric);
num!(vec!["a", "b"], NonNumeric);
num!(&["a", "b"][..], NonNumeric);
num!(&["a", "b"], NonNumeric);
num!(Some(5i64), NumberIsInteger);
num!(Some(1.5f64), NumberIsFloat);
num!(Some("x"), NonNumeric);
num!(Option::<f64>::None, NonNumeric);
num!(&5i64, NumberIsInteger);
num!(&1.5f64, NumberIsFloat);
num!(&&5i64, NumberIsInteger);
num!(&"x", NonNumeric);
num!(&*HASH_SET_2, NonNumeric);
num!(&*BTREE_SET_2, NonNumeric);
num!(&*HASH_MAP_2, NonNumeric);
num!(&*BTREE_MAP_2, NonNumeric);
num!((1,), NonNumeric);
num!((1, 2), NonNumeric);
num!((1, 2, 3), NonNumeric);
}
}
#[cfg(test)]
mod to_single_arg {
use super::*;
fn assert_single<T: ToSingleValkeyArg>(value: T) {
assert_eq!(value.num_of_args(), 1);
}
#[test]
fn to_single_valkey_arg_impls_encode_one_arg() {
assert_single(1i8);
assert_single(1i64);
assert_single(1usize);
assert_single(255u8);
assert_single(core::num::NonZeroU64::new(1).unwrap());
assert_single(1.5f32);
assert_single(1.5f64);
assert_single(true);
assert_single("k");
assert_single(String::from("k"));
assert_single(Bytes::from_static(b"b"));
assert_single(b"raw".to_vec());
assert_single(&b"raw"[..]);
assert_single(b"raw");
assert_single(Vec::<u8>::new());
#[allow(clippy::needless_borrows_for_generic_args)]
{
assert_single(&"k");
assert_single(&&5i64);
}
}
}