shardmap 0.3.2

Sharded embedded in-memory map with optional cache, protocol, and server internals
Documentation
use crate::commands::hexpire::{
    TimeBase, TimeUnit, fields_clause_error, parse_fields_clause, resolve_deadline_ms,
};
use crate::commands::redis::{
    define_redis_command, eq_ignore_ascii_case, error, frame_from_result, parse_i64, wrong_arity,
};
use crate::protocol::Frame;
use crate::storage::{EmbeddedStore, HashFieldGetExpireAction};

define_redis_command!(HGetEx, "HGETEX", true);

impl crate::commands::redis::RedisCommand for HGetEx {
    fn execute(store: &EmbeddedStore, args: &[&[u8]]) -> Frame {
        let [key, rest @ ..] = args else {
            return wrong_arity("HGETEX");
        };
        let (action, fields_tail) = match parse_hgetex_action(rest) {
            Ok(parsed) => parsed,
            Err(frame) => return frame,
        };
        let Some(fields) = parse_fields_clause(fields_tail) else {
            return fields_clause_error();
        };
        frame_from_result(store.hgetex(key, &fields, action))
    }
}

fn parse_hgetex_action<'a>(
    args: &'a [&'a [u8]],
) -> Result<(HashFieldGetExpireAction, &'a [&'a [u8]]), Frame> {
    match args {
        [fields, ..] if eq_ignore_ascii_case(fields, b"FIELDS") => {
            Ok((HashFieldGetExpireAction::Keep, args))
        }
        [option, fields_tail @ ..] if eq_ignore_ascii_case(option, b"PERSIST") => {
            Ok((HashFieldGetExpireAction::Persist, fields_tail))
        }
        [option, raw_ttl, fields_tail @ ..] => {
            let (unit, base) = match *option {
                option if eq_ignore_ascii_case(option, b"EX") => {
                    (TimeUnit::Seconds, TimeBase::Relative)
                }
                option if eq_ignore_ascii_case(option, b"PX") => {
                    (TimeUnit::Millis, TimeBase::Relative)
                }
                option if eq_ignore_ascii_case(option, b"EXAT") => {
                    (TimeUnit::Seconds, TimeBase::Absolute)
                }
                option if eq_ignore_ascii_case(option, b"PXAT") => {
                    (TimeUnit::Millis, TimeBase::Absolute)
                }
                _ => return Ok((HashFieldGetExpireAction::Keep, args)),
            };
            let Ok(ttl) = parse_i64(raw_ttl) else {
                return Err(error("ERR value is not an integer or out of range"));
            };
            let Some(expire_at_ms) = resolve_deadline_ms(ttl, unit, base) else {
                return Err(error("ERR invalid expire time, must be >= 0"));
            };
            Ok((
                HashFieldGetExpireAction::ExpireAt(expire_at_ms),
                fields_tail,
            ))
        }
        _ => Ok((HashFieldGetExpireAction::Keep, args)),
    }
}