use crate::error::ProtocolError;
use crate::request::{find_crlf, parse_int};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Reply {
Simple(Vec<u8>),
Error(Vec<u8>),
Int(i64),
Bulk(Vec<u8>),
Nil,
Array(Vec<Reply>),
}
pub fn parse_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(&tag) = buf.first() else {
return Ok(None);
};
match tag {
b'+' => Ok(reply_line(buf).map(|(b, used)| (Reply::Simple(b.to_vec()), used))),
b'-' => Ok(reply_line(buf).map(|(b, used)| (Reply::Error(b.to_vec()), used))),
b':' => match reply_line(buf) {
None => Ok(None),
Some((b, used)) => {
let n = parse_int(b).ok_or(ProtocolError::Malformed("bad integer reply"))?;
Ok(Some((Reply::Int(n), used)))
}
},
b'$' => parse_bulk_reply(buf),
b'*' => parse_array_reply(buf),
_ => Err(ProtocolError::Malformed("unknown reply type")),
}
}
fn reply_line(buf: &[u8]) -> Option<(&[u8], usize)> {
find_crlf(buf, 1).map(|eol| (&buf[1..eol], eol + 2))
}
fn parse_bulk_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let len = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad bulk length"))?;
if len < 0 {
return Ok(Some((Reply::Nil, hdr_end + 2)));
}
let data_start = hdr_end + 2;
let data_end = data_start + len as usize;
if buf.len() < data_end + 2 {
return Ok(None);
}
Ok(Some((
Reply::Bulk(buf[data_start..data_end].to_vec()),
data_end + 2,
)))
}
fn parse_array_reply(buf: &[u8]) -> Result<Option<(Reply, usize)>, ProtocolError> {
let Some(hdr_end) = find_crlf(buf, 1) else {
return Ok(None);
};
let count = parse_int(&buf[1..hdr_end]).ok_or(ProtocolError::Malformed("bad array length"))?;
if count < 0 {
return Ok(Some((Reply::Nil, hdr_end + 2)));
}
let mut pos = hdr_end + 2;
let cap = (count as usize).min(buf.len().saturating_sub(pos));
let mut items = Vec::with_capacity(cap);
for _ in 0..count {
match parse_reply(&buf[pos..])? {
None => return Ok(None),
Some((r, used)) => {
items.push(r);
pos += used;
}
}
}
Ok(Some((Reply::Array(items), pos)))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_replies() {
let r = |b: &[u8]| parse_reply(b).unwrap().unwrap().0;
assert_eq!(r(b"+OK\r\n"), Reply::Simple(b"OK".to_vec()));
assert_eq!(r(b"-ERR bad\r\n"), Reply::Error(b"ERR bad".to_vec()));
assert_eq!(r(b":42\r\n"), Reply::Int(42));
assert_eq!(r(b"$5\r\nhello\r\n"), Reply::Bulk(b"hello".to_vec()));
assert_eq!(r(b"$-1\r\n"), Reply::Nil);
assert_eq!(r(b"*-1\r\n"), Reply::Nil);
let (arr, used) = parse_reply(b"*2\r\n:1\r\n$2\r\nhi\r\n").unwrap().unwrap();
assert_eq!(
arr,
Reply::Array(vec![Reply::Int(1), Reply::Bulk(b"hi".to_vec())])
);
assert_eq!(used, 16);
assert_eq!(parse_reply(b"$5\r\nhel").unwrap(), None);
assert_eq!(parse_reply(b"*2\r\n:1\r\n").unwrap(), None);
assert!(parse_reply(b"!nope\r\n").is_err());
}
}