use super::resp_memory_writer::RespMemoryWriter;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum RespCommandArgumentType {
#[default]
None,
String,
Integer,
Double,
Key,
Pattern,
UnixTime,
PureToken,
OneOf,
Block,
}
impl RespCommandArgumentType {
pub fn description(&self) -> &'static str {
match self {
Self::None => "None",
Self::String => "string",
Self::Integer => "integer",
Self::Double => "double",
Self::Key => "key",
Self::Pattern => "pattern",
Self::UnixTime => "unix-time",
Self::PureToken => "pure-token",
Self::OneOf => "oneof",
Self::Block => "block",
}
}
pub fn from_member_name(name: &str) -> Option<Self> {
Some(match name.to_ascii_uppercase().as_str() {
"NONE" => Self::None,
"STRING" => Self::String,
"INTEGER" => Self::Integer,
"DOUBLE" => Self::Double,
"KEY" => Self::Key,
"PATTERN" => Self::Pattern,
"UNIXTIME" => Self::UnixTime,
"PURETOKEN" => Self::PureToken,
"ONEOF" => Self::OneOf,
"BLOCK" => Self::Block,
_ => return None,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RespCommandArgumentFlags(u8);
impl RespCommandArgumentFlags {
pub const NONE: Self = Self(0);
pub const OPTIONAL: Self = Self(1);
pub const MULTIPLE: Self = Self(1 << 1);
pub const MULTIPLE_TOKEN: Self = Self(1 << 2);
#[inline]
pub fn is_none(&self) -> bool {
self.0 == 0
}
#[inline]
pub fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub fn descriptions(&self) -> Vec<&'static str> {
[
(Self::OPTIONAL.0, "optional"),
(Self::MULTIPLE.0, "multiple"),
(Self::MULTIPLE_TOKEN.0, "multiple-token"),
]
.iter()
.filter(|(bit, _)| self.0 & bit != 0)
.map(|(_, name)| *name)
.collect()
}
pub fn from_member_name(name: &str) -> Option<Self> {
let mut out = Self::NONE;
for part in name.split(',') {
let bit = match part.trim().to_ascii_uppercase().as_str() {
"NONE" => Self::NONE,
"OPTIONAL" => Self::OPTIONAL,
"MULTIPLE" => Self::MULTIPLE,
"MULTIPLE_TOKEN" | "MULTIPLETOKEN" => Self::MULTIPLE_TOKEN,
_ => return None,
};
out = out.union(bit);
}
Some(out)
}
}
#[derive(Debug, Clone, Default)]
pub struct ArgumentBase {
pub name: String,
pub display_text: Option<String>,
pub argument_type: RespCommandArgumentType,
pub token: Option<String>,
pub summary: Option<String>,
pub argument_flags: RespCommandArgumentFlags,
}
#[derive(Debug, Clone, Default)]
pub enum RespCommandArgument {
Key {
base: ArgumentBase,
value: Option<String>,
key_spec_index: i32,
},
Basic {
base: ArgumentBase,
value: Option<String>,
},
Container {
base: ArgumentBase,
arguments: Option<Vec<RespCommandArgument>>,
},
#[default]
Empty,
}
impl RespCommandArgument {
pub fn can_convert(type_discriminator: &str) -> bool {
matches!(
type_discriminator,
"RespCommandKeyArgument" | "RespCommandBasicArgument" | "RespCommandContainerArgument"
)
}
pub fn to_resp_format(&self, writer: &mut RespMemoryWriter) {
match self {
Self::Key {
base,
key_spec_index,
..
} => {
to_byte_resp_format(base, true, writer);
writer.write_bulk_string(b"key_spec_index");
writer.write_int32(*key_spec_index);
}
Self::Basic { base, value } => {
to_byte_resp_format(base, value.is_some(), writer);
if let Some(value) = value {
writer.write_bulk_string(b"value");
writer.write_ascii_bulk_string(value);
}
}
Self::Container { base, arguments } => {
if let Some(arguments) = arguments {
to_byte_resp_format(base, true, writer);
writer.write_bulk_string(b"arguments");
writer.write_array_length(arguments.len());
for argument in arguments {
argument.to_resp_format(writer);
}
} else {
to_byte_resp_format(base, false, writer);
}
}
Self::Empty => {}
}
}
}
fn to_byte_resp_format(base: &ArgumentBase, increment: bool, writer: &mut RespMemoryWriter) {
let mut arg_count = 2;
if base.display_text.is_some() {
arg_count += 1;
}
if base.token.is_some() {
arg_count += 1;
}
if base.summary.is_some() {
arg_count += 1;
}
if !base.argument_flags.is_none() {
arg_count += 1;
}
if increment {
arg_count += 1;
}
writer.write_map_length(arg_count);
writer.write_bulk_string(b"name");
writer.write_ascii_bulk_string(&base.name);
writer.write_bulk_string(b"type");
writer.write_ascii_bulk_string(base.argument_type.description());
if let Some(display_text) = &base.display_text {
writer.write_bulk_string(b"display_text");
writer.write_ascii_bulk_string(display_text);
}
if let Some(token) = &base.token {
writer.write_bulk_string(b"token");
writer.write_ascii_bulk_string(token);
}
if let Some(summary) = &base.summary {
writer.write_bulk_string(b"summary");
writer.write_ascii_bulk_string(summary);
}
if !base.argument_flags.is_none() {
let resp_format_arg_flags = base.argument_flags.descriptions();
writer.write_bulk_string(b"flags");
writer.write_set_length(resp_format_arg_flags.len());
for resp_arg_flag in resp_format_arg_flags {
writer.write_simple_string(resp_arg_flag);
}
}
}
#[cfg(test)]
mod tests {
use super::{
ArgumentBase, RespCommandArgument, RespCommandArgumentFlags, RespCommandArgumentType,
};
use crate::resp::resp_memory_writer::RespMemoryWriter;
#[test]
fn type_descriptions_and_parse() {
assert_eq!(RespCommandArgumentType::Key.description(), "key");
assert_eq!(RespCommandArgumentType::UnixTime.description(), "unix-time");
assert_eq!(
RespCommandArgumentType::from_member_name("oneof"),
Some(RespCommandArgumentType::OneOf)
);
assert_eq!(RespCommandArgumentType::from_member_name("bad"), None);
}
#[test]
fn flags_descriptions_and_parse() {
let flags = RespCommandArgumentFlags::OPTIONAL.union(RespCommandArgumentFlags::MULTIPLE_TOKEN);
assert_eq!(flags.descriptions(), vec!["optional", "multiple-token"]);
assert_eq!(
RespCommandArgumentFlags::from_member_name("MultipleToken"),
Some(RespCommandArgumentFlags::MULTIPLE_TOKEN)
);
}
#[test]
fn key_argument_resp_format() {
let arg = RespCommandArgument::Key {
base: ArgumentBase {
name: "key".to_string(),
display_text: None,
argument_type: RespCommandArgumentType::Key,
token: None,
summary: None,
argument_flags: RespCommandArgumentFlags::NONE,
},
value: Some("key".to_string()),
key_spec_index: 0,
};
let mut w = RespMemoryWriter::new(true);
arg.to_resp_format(&mut w);
let text = String::from_utf8(w.out).unwrap();
assert!(text.starts_with("%3\r\n"), "{text}");
assert!(text.contains("$14\r\nkey_spec_index\r\n:0\r\n"), "{text}");
}
}