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.

02-resp-parser/src/main.rs
rust
#[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.

02-resp-parser/src/main.rs
rust
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…