beyond-resp
Frame and parse RESP2/RESP3 over any async byte stream
RespCodec implements tokio-util's Encoder and Decoder traits. Drop it onto a Framed transport and you have a working Redis wire codec.
Install
[]
= "0.1"
Enable monoio support:
[]
= { = "0.1", = ["monoio"] }
Quick Start
use ;
use BytesMut;
use ;
let mut codec = resp2;
let mut buf = new;
// Encode
codec.encode?;
// Decode
let mut incoming = from;
let value = codec.decode?; // Some(Value::SimpleString("OK"))
With a tokio transport:
use ;
use TcpStream;
use Framed;
use ;
let stream = connect.await?;
let mut framed = new;
framed.send.await?;
if let Some = framed.next.await
With a monoio transport:
use ;
use TcpStream;
use Decoder; // brings .framed() into scope
let stream = connect.await?;
let mut framed = resp2.framed;
monoio_codec::Decoder returns Decoded<Value> — Decoded::Some(v) when a frame is ready, Decoded::Insufficient when more bytes are needed.
Protocol Version
RespCodec::resp2() and RespCodec::resp3() select the version at construction. Switch mid-stream after a HELLO 3 handshake:
codec.set_version;
RESP3 enables Map, Set, Push, Boolean, Double, BigNumber, VerbatimString, BulkError, and Attribute types. RESP2 encodes Null as $-1\r\n; RESP3 uses _\r\n.
Frame Size Limit
Default maximum frame size matches Redis at 512 MiB. Override:
let codec = resp2.with_max_frame_bytes;
Frames exceeding the limit return RespError::FrameTooLarge.
Value Types
| Variant | RESP2 | RESP3 |
|---|---|---|
SimpleString |
✓ | ✓ |
SimpleError |
✓ | ✓ |
Integer |
✓ | ✓ |
BulkString |
✓ | ✓ |
Array |
✓ | ✓ |
Null |
✓ | ✓ |
Boolean |
✓ | |
Double |
✓ | |
BigNumber |
✓ | |
BulkError |
✓ | |
VerbatimString |
✓ | |
Map |
✓ | |
Attribute |
✓ | |
Set |
✓ | |
Push |
✓ |
Performance
Benchmarked with divan on an Apple M2 (MacBook Air, 8-core, 24 GB RAM).
The decoder uses a two-phase design: phase 1 walks the wire bytes to find the frame boundary without allocating; phase 2 builds the Value tree using zero-copy Bytes::slice() views into the receive buffer. String data (SimpleString, SimpleError, BulkString, BulkError, BigNumber, VerbatimString) is never copied — it points directly into the BytesMut.
decode_simple_string 36 ns
decode_integer 57 ns
decode_bulk_string (11 B) 54 ns
decode_bulk_string (1 KB) 92 ns
decode_bulk_string (64 KB) 1.1 µs
decode_array (10 i64) 273 ns
decode_array (10 str) 500 ns
decode_nested_array 315 ns
decode_map (5 KV) 273 ns
decode_pipeline_100 2.9 µs (~29 ns/frame)
encode_null 11 ns
encode_simple_string 15 ns
encode_integer 19 ns
encode_double 29 ns
encode_bulk_string (11 B) 32 ns
encode_bulk_string (64 KB) 958 ns
encode_array (10 i64) 140 ns
roundtrip_get_response 68 ns
roundtrip_hgetall_response 1.2 µs (10-entry map)
Run benchmarks locally:
License
MIT