#redis-cluster #redis #protocols #message-parser #resp

no-std redis-protocol

An implementation of the RESP2 and RESP3 protocols

13 releases (stable)

new 5.0.1 Apr 5, 2024
5.0.0 Mar 31, 2024
4.1.0 Jun 12, 2022
4.0.1 Jan 19, 2022
0.1.1 Oct 4, 2018

#147 in Database interfaces

Download history 5149/week @ 2023-12-20 4198/week @ 2023-12-27 9302/week @ 2024-01-03 24982/week @ 2024-01-10 27049/week @ 2024-01-17 26623/week @ 2024-01-24 39282/week @ 2024-01-31 34244/week @ 2024-02-07 35814/week @ 2024-02-14 46417/week @ 2024-02-21 47057/week @ 2024-02-28 47549/week @ 2024-03-06 48538/week @ 2024-03-13 48704/week @ 2024-03-20 57457/week @ 2024-03-27 43248/week @ 2024-04-03

208,735 downloads per month
Used in 22 crates (3 directly)

MIT license

480KB
13K SLoC

Redis Protocol

License License CircleCI Crates.io API docs

A Rust implementation of the Redis protocol.

Features

  • Owned and zero-copy Bytes-based parsing interfaces.
  • Supports RESP2 and RESP3 frames, including streaming frames.
  • Publish-subscribe message utilities.
  • A cluster hash slot interface.
  • RESP2 and RESP3 codec interfaces.
  • Utility functions for converting from RESP2 to RESP3.
  • Traits for converting frames into other types.

Examples

use redis_protocol::resp2::{
  decode::decode,
  encode::encode,
  types::{OwnedFrame as Frame, Resp2Frame}
};

fn main() {
  let frame = Frame::BulkString("foobar".into());
  let mut buf = vec![0; frame.encode_len()];

  let len = encode(&mut buf, &frame).expect("Error encoding frame");
  println!("Encoded {} bytes into buffer with contents {:?}", len, buf);

  // ["Foo", nil, "Bar"]
  let buf: &[u8] = b"*3\r\n$3\r\nFoo\r\n$-1\r\n$3\r\nBar\r\n";
  match decode(&buf).unwrap() {
    Some((frame, amt)) => println!("Parsed {:?} and read {} bytes", frame, amt),
    None => println!("Incomplete frame."),
  };
}

Build Features

Name Default Description
std x Enable stdlib features and most dependency default features.
resp2 x Enable the RESP2 interface.
resp3 x Enable the RESP3 interface.
bytes Enable the zero-copy parsing interface via Bytes types.
decode-logs Enable extra debugging TRACE logs during the frame decoding process.
codec Enable a RESP2 and RESP3 Tokio codec interface.
convert Enable the FromResp2 and FromResp3 trait interfaces.
index-map Use IndexMap types instead of HashMap. This is useful for testing and may also be useful for callers.

no_std

no_std builds are supported by disabling the std feature. However, a few optional dependencies must be activated as a substitute.

redis-protocol = { version = "X.X.X", default-features = false, features = ["libm", "hashbrown", "alloc"] }

Decoding

Both RESP2 and RESP3 interfaces support 3 different Frame interfaces. These interfaces are designed to support different use cases:

  • OwnedFrame types use core container types to implement owned frame variants. This is the easiest frame interface to use, but often requires moving or copying the underlying buffer contents when decoding.
  • BytesFrame types use Bytes as the backing container. The bytes feature flag enables this frame type and a set of associated functions that avoid moving or copying BytesMut contents.
  • RangeFrame types represent ranges into an associated buffer and are typically used to implement forms of zero-copy parsing. This is the lowest level interface.

RESP2 OwnedFrame Decoding Example

Simple array decoding example adapted from the tests

use redis_protocol::resp2::{
  decode::decode,
  types::{OwnedFrame, Resp2Frame}
};

fn should_decode_array() {
  // ["Foo", nil, "Bar"]
  let buf: &[u8] = b"*3\r\n$3\r\nFoo\r\n$-1\r\n$3\r\nBar\r\n";

  let (frame, amt) = decode(&buf).unwrap().unwrap();
  assert_eq!(frame, OwnedFrame::Array(vec![
    OwnedFrame::BulkString("Foo".into()),
    OwnedFrame::Null,
    OwnedFrame::BulkString("Bar".into())
  ]));
  assert_eq!(amt, buf.len());
}

RESP2 BytesFrame Decoding Example

Array decoding example adapted from the tests

use redis_protocol::resp2::{
  decode::decode_bytes_mut,
  types::{BytesFrame, Resp2Frame}
};
use bytes::BytesMut;

fn should_decode_array_no_nulls() {
  let expected = (
    BytesFrame::Array(vec![
      BytesFrame::SimpleString("Foo".into()),
      BytesFrame::SimpleString("Bar".into()),
    ]),
    16,
  );
  let mut bytes: BytesMut = "*2\r\n+Foo\r\n+Bar\r\n".into();
  let total_len = bytes.len();

  let (frame, amt, buf) = match decode_bytes_mut(&mut bytes) {
    Ok(Some(result)) => result,
    Ok(None) => panic!("Expected complete frame"),
    Err(e) => panic!("{:?}", e)
  };

  assert_eq!(frame, expected.0, "decoded frame matched");
  assert_eq!(amt, expected.1, "decoded frame len matched");
  assert_eq!(buf.len(), expected.1, "output buffer len matched");
  assert_eq!(buf.len() + bytes.len(), total_len, "total len matched");
}

RESP2 RangeFrame Decoding Example

Implement a custom borrowed frame type that can only represent BulkString and SimpleString

use redis_protocol::resp2::{
  decode::decode_range,
  types::RangeFrame
};
use std::str;

enum MyBorrowedFrame<'a> {
  BulkString(&'a [u8]),
  SimpleString(&'a str),
}

fn decode_borrowed(buf: &[u8]) -> Option<MyBorrowedFrame> {
  match decode_range(buf).ok()? {
    Some((RangeFrame::BulkString((i, j)), _)) => {
      Some(MyBorrowedFrame::BulkString(&buf[i..j]))
    }
    Some((RangeFrame::SimpleString((i, j)), _)) => {
      let parsed = str::from_utf8(&buf[i..j]).ok()?;
      Some(MyBorrowedFrame::SimpleString(parsed))
    }
    _ => None,
  }
}

Dependencies

~1–3MB
~53K SLoC