use crate::error::Result;
use crate::executor::CommandExecutor;
use crate::value;
use async_trait::async_trait;
use redis::{Cmd, ToRedisArgs};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitmapIndexType {
Byte,
Bit,
}
impl BitmapIndexType {
fn as_arg(&self) -> &'static str {
match self {
BitmapIndexType::Byte => "BYTE",
BitmapIndexType::Bit => "BIT",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitEncoding {
Signed(u32),
Unsigned(u32),
}
impl BitEncoding {
fn to_arg(self) -> String {
match self {
BitEncoding::Signed(n) => format!("i{n}"),
BitEncoding::Unsigned(n) => format!("u{n}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitFieldOffset {
Bit(u64),
Multiplier(u64),
}
impl BitFieldOffset {
fn to_arg(self) -> String {
match self {
BitFieldOffset::Bit(n) => n.to_string(),
BitFieldOffset::Multiplier(n) => format!("#{n}"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitOverflow {
Wrap,
Sat,
Fail,
}
impl BitOverflow {
fn as_arg(&self) -> &'static str {
match self {
BitOverflow::Wrap => "WRAP",
BitOverflow::Sat => "SAT",
BitOverflow::Fail => "FAIL",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BitFieldSubcommand {
Get {
encoding: BitEncoding,
offset: BitFieldOffset,
},
Set {
encoding: BitEncoding,
offset: BitFieldOffset,
value: i64,
},
IncrBy {
encoding: BitEncoding,
offset: BitFieldOffset,
increment: i64,
},
Overflow(BitOverflow),
}
impl BitFieldSubcommand {
fn add_to(&self, cmd: &mut Cmd) {
match self {
BitFieldSubcommand::Get { encoding, offset } => {
cmd.arg("GET").arg(encoding.to_arg()).arg(offset.to_arg());
}
BitFieldSubcommand::Set {
encoding,
offset,
value,
} => {
cmd.arg("SET")
.arg(encoding.to_arg())
.arg(offset.to_arg())
.arg(value);
}
BitFieldSubcommand::IncrBy {
encoding,
offset,
increment,
} => {
cmd.arg("INCRBY")
.arg(encoding.to_arg())
.arg(offset.to_arg())
.arg(increment);
}
BitFieldSubcommand::Overflow(o) => {
cmd.arg("OVERFLOW").arg(o.as_arg());
}
}
}
}
#[async_trait]
pub trait BitmapCommands: CommandExecutor {
async fn bitpos<K: ToRedisArgs + Send>(&self, key: K, bit: u8) -> Result<i64> {
let mut cmd = Cmd::new();
cmd.arg("BITPOS").arg(key).arg(bit);
value::to_i64(self.execute_command(cmd, None).await?)
}
async fn bitpos_range<K: ToRedisArgs + Send>(
&self,
key: K,
bit: u8,
start: i64,
end: i64,
index_type: Option<BitmapIndexType>,
) -> Result<i64> {
let mut cmd = Cmd::new();
cmd.arg("BITPOS").arg(key).arg(bit).arg(start).arg(end);
if let Some(it) = index_type {
cmd.arg(it.as_arg());
}
value::to_i64(self.execute_command(cmd, None).await?)
}
async fn bitfield<K: ToRedisArgs + Send>(
&self,
key: K,
subcommands: &[BitFieldSubcommand],
) -> Result<Vec<Option<i64>>> {
let mut cmd = Cmd::new();
cmd.arg("BITFIELD").arg(key);
for sub in subcommands {
sub.add_to(&mut cmd);
}
parse_bitfield(self.execute_command(cmd, None).await?)
}
async fn bitfield_readonly<K: ToRedisArgs + Send>(
&self,
key: K,
subcommands: &[BitFieldSubcommand],
) -> Result<Vec<Option<i64>>> {
let mut cmd = Cmd::new();
cmd.arg("BITFIELD_RO").arg(key);
for sub in subcommands {
sub.add_to(&mut cmd);
}
parse_bitfield(self.execute_command(cmd, None).await?)
}
}
impl<T: CommandExecutor + ?Sized> BitmapCommands for T {}
fn parse_bitfield(v: redis::Value) -> Result<Vec<Option<i64>>> {
match v {
redis::Value::Nil => Ok(Vec::new()),
redis::Value::Array(items) => items
.into_iter()
.map(|it| match it {
redis::Value::Nil => Ok(None),
other => Ok(Some(value::to_i64(other)?)),
})
.collect(),
other => Ok(vec![Some(value::to_i64(other)?)]),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn args_of(cmd: &Cmd) -> Vec<String> {
cmd.args_iter()
.filter_map(|a| match a {
redis::Arg::Simple(bytes) => Some(String::from_utf8_lossy(bytes).into_owned()),
redis::Arg::Cursor => None,
})
.collect()
}
#[test]
fn encoding_and_offset_args() {
assert_eq!(BitEncoding::Signed(8).to_arg(), "i8");
assert_eq!(BitEncoding::Unsigned(16).to_arg(), "u16");
assert_eq!(BitFieldOffset::Bit(5).to_arg(), "5");
assert_eq!(BitFieldOffset::Multiplier(3).to_arg(), "#3");
}
#[test]
fn bitfield_subcommand_args() {
let mut cmd = Cmd::new();
BitFieldSubcommand::Get {
encoding: BitEncoding::Unsigned(8),
offset: BitFieldOffset::Bit(0),
}
.add_to(&mut cmd);
assert_eq!(args_of(&cmd), vec!["GET", "u8", "0"]);
let mut cmd = Cmd::new();
BitFieldSubcommand::Set {
encoding: BitEncoding::Signed(5),
offset: BitFieldOffset::Multiplier(1),
value: 12,
}
.add_to(&mut cmd);
assert_eq!(args_of(&cmd), vec!["SET", "i5", "#1", "12"]);
let mut cmd = Cmd::new();
BitFieldSubcommand::IncrBy {
encoding: BitEncoding::Unsigned(4),
offset: BitFieldOffset::Bit(2),
increment: -3,
}
.add_to(&mut cmd);
assert_eq!(args_of(&cmd), vec!["INCRBY", "u4", "2", "-3"]);
let mut cmd = Cmd::new();
BitFieldSubcommand::Overflow(BitOverflow::Sat).add_to(&mut cmd);
assert_eq!(args_of(&cmd), vec!["OVERFLOW", "SAT"]);
}
#[test]
fn parse_bitfield_handles_nil() {
let v = redis::Value::Array(vec![redis::Value::Int(1), redis::Value::Nil]);
assert_eq!(parse_bitfield(v).unwrap(), vec![Some(1), None]);
}
}