shardmap 0.3.2

Sharded embedded in-memory map with optional cache, protocol, and server internals
Documentation
use bytes::BytesMut;

use crate::commands::redis::{
    define_redis_command, error, int, parse_i64, write_frame, wrong_arity, wrongtype,
};
use crate::commands::string_bits::{BitPosSpec, BitRangeUnit, find_bit_position, parse_bit_value};
use crate::protocol::Frame;
#[cfg(feature = "server")]
use crate::server::wire::ServerWire;
use crate::storage::{EmbeddedStore, RedisStringLookup};

define_redis_command!(BitPos, "BITPOS", false);

impl crate::commands::redis::RedisCommand for BitPos {
    fn execute(store: &EmbeddedStore, args: &[&[u8]]) -> Frame {
        match parse_bitpos_args(args) {
            Ok((key, spec)) => match bitpos_value(store, key, spec) {
                Ok(value) => int(value),
                Err(()) => wrongtype(),
            },
            Err(frame) => frame,
        }
    }

    #[cfg(feature = "server")]
    fn write_resp(store: &EmbeddedStore, args: &[&[u8]], out: &mut BytesMut) {
        match parse_bitpos_args(args) {
            Ok((key, spec)) => match bitpos_value(store, key, spec) {
                Ok(value) => ServerWire::write_resp_integer(out, value),
                Err(()) => write_frame(out, &wrongtype()),
            },
            Err(frame) => write_frame(out, &frame),
        }
    }
}

fn parse_bitpos_args<'a>(
    args: &'a [&'a [u8]],
) -> std::result::Result<(&'a [u8], BitPosSpec), Frame> {
    let (key, bit, start, stop, unit) = match args {
        [key, bit] => (*key, *bit, None, None, BitRangeUnit::Byte),
        [key, bit, start] => (*key, *bit, Some(*start), None, BitRangeUnit::Byte),
        [key, bit, start, stop] => (*key, *bit, Some(*start), Some(*stop), BitRangeUnit::Byte),
        [key, bit, start, stop, unit] => {
            let Some(unit) = BitRangeUnit::parse(unit) else {
                return Err(error("ERR syntax error"));
            };
            (*key, *bit, Some(*start), Some(*stop), unit)
        }
        _ => return Err(wrong_arity("BITPOS")),
    };
    let Ok(bit) = parse_bit_value(bit) else {
        return Err(error("ERR bit is not an integer or out of range"));
    };
    let start = match start {
        Some(start) => match parse_i64(start) {
            Ok(start) => Some(start),
            Err(_) => return Err(error("ERR value is not an integer or out of range")),
        },
        None => None,
    };
    let stop = match stop {
        Some(stop) => match parse_i64(stop) {
            Ok(stop) => Some(stop),
            Err(_) => return Err(error("ERR value is not an integer or out of range")),
        },
        None => None,
    };
    Ok((
        key,
        BitPosSpec {
            bit,
            start,
            stop,
            unit,
        },
    ))
}

fn bitpos_value(
    store: &EmbeddedStore,
    key: &[u8],
    spec: BitPosSpec,
) -> std::result::Result<i64, ()> {
    let mut position = if spec.bit { -1 } else { 0 };
    match store.get_string_value_into(key, |bytes| {
        position = find_bit_position(bytes, spec.bit, spec.start, spec.stop, spec.unit);
    }) {
        RedisStringLookup::Hit => Ok(position),
        RedisStringLookup::Miss => Ok(position),
        RedisStringLookup::WrongType => Err(()),
    }
}