shardmap 0.3.2

Sharded embedded in-memory map with optional cache, protocol, and server internals
Documentation
#[cfg(feature = "server")]
use bytes::BytesMut;

#[cfg(feature = "server")]
use crate::commands::redis::write_frame;
use crate::commands::redis::{bulk, define_redis_command, error, int};
use crate::protocol::Frame;
#[cfg(feature = "server")]
use crate::server::wire::{RespProtocolVersion, ServerWire};
use crate::storage::EmbeddedStore;

define_redis_command!(Hello, "HELLO", false);

impl crate::commands::redis::RedisCommand for Hello {
    fn execute(_store: &EmbeddedStore, args: &[&[u8]]) -> Frame {
        match parse_protocol(args) {
            Ok(HelloProtocol::Resp2) => hello_array(2),
            Ok(HelloProtocol::Resp3) => hello_map(),
            Err(HelloError::UnsupportedProtocol) => error("NOPROTO unsupported protocol version"),
            Err(HelloError::Syntax) => error("ERR syntax error"),
        }
    }

    #[cfg(feature = "server")]
    fn write_resp(_store: &EmbeddedStore, args: &[&[u8]], out: &mut BytesMut) {
        match parse_protocol(args) {
            Ok(HelloProtocol::Resp2) => {
                ServerWire::write_resp_hello(out, RespProtocolVersion::Resp2)
            }
            Ok(HelloProtocol::Resp3) => {
                ServerWire::write_resp_hello(out, RespProtocolVersion::Resp3)
            }
            Err(HelloError::UnsupportedProtocol) => {
                write_frame(out, &error("NOPROTO unsupported protocol version"));
            }
            Err(HelloError::Syntax) => write_frame(out, &error("ERR syntax error")),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HelloProtocol {
    Resp2,
    Resp3,
}

impl HelloProtocol {
    fn from_argument(arg: &[u8]) -> std::result::Result<Self, HelloError> {
        match arg {
            b"2" => Ok(Self::Resp2),
            b"3" => Ok(Self::Resp3),
            _ => Err(HelloError::UnsupportedProtocol),
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HelloError {
    UnsupportedProtocol,
    Syntax,
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum HelloOption {
    Auth,
    SetName,
}

impl HelloOption {
    const NAMES: &'static [(&'static [u8], Self)] =
        &[(b"AUTH", Self::Auth), (b"SETNAME", Self::SetName)];

    fn from_name(name: &[u8]) -> Option<Self> {
        Self::NAMES
            .iter()
            .find_map(|(candidate, option)| name.eq_ignore_ascii_case(candidate).then_some(*option))
    }

    fn argument_count(self) -> usize {
        match self {
            Self::Auth => 2,
            Self::SetName => 1,
        }
    }
}

fn parse_protocol(args: &[&[u8]]) -> std::result::Result<HelloProtocol, HelloError> {
    let (protocol, options) = split_protocol_arg(args)?;
    validate_options(options)?;
    Ok(protocol)
}

fn split_protocol_arg<'args, 'value>(
    args: &'args [&'value [u8]],
) -> std::result::Result<(HelloProtocol, &'args [&'value [u8]]), HelloError> {
    match args.split_first() {
        Some((first, rest)) if HelloOption::from_name(first).is_none() => {
            Ok((HelloProtocol::from_argument(first)?, rest))
        }
        _ => Ok((HelloProtocol::Resp2, args)),
    }
}

fn validate_options(mut args: &[&[u8]]) -> std::result::Result<(), HelloError> {
    while let Some((name, rest)) = args.split_first() {
        let option = HelloOption::from_name(name).ok_or(HelloError::Syntax)?;
        args = rest
            .get(option.argument_count()..)
            .ok_or(HelloError::Syntax)?;
    }
    Ok(())
}

fn hello_array(proto: i64) -> Frame {
    Frame::Array(vec![
        bulk(b"server".to_vec()),
        bulk(b"shardcache".to_vec()),
        bulk(b"version".to_vec()),
        bulk(env!("CARGO_PKG_VERSION").as_bytes().to_vec()),
        bulk(b"proto".to_vec()),
        int(proto),
        bulk(b"id".to_vec()),
        int(0),
        bulk(b"mode".to_vec()),
        bulk(b"standalone".to_vec()),
        bulk(b"role".to_vec()),
        bulk(b"master".to_vec()),
        bulk(b"modules".to_vec()),
        hello_modules(),
    ])
}

fn hello_map() -> Frame {
    Frame::Map(vec![
        (bulk(b"server".to_vec()), bulk(b"shardcache".to_vec())),
        (
            bulk(b"version".to_vec()),
            bulk(env!("CARGO_PKG_VERSION").as_bytes().to_vec()),
        ),
        (bulk(b"proto".to_vec()), int(3)),
        (bulk(b"id".to_vec()), int(0)),
        (bulk(b"mode".to_vec()), bulk(b"standalone".to_vec())),
        (bulk(b"role".to_vec()), bulk(b"master".to_vec())),
        (bulk(b"modules".to_vec()), hello_modules()),
    ])
}

fn hello_modules() -> Frame {
    #[cfg(feature = "redis-modules")]
    {
        crate::commands::redis_modules::module_list_frame()
    }
    #[cfg(not(feature = "redis-modules"))]
    {
        Frame::Array(Vec::new())
    }
}