use super::codec::RespValue;
#[derive(Debug)]
pub struct RespCommand {
pub name: String,
pub args: Vec<Vec<u8>>,
}
impl RespCommand {
pub fn parse(value: &RespValue) -> Option<Self> {
let items = match value {
RespValue::Array(Some(items)) if !items.is_empty() => items,
_ => return None,
};
let name = match &items[0] {
RespValue::BulkString(Some(data)) => String::from_utf8_lossy(data).to_uppercase(),
_ => return None,
};
let args: Vec<Vec<u8>> = items[1..]
.iter()
.filter_map(|item| match item {
RespValue::BulkString(Some(data)) => Some(data.clone()),
_ => None,
})
.collect();
Some(Self { name, args })
}
pub fn arg(&self, index: usize) -> Option<&[u8]> {
self.args.get(index).map(|v| v.as_slice())
}
pub fn arg_str(&self, index: usize) -> Option<&str> {
self.args
.get(index)
.and_then(|v| std::str::from_utf8(v).ok())
}
pub fn arg_i64(&self, index: usize) -> Option<i64> {
self.arg_str(index).and_then(|s| s.parse().ok())
}
pub fn argc(&self) -> usize {
self.args.len()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_get_command() {
let value = RespValue::array(vec![
RespValue::bulk_str("get"),
RespValue::bulk_str("mykey"),
]);
let cmd = RespCommand::parse(&value).unwrap();
assert_eq!(cmd.name, "GET");
assert_eq!(cmd.arg(0), Some(b"mykey".as_slice()));
assert_eq!(cmd.argc(), 1);
}
#[test]
fn parse_set_with_ex() {
let value = RespValue::array(vec![
RespValue::bulk_str("SET"),
RespValue::bulk_str("key"),
RespValue::bulk_str("value"),
RespValue::bulk_str("EX"),
RespValue::bulk_str("60"),
]);
let cmd = RespCommand::parse(&value).unwrap();
assert_eq!(cmd.name, "SET");
assert_eq!(cmd.argc(), 4);
assert_eq!(cmd.arg_str(2), Some("EX"));
assert_eq!(cmd.arg_i64(3), Some(60));
}
#[test]
fn parse_empty_array_returns_none() {
let value = RespValue::array(vec![]);
assert!(RespCommand::parse(&value).is_none());
}
#[test]
fn parse_non_array_returns_none() {
let value = RespValue::ok();
assert!(RespCommand::parse(&value).is_none());
}
}