use crate::{
Error, Result,
read::{try_read_ptr_with_signed_length_header, try_read_unsigned_array_length},
};
pub type ParsedFrame<'a> = (usize, Vec<&'a [u8]>);
pub fn parse_resp_frame(buffer: &[u8]) -> Result<Option<ParsedFrame<'_>>> {
if buffer.is_empty() {
return Ok(None);
}
let mut ptr = buffer;
if buffer[0] == b'*' {
let mut count = 0;
if !try_read_unsigned_array_length(&mut count, &mut ptr)? {
return Ok(None);
}
let count = count as usize;
let mut args = Vec::with_capacity(count.min(1024));
for _ in 0..count {
let mut span = None;
if !try_read_ptr_with_signed_length_header(&mut span, &mut ptr)? {
return Ok(None);
}
let Some(span) = span else {
return Err(Error::InvalidStringLength(-1));
};
args.push(span);
}
let consumed = buffer.len() - ptr.len();
Ok(Some((consumed, args)))
} else {
let Some(newline) = buffer.iter().position(|&b| b == b'\n') else {
return Ok(None);
};
let line = &buffer[..newline];
let line = line.strip_suffix(b"\r").unwrap_or(line);
let args: Vec<&[u8]> = line
.split(|&b| b == b' ' || b == b'\t')
.filter(|s| !s.is_empty())
.collect();
Ok(Some((newline + 1, args)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_resp_array() {
let raw = b"*2\r\n$4\r\nECHO\r\n$5\r\nHELLO\r\n";
let (consumed, args) = parse_resp_frame(raw)
.expect("应当解析成功")
.expect("完整帧");
assert_eq!(consumed, raw.len());
assert_eq!(args, vec![&b"ECHO"[..], &b"HELLO"[..]]);
}
#[test]
fn test_parse_resp_inline() {
let raw = b"PING\r\n";
let (consumed, args) = parse_resp_frame(raw)
.expect("应当解析成功")
.expect("完整帧");
assert_eq!(consumed, raw.len());
assert_eq!(args, vec![&b"PING"[..]]);
}
#[test]
fn test_parse_resp_inline_whitespace() {
let raw = b"SET foo bar\r\n";
let (consumed, args) = parse_resp_frame(raw)
.expect("应当解析成功")
.expect("完整帧");
assert_eq!(consumed, raw.len());
assert_eq!(args, vec![&b"SET"[..], &b"foo"[..], &b"bar"[..]]);
}
#[test]
fn test_parse_resp_incomplete() {
let raw = b"*2\r\n$4\r\nECH";
assert!(matches!(parse_resp_frame(raw), Ok(None)));
}
#[test]
fn test_parse_resp_negative_array_length_is_error() {
let res = parse_resp_frame(b"*-1\r\n");
assert_eq!(res, Err(Error::InvalidStringLength(-1)));
}
#[test]
fn test_parse_resp_bad_element_sigil_is_error() {
let res = parse_resp_frame(b"*1\r\n:5\r\n");
assert_eq!(res, Err(Error::UnexpectedToken(b':')));
}
#[test]
fn test_parse_resp_null_element_is_error() {
let res = parse_resp_frame(b"*2\r\n$4\r\nECHO\r\n$-1\r\n");
assert_eq!(res, Err(Error::InvalidStringLength(-1)));
}
#[test]
fn test_parse_resp_length_overflow_is_error() {
let res = parse_resp_frame(b"*99999999999\r\n");
assert!(matches!(res, Err(Error::IntegerOverflow { .. })));
}
}