Skip to main content

resp_rs/
lib.rs

1//! Zero-copy RESP2 and RESP3 protocol parser and serializer.
2//!
3//! `resp-rs` provides high-performance parsing and serialization for the
4//! [Redis Serialization Protocol](https://redis.io/docs/latest/develop/reference/protocol-spec/)
5//! (RESP), supporting both RESP2 and RESP3.
6//!
7//! # Features
8//!
9//! - **Zero-copy parsing** using [`bytes::Bytes`] -- parsing a bulk string is an O(1)
10//!   slice operation, not a copy
11//! - **RESP2 and RESP3** support with separate [`resp2::Frame`] and [`resp3::Frame`] types
12//! - **Streaming parser** ([`resp2::Parser`] / [`resp3::Parser`]) for incremental data --
13//!   handles partial reads and pipelining
14//! - **Serialization** via [`resp2::frame_to_bytes`] and [`resp3::frame_to_bytes`]
15//! - **Minimal dependencies** -- only [`bytes`] and [`thiserror`]
16//! - **No async runtime required** -- pure sync parsing that works in any context
17//!
18//! # Modules
19//!
20//! | Module | Description |
21//! |--------|-------------|
22//! | [`resp2`] | RESP2 types, [`resp2::parse_frame`], [`resp2::frame_to_bytes`], [`resp2::Parser`] |
23//! | [`resp3`] | RESP3 types, [`resp3::parse_frame`], [`resp3::frame_to_bytes`], [`resp3::Parser`], [`resp3::parse_streaming_sequence`] |
24//!
25//! # Quick Start
26//!
27//! ## Parsing a RESP2 command
28//!
29//! ```
30//! use bytes::Bytes;
31//! use resp_rs::resp2::{self, Frame};
32//!
33//! // A Redis SET command on the wire
34//! let data = Bytes::from("*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n");
35//! let (frame, remaining) = resp2::parse_frame(data).unwrap();
36//!
37//! assert_eq!(frame, Frame::Array(Some(vec![
38//!     Frame::BulkString(Some(Bytes::from("SET"))),
39//!     Frame::BulkString(Some(Bytes::from("key"))),
40//!     Frame::BulkString(Some(Bytes::from("value"))),
41//! ])));
42//! assert!(remaining.is_empty());
43//! ```
44//!
45//! ## Parsing RESP3 types
46//!
47//! RESP3 adds null, booleans, doubles, maps, sets, and more:
48//!
49//! ```
50//! use bytes::Bytes;
51//! use resp_rs::resp3::{self, Frame};
52//!
53//! // Simple string
54//! let (frame, _) = resp3::parse_frame(Bytes::from("+OK\r\n")).unwrap();
55//! assert_eq!(frame, Frame::SimpleString(Bytes::from("OK")));
56//!
57//! // Null
58//! let (frame, _) = resp3::parse_frame(Bytes::from("_\r\n")).unwrap();
59//! assert_eq!(frame, Frame::Null);
60//!
61//! // Boolean
62//! let (frame, _) = resp3::parse_frame(Bytes::from("#t\r\n")).unwrap();
63//! assert_eq!(frame, Frame::Boolean(true));
64//!
65//! // Double
66//! let (frame, _) = resp3::parse_frame(Bytes::from(",3.14\r\n")).unwrap();
67//! assert_eq!(frame, Frame::Double(3.14));
68//!
69//! // Integer
70//! let (frame, _) = resp3::parse_frame(Bytes::from(":-42\r\n")).unwrap();
71//! assert_eq!(frame, Frame::Integer(-42));
72//!
73//! // Bulk string
74//! let (frame, _) = resp3::parse_frame(Bytes::from("$5\r\nhello\r\n")).unwrap();
75//! assert_eq!(frame, Frame::BulkString(Some(Bytes::from("hello"))));
76//!
77//! // Null bulk string
78//! let (frame, _) = resp3::parse_frame(Bytes::from("$-1\r\n")).unwrap();
79//! assert_eq!(frame, Frame::BulkString(None));
80//!
81//! // Array
82//! let (frame, _) = resp3::parse_frame(Bytes::from("*2\r\n:1\r\n:2\r\n")).unwrap();
83//! assert_eq!(frame, Frame::Array(Some(vec![Frame::Integer(1), Frame::Integer(2)])));
84//!
85//! // Map
86//! let data = Bytes::from("%2\r\n+name\r\n$5\r\nAlice\r\n+age\r\n:30\r\n");
87//! let (frame, _) = resp3::parse_frame(data).unwrap();
88//! assert_eq!(frame, Frame::Map(vec![
89//!     (Frame::SimpleString(Bytes::from("name")), Frame::BulkString(Some(Bytes::from("Alice")))),
90//!     (Frame::SimpleString(Bytes::from("age")), Frame::Integer(30)),
91//! ]));
92//!
93//! // Set
94//! let (frame, _) = resp3::parse_frame(Bytes::from("~3\r\n:1\r\n:2\r\n:3\r\n")).unwrap();
95//! assert_eq!(frame, Frame::Set(vec![Frame::Integer(1), Frame::Integer(2), Frame::Integer(3)]));
96//!
97//! // Big number
98//! let (frame, _) = resp3::parse_frame(Bytes::from("(12345678901234567890\r\n")).unwrap();
99//! assert_eq!(frame, Frame::BigNumber(Bytes::from("12345678901234567890")));
100//!
101//! // Verbatim string
102//! let (frame, _) = resp3::parse_frame(Bytes::from("=15\r\ntxt:hello world\r\n")).unwrap();
103//! assert_eq!(frame, Frame::VerbatimString(Bytes::from("txt"), Bytes::from("hello world")));
104//!
105//! // Blob error
106//! let (frame, _) = resp3::parse_frame(Bytes::from("!5\r\nOOPS!\r\n")).unwrap();
107//! assert_eq!(frame, Frame::BlobError(Bytes::from("OOPS!")));
108//!
109//! // Error
110//! let (frame, _) = resp3::parse_frame(Bytes::from("-ERR unknown\r\n")).unwrap();
111//! assert_eq!(frame, Frame::Error(Bytes::from("ERR unknown")));
112//! ```
113//!
114//! ## Serialization
115//!
116//! Convert any [`resp2::Frame`] or [`resp3::Frame`] back to wire format:
117//!
118//! ```
119//! use bytes::Bytes;
120//! use resp_rs::resp2::{Frame, frame_to_bytes};
121//!
122//! let frame = Frame::Array(Some(vec![
123//!     Frame::BulkString(Some(Bytes::from("GET"))),
124//!     Frame::BulkString(Some(Bytes::from("mykey"))),
125//! ]));
126//! let wire = frame_to_bytes(&frame);
127//! assert_eq!(wire, Bytes::from("*2\r\n$3\r\nGET\r\n$5\r\nmykey\r\n"));
128//! ```
129//!
130//! Roundtrip is guaranteed: `parse_frame(frame_to_bytes(&frame)) == Ok((frame, empty))`.
131//!
132//! ## Streaming parser
133//!
134//! The [`resp2::Parser`] and [`resp3::Parser`] types buffer incremental data
135//! and yield frames as they become complete -- ideal for reading from TCP sockets.
136//!
137//! ```
138//! use bytes::Bytes;
139//! use resp_rs::resp3::{Parser, Frame};
140//!
141//! let mut parser = Parser::new();
142//!
143//! // Simulate receiving data in chunks (e.g., from TCP)
144//! parser.feed(Bytes::from("+HEL"));
145//! assert!(parser.next_frame().unwrap().is_none()); // not enough data yet
146//!
147//! parser.feed(Bytes::from("LO\r\n:42\r\n"));
148//! // Now we have two complete frames buffered
149//!
150//! let frame1 = parser.next_frame().unwrap().unwrap();
151//! assert_eq!(frame1, Frame::SimpleString(Bytes::from("HELLO")));
152//!
153//! let frame2 = parser.next_frame().unwrap().unwrap();
154//! assert_eq!(frame2, Frame::Integer(42));
155//!
156//! assert!(parser.next_frame().unwrap().is_none()); // buffer exhausted
157//! ```
158//!
159//! ## Pipelined commands
160//!
161//! Multiple frames in a single buffer parse naturally in sequence:
162//!
163//! ```
164//! use bytes::Bytes;
165//! use resp_rs::resp2::{self, Frame};
166//!
167//! let wire = Bytes::from("+OK\r\n$5\r\nhello\r\n:42\r\n");
168//!
169//! let (f1, rest) = resp2::parse_frame(wire).unwrap();
170//! assert_eq!(f1, Frame::SimpleString(Bytes::from("OK")));
171//!
172//! let (f2, rest) = resp2::parse_frame(rest).unwrap();
173//! assert_eq!(f2, Frame::BulkString(Some(Bytes::from("hello"))));
174//!
175//! let (f3, rest) = resp2::parse_frame(rest).unwrap();
176//! assert_eq!(f3, Frame::Integer(42));
177//!
178//! assert!(rest.is_empty());
179//! ```
180//!
181//! ## RESP3 streaming sequences
182//!
183//! RESP3 supports chunked/streaming data. Use [`resp3::parse_streaming_sequence`]
184//! to accumulate chunks into a complete frame:
185//!
186//! ```
187//! use bytes::Bytes;
188//! use resp_rs::resp3::{self, Frame};
189//!
190//! // Streaming string: $?\r\n followed by chunks, terminated by ;0\r\n
191//! let data = Bytes::from("$?\r\n;5\r\nHello\r\n;6\r\n World\r\n;0\r\n\r\n");
192//! let (frame, _) = resp3::parse_streaming_sequence(data).unwrap();
193//!
194//! if let Frame::StreamedString(chunks) = frame {
195//!     assert_eq!(chunks.len(), 2);
196//!     assert_eq!(chunks[0], Bytes::from("Hello"));
197//!     assert_eq!(chunks[1], Bytes::from(" World"));
198//! }
199//! ```
200//!
201//! # Tokio Codec (async)
202//!
203//! Enable the `codec` feature for `tokio_util::codec` integration:
204//!
205//! ```toml
206//! [dependencies]
207//! resp-rs = { version = "0.1", features = ["codec"] }
208//! tokio = { version = "1", features = ["net"] }
209//! tokio-util = { version = "0.7", features = ["codec"] }
210//! futures = "0.3"  # for SinkExt / StreamExt
211//! ```
212//!
213//! This provides `resp2::Codec` and `resp3::Codec`, which implement
214//! `tokio_util::codec::Decoder` and `tokio_util::codec::Encoder`.
215//! Wrap a TCP stream with `tokio_util::codec::Framed` for async
216//! frame-level I/O:
217//!
218//! ```ignore
219//! use resp_rs::resp2::{Codec, Frame};
220//! use tokio::net::TcpStream;
221//! use tokio_util::codec::Framed;
222//! use futures::{SinkExt, StreamExt};
223//! use bytes::Bytes;
224//!
225//! let stream = TcpStream::connect("127.0.0.1:6379").await?;
226//! let mut framed = Framed::new(stream, Codec::new());
227//!
228//! // Send a PING
229//! framed.send(Frame::Array(Some(vec![
230//!     Frame::BulkString(Some(Bytes::from("PING"))),
231//! ]))).await?;
232//!
233//! // Read the response
234//! if let Some(Ok(frame)) = framed.next().await {
235//!     println!("{frame:?}"); // SimpleString("PONG")
236//! }
237//! ```
238//!
239//! The decoder uses the same zero-copy [`parse_frame`](resp2::parse_frame) path
240//! internally. Errors are returned as `codec::CodecError`, which wraps both
241//! [`ParseError`] and `std::io::Error`.
242//!
243//! # Working with Frames
244//!
245//! Both [`resp2::Frame`] and [`resp3::Frame`] provide convenience methods for
246//! extracting typed data without manual pattern matching:
247//!
248//! ```
249//! use bytes::Bytes;
250//! use resp_rs::resp2::{self, Frame};
251//!
252//! let data = Bytes::from("*3\r\n+SET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n");
253//! let (frame, _) = resp2::parse_frame(data).unwrap();
254//!
255//! // Extract array items
256//! let items = frame.into_array().unwrap();
257//! assert_eq!(items[0].as_str(), Some("SET"));
258//! assert_eq!(items[1].as_str(), Some("key"));
259//! assert_eq!(items[2].as_str(), Some("value"));
260//! ```
261//!
262//! Available methods include:
263//!
264//! | Method | Returns | Works on |
265//! |--------|---------|----------|
266//! | `as_bytes()` | `Option<&Bytes>` | Strings, bulk strings, errors |
267//! | `as_str()` | `Option<&str>` | String-like frames with valid UTF-8 |
268//! | `as_integer()` | `Option<i64>` | `Integer` |
269//! | `as_double()` | `Option<f64>` | `Double` (RESP3 only) |
270//! | `as_boolean()` | `Option<bool>` | `Boolean` (RESP3 only) |
271//! | `as_array()` | `Option<&[Frame]>` | `Array` |
272//! | `as_map()` | `Option<&[(Frame, Frame)]>` | `Map` (RESP3 only) |
273//! | `into_array()` | `Result<Vec<Frame>, Frame>` | `Array` |
274//! | `into_bulk_string()` | `Result<Bytes, Frame>` | `BulkString` |
275//! | `into_map()` | `Result<Vec<(Frame, Frame)>, Frame>` | `Map` (RESP3 only) |
276//! | `is_null()` | `bool` | Any frame |
277//! | `is_error()` | `bool` | Any frame |
278//!
279//! # Redis Cluster (hash slots)
280//!
281//! Enable the `cluster` feature for hash slot calculation:
282//!
283//! ```toml
284//! [dependencies]
285//! resp-rs = { version = "0.1", features = ["cluster"] }
286//! ```
287//!
288//! ```ignore
289//! use resp_rs::cluster::hash_slot;
290//!
291//! // Keys with the same hash tag route to the same slot
292//! assert_eq!(hash_slot(b"{user}.name"), hash_slot(b"{user}.email"));
293//! ```
294//!
295//! Implements CRC16-CCITT with hash tag extraction per the
296//! [Redis Cluster specification](https://redis.io/docs/latest/operate/oss_and_stack/reference/cluster-spec/#hash-tags).
297//!
298//! # `no_std` Support
299//!
300//! This crate supports `no_std` environments (with `alloc`). The `std` feature
301//! is enabled by default; disable it for embedded or WASM targets:
302//!
303//! ```toml
304//! [dependencies]
305//! resp-rs = { version = "0.1", default-features = false }
306//! ```
307//!
308//! All core parsing and serialization works without `std`. The `codec` feature
309//! requires `std` (Tokio dependency). The `cluster` and `unsafe-internals`
310//! features work in `no_std`.
311//!
312//! # Performance
313//!
314//! The parser uses offset-based internal parsing to minimize allocations. Bulk string
315//! parsing is an O(1) slice into the input buffer, not a copy.
316//!
317//! ## `parse_frame` vs `Parser`
318//!
319//! [`resp2::parse_frame`] and [`resp3::parse_frame`] parse directly from a [`bytes::Bytes`]
320//! buffer with no overhead. The [`resp2::Parser`] and [`resp3::Parser`] wrappers add
321//! incremental buffering for TCP streams, but have roughly 2x overhead per frame due to
322//! internal `BytesMut` split/unsplit operations.
323//!
324//! **If you already have a complete buffer** (e.g., a full response read from a socket),
325//! call `parse_frame` directly in a loop rather than going through `Parser`:
326//!
327//! ```
328//! use bytes::Bytes;
329//! use resp_rs::resp2;
330//!
331//! let wire = Bytes::from("+OK\r\n:42\r\n$5\r\nhello\r\n");
332//! let mut input = wire;
333//! while !input.is_empty() {
334//!     match resp2::parse_frame(input) {
335//!         Ok((frame, rest)) => {
336//!             // process frame
337//!             input = rest;
338//!         }
339//!         Err(resp_rs::ParseError::Incomplete) => break, // need more data
340//!         Err(e) => panic!("parse error: {e}"),
341//!     }
342//! }
343//! ```
344//!
345//! **Use `Parser` when** data arrives incrementally (e.g., reading from a TCP socket in
346//! chunks) and you need to buffer partial frames across reads.
347//!
348//! ## RESP2 vs RESP3
349//!
350//! RESP2 parsing is roughly 3x faster than RESP3 for simple types due to RESP2's smaller
351//! type tag match (5 variants vs 16+). The gap narrows for collection-heavy workloads
352//! where per-element parsing dominates. If your application only needs RESP2, prefer the
353//! [`resp2`] module for best performance.
354//!
355//! ## Representative timings
356//!
357//! Measured on Apple M4 (single core, criterion):
358//!
359//! | Operation | RESP2 | RESP3 |
360//! |-----------|-------|-------|
361//! | Simple string | 12 ns | 39 ns |
362//! | Bulk string | 13 ns | 39 ns |
363//! | Integer | 25 ns | 47 ns |
364//! | 3-element array | 43 ns | 82 ns |
365//! | 100-element array | 822 ns | 2.0 us |
366//! | 5-frame pipeline (direct) | 107 ns | 129 ns |
367//! | 5-frame pipeline (Parser) | 242 ns | 286 ns |
368//! | Streaming string (2 chunks) | -- | 124 ns |
369//! | Streaming array (5 elements) | -- | 226 ns |
370//!
371//! Run `cargo bench` to reproduce on your hardware.
372//!
373//! # Error Handling
374//!
375//! All parsing functions return [`Result<_, ParseError>`]. The [`ParseError::Incomplete`]
376//! variant signals that more data is needed (not a protocol error), while other
377//! variants indicate malformed input.
378//!
379//! ```
380//! use bytes::Bytes;
381//! use resp_rs::{ParseError, resp2};
382//!
383//! // Not enough data
384//! assert_eq!(resp2::parse_frame(Bytes::from("$5\r\nhel")), Err(ParseError::Incomplete));
385//!
386//! // Unknown type tag
387//! assert_eq!(resp2::parse_frame(Bytes::from("X\r\n")), Err(ParseError::InvalidTag(b'X')));
388//!
389//! // Empty input
390//! assert_eq!(resp2::parse_frame(Bytes::new()), Err(ParseError::Incomplete));
391//! ```
392
393#![cfg_attr(not(feature = "std"), no_std)]
394
395extern crate alloc;
396
397pub mod resp2;
398pub mod resp3;
399
400#[cfg(feature = "codec")]
401pub mod codec;
402
403#[cfg(feature = "cluster")]
404pub mod cluster;
405
406/// Errors that can occur during RESP parsing.
407#[derive(Debug, Clone, PartialEq, thiserror::Error)]
408pub enum ParseError {
409    /// Not enough data to parse a complete frame.
410    #[error("incomplete data")]
411    Incomplete,
412
413    /// Invalid type tag byte at the start of a frame.
414    #[error("invalid tag byte: 0x{0:02x}")]
415    InvalidTag(u8),
416
417    /// Invalid length in a bulk string or collection.
418    #[error("invalid length")]
419    BadLength,
420
421    /// Invalid UTF-8 in a string value.
422    #[error("invalid UTF-8")]
423    Utf8Error,
424
425    /// Invalid frame format.
426    #[error("invalid format")]
427    InvalidFormat,
428
429    /// Invalid boolean value (not 't' or 'f').
430    #[error("invalid boolean value")]
431    InvalidBoolean,
432
433    /// Invalid special float (not 'inf', '-inf', or 'nan').
434    #[error("invalid special float")]
435    InvalidSpecialFloat,
436
437    /// Integer value overflowed i64 range.
438    #[error("integer overflow")]
439    Overflow,
440}