RESP parser with an enum sum type
Rust enums are real sum types. Every RESP value is exactly one variant. The compiler will not let you forget a case. This is the parser shape that pays off on every PR forever.
#[derive(Debug, Clone)]
enum RespValue {
SimpleString(String),
Integer(i64),
BulkString(Option<String>),
Array(Vec<RespValue>),
}
#[derive(Debug)]
enum ParseError {
Incomplete,
InvalidType(u8),
BadHeader,
}The typed return value. Option<String> for bulk strings captures the RESP null case ($-1) explicitly.
One thing to see before the parser: what a RESP frame looks like on the wire. The first byte is a tag for the kind of value that follows. A plus (+) is a simple string, a colon (:) is an integer, a dollar ($) is a bulk string that carries a byte-length prefix, and a star (*) is an array that carries an element count. Every part ends in \r\n. So the command SET foo bar arrives as *3\r\n$3\r\nSET\r\n$3\r\nfoo\r\n$3\r\nbar\r\n, an array of three bulk strings. The parser below reads that first tag byte, grabs the header up to the \r\n, and branches on the tag.
fn parse_resp(buf: &[u8]) -> Result<(RespValue, usize), ParseError> {
if buf.is_empty() { return Err(ParseError::Incomplete); }
let t = buf[0];
let crlf = buf[1..].windows(2).position(|w| w == b"\r\n").ok_or(ParseError::Incomplete)? + 1;
let header = std::str::from_utf8(&buf[1..crlf]).map_err(|_| ParseError::BadHeader)?;
let after = crlf + 2;
match t {
b'+' => Ok((RespValue::SimpleString(header.to_string()), after)),
b':' => Ok((RespValue::Integer(header.parse().map_err(|_| ParseError::BadHeader)?), after)),
b'$' => { /* bulk string with length prefix */ }
b'*' => { /* recursive array */ }
b => Err(ParseError::InvalidType(b)),
}
}The whole parser in ~30 lines. Recursive for arrays. Returns (RespValue, usize) so the caller knows how many bytes to drain.
ParseError::Incomplete is the key insight. TCP gives you bytes whenever it feels like it; one Read may carry half a frame. Returning Incomplete tells the caller \u201Cbuffer more bytes, try again.\u201D That keeps the parser pure and the IO loop simple.
Quiz: Quiz
Loading practice…
AI prompt: Try it: hand-write a frame
Loading practice…