use bytes::Buf;
use crate::error::{RedisError, RedisResult};
use crate::value::Value;
pub fn decode(buf: &[u8]) -> RedisResult<(Value, usize)> {
if buf.is_empty() {
return Err(RedisError::Protocol("Empty buffer".into()));
}
let mut cursor = std::io::Cursor::new(buf);
let value = decode_value(&mut cursor)?;
Ok((value, cursor.position() as usize))
}
fn decode_value(cursor: &mut std::io::Cursor<&[u8]>) -> RedisResult<Value> {
if !cursor.has_remaining() {
return Err(RedisError::Protocol("Unexpected end of buffer".into()));
}
let type_byte = cursor.get_u8();
match type_byte {
b'+' => {
let line = read_line(cursor)?;
Ok(Value::String(line))
}
b'-' => {
let line = read_line(cursor)?;
Ok(Value::Error(line))
}
b':' => {
let line = read_line(cursor)?;
let num: i64 = line.parse().map_err(|_| {
RedisError::Protocol(format!("Invalid integer: {}", line))
})?;
Ok(Value::Integer(num))
}
b'$' => {
let line = read_line(cursor)?;
let len: i64 = line.parse().map_err(|_| {
RedisError::Protocol(format!("Invalid bulk length: {}", line))
})?;
if len < 0 {
return Ok(Value::Null);
}
let len = len as usize;
if cursor.remaining() < len + 2 {
return Err(RedisError::Protocol("Incomplete bulk string".into()));
}
let mut data = vec![0u8; len];
cursor.copy_to_slice(&mut data);
cursor.advance(2);
Ok(Value::Bulk(data))
}
b'*' => {
let line = read_line(cursor)?;
let count: i64 = line.parse().map_err(|_| {
RedisError::Protocol(format!("Invalid array length: {}", line))
})?;
if count < 0 {
return Ok(Value::Null);
}
let mut items = Vec::with_capacity(count as usize);
for _ in 0..count {
items.push(decode_value(cursor)?);
}
Ok(Value::Array(items))
}
b'_' => {
read_line(cursor)?; Ok(Value::Null)
}
b'#' => {
let line = read_line(cursor)?;
match line.as_str() {
"t" => Ok(Value::Boolean(true)),
"f" => Ok(Value::Boolean(false)),
_ => Err(RedisError::Protocol(format!("Invalid boolean: {}", line))),
}
}
b',' => {
let line = read_line(cursor)?;
let num: f64 = line.parse().map_err(|_| {
RedisError::Protocol(format!("Invalid double: {}", line))
})?;
Ok(Value::Double(num))
}
b'%' => {
let line = read_line(cursor)?;
let count: i64 = line.parse().map_err(|_| {
RedisError::Protocol(format!("Invalid map length: {}", line))
})?;
let mut pairs = Vec::with_capacity(count as usize);
for _ in 0..count {
let key = decode_value(cursor)?;
let value = decode_value(cursor)?;
pairs.push((key, value));
}
Ok(Value::Map(pairs))
}
_ => Err(RedisError::Protocol(format!(
"Unknown type byte: {}",
type_byte as char
))),
}
}
fn read_line(cursor: &mut std::io::Cursor<&[u8]>) -> RedisResult<String> {
let start = cursor.position() as usize;
let buf = cursor.get_ref();
let mut end = start;
while end < buf.len() - 1 {
if buf[end] == b'\r' && buf[end + 1] == b'\n' {
let line = std::str::from_utf8(&buf[start..end])
.map_err(|_| RedisError::Protocol("Invalid UTF-8".into()))?;
cursor.set_position((end + 2) as u64);
return Ok(line.to_string());
}
end += 1;
}
Err(RedisError::Protocol("Incomplete line".into()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_decode_simple_string() {
let (value, len) = decode(b"+OK\r\n").unwrap();
assert_eq!(value, Value::String("OK".into()));
assert_eq!(len, 5);
}
#[test]
fn test_decode_error() {
let (value, _) = decode(b"-ERR unknown command\r\n").unwrap();
assert_eq!(value, Value::Error("ERR unknown command".into()));
}
#[test]
fn test_decode_integer() {
let (value, _) = decode(b":1000\r\n").unwrap();
assert_eq!(value, Value::Integer(1000));
}
#[test]
fn test_decode_bulk_string() {
let (value, _) = decode(b"$5\r\nhello\r\n").unwrap();
assert_eq!(value, Value::Bulk(b"hello".to_vec()));
}
#[test]
fn test_decode_null_bulk() {
let (value, _) = decode(b"$-1\r\n").unwrap();
assert_eq!(value, Value::Null);
}
#[test]
fn test_decode_array() {
let (value, _) = decode(b"*2\r\n$3\r\nfoo\r\n$3\r\nbar\r\n").unwrap();
assert_eq!(
value,
Value::Array(vec![
Value::Bulk(b"foo".to_vec()),
Value::Bulk(b"bar".to_vec()),
])
);
}
#[test]
fn test_decode_resp3_boolean() {
let (value, _) = decode(b"#t\r\n").unwrap();
assert_eq!(value, Value::Boolean(true));
}
#[test]
fn test_decode_resp3_null() {
let (value, _) = decode(b"_\r\n").unwrap();
assert_eq!(value, Value::Null);
}
}