Skip to content

Commit 3998af3

Browse files
authored
6.0.0 (#37)
* feat: encode integers without allocating * feat: add int_as_blobstring options * feat: add borrowed frame variants
1 parent d055fa3 commit 3998af3

File tree

16 files changed

+1937
-377
lines changed

16 files changed

+1937
-377
lines changed

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ license = "MIT"
77
name = "redis-protocol"
88
readme = "README.md"
99
repository = "https://github.com/aembke/redis-protocol.rs"
10-
version = "5.0.1"
10+
version = "6.0.0"
1111
edition = "2021"
1212
exclude = ["fuzz", ".circleci", "benches"]
1313

README.md

Lines changed: 61 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ use redis_protocol::resp2::{
3030

3131
fn main() {
3232
let frame = Frame::BulkString("foobar".into());
33-
let mut buf = vec![0; frame.encode_len()];
33+
let mut buf = vec![0; frame.encode_len(false)];
3434

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

3838
// ["Foo", nil, "Bar"]
@@ -167,3 +167,62 @@ fn decode_borrowed(buf: &[u8]) -> Option<MyBorrowedFrame> {
167167
}
168168
}
169169
```
170+
171+
## Encoding
172+
173+
Similar to the decoding interface, there are 3 frame types available to callers for encoding use cases.
174+
175+
* `OwnedFrame` - See above. Callers that use this interface often have to allocate additional containers on the heap,
176+
and often have to move or copy the contents into these frame types from other types, such as `RedisValue` in `fred`.
177+
* `BytesFrame` - See above. This frame type often avoids the need to copy or move the contents from upstream types such
178+
as `RedisValue`, but still requires callers to allocate containers on the heap (i.e. the `Vec` in the `Array`
179+
variant).
180+
* `BorrowedFrame` - This frame type serves a similar purpose to the `RangeFrame` type in the decoding interface in that
181+
it can be used when callers want to entirely avoid heap allocations. In practice this often requires the caller to
182+
create some intermediate reference-based types, but significantly outperforms other approaches that require
183+
allocations.
184+
185+
### Options
186+
187+
All encoding interfaces expose an additional parameter, `int_as_bulkstring` for RESP2 or `int_as_blobstring` for RESP3,
188+
that can be used to encode integers as `BulkString` or `BlobString` using a more optimized code path that avoids heap
189+
allocations.
190+
191+
In practice Redis expects all incoming frames to be an array of blob or bulk strings. This option can be used to
192+
automatically convert integers to byte arrays without additional allocations.
193+
194+
### Buffer Management
195+
196+
In some cases this library can automatically extend buffers while encoding. The `extend_encode_*` family of functions
197+
operate on `BytesMut` types and can automatically resize buffers while encoding, whereas the `encode_*` family of
198+
functions require the caller to allocate the correct amount beforehand.
199+
200+
```rust
201+
use redis_protocol::resp2::{
202+
encode,
203+
types::{OwnedFrame, BytesFrame, BorrowedFrame, Resp2Frame}
204+
};
205+
use bytes::BytesMut;
206+
207+
fn main() {
208+
// using OwnedFrame variants, manually allocating the buffer
209+
let frame = OwnedFrame::BulkString("foobar".into());
210+
let mut buf = vec![0; frame.encode_len(false)];
211+
let len = encode::encode(&mut buf, &frame, false).expect("Error encoding frame");
212+
println!("Encoded {} bytes into buffer with contents {:?}", len, buf);
213+
214+
// using BytesFrame variants
215+
let frame = BytesFrame::BulkString("foobar".into());
216+
let mut buf = BytesMut::new();
217+
let len = encode::extend_encode(&mut buf, &frame, false).expect("Error encoding frame");
218+
println!("Encoded {} bytes into buffer with contents {:?}", len, buf);
219+
220+
// using BorrowedFrame variants
221+
let value: String = "foobar".into();
222+
let frame = BorrowedFrame::BulkString(value.as_bytes());
223+
let mut buf = BytesMut::new();
224+
let len = encode::extend_encode_borrowed(&mut buf, &frame, false).expect("Error encoding frame");
225+
println!("Encoded {} bytes into buffer with contents {:?}", len, buf);
226+
}
227+
228+
```

benches/resp2_decode.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use bytes::{BufMut, Bytes, BytesMut};
1010

1111
#[cfg(feature = "bytes")]
1212
fn gen_bulkstring_bytes(len: usize, buf: Option<BytesMut>) -> BytesMut {
13-
let digits = redis_protocol::digits_in_number(len);
13+
let digits = redis_protocol::digits_in_usize(len);
1414
let mut v = buf.unwrap_or_else(|| BytesMut::with_capacity(1 + digits + 2 + len + 2));
1515

1616
v.put_u8(b'$');
@@ -30,8 +30,8 @@ fn gen_null_bytes(buf: Option<BytesMut>) -> BytesMut {
3030

3131
#[cfg(feature = "bytes")]
3232
fn gen_array_bytes(len: usize, null_every: usize, str_len: usize) -> BytesMut {
33-
let arr_len_digits = redis_protocol::digits_in_number(len);
34-
let str_len_digits = redis_protocol::digits_in_number(str_len);
33+
let arr_len_digits = redis_protocol::digits_in_usize(len);
34+
let str_len_digits = redis_protocol::digits_in_usize(str_len);
3535
let buf = BytesMut::with_capacity(1 + arr_len_digits + 2 + (len * (1 + str_len_digits + 2 + str_len + 2)));
3636

3737
(0 .. len).fold(buf, |buf, i| {
@@ -44,7 +44,7 @@ fn gen_array_bytes(len: usize, null_every: usize, str_len: usize) -> BytesMut {
4444
}
4545

4646
fn gen_bulkstring_owned(len: usize, buf: Option<Vec<u8>>) -> Vec<u8> {
47-
let digits = redis_protocol::digits_in_number(len);
47+
let digits = redis_protocol::digits_in_usize(len);
4848
let mut v = buf.unwrap_or_else(|| Vec::with_capacity(1 + digits + 2 + len + 2));
4949

5050
v.push(b'$');
@@ -62,8 +62,8 @@ fn gen_null_owned(buf: Option<Vec<u8>>) -> Vec<u8> {
6262
}
6363

6464
fn gen_array_owned(len: usize, null_every: usize, str_len: usize) -> Vec<u8> {
65-
let arr_len_digits = redis_protocol::digits_in_number(len);
66-
let str_len_digits = redis_protocol::digits_in_number(str_len);
65+
let arr_len_digits = redis_protocol::digits_in_usize(len);
66+
let str_len_digits = redis_protocol::digits_in_usize(str_len);
6767
let buf = Vec::with_capacity(1 + arr_len_digits + 2 + (len * (1 + str_len_digits + 2 + str_len + 2)));
6868

6969
(0 .. len).fold(buf, |buf, i| {

src/codec.rs

Lines changed: 58 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ mod resp3 {
44
error::{RedisProtocolError, RedisProtocolErrorKind},
55
resp3::{
66
decode::streaming::decode_bytes_mut as resp3_decode,
7-
encode::complete::extend_encode as resp3_encode,
8-
types::{BytesFrame as Resp3Frame, StreamedFrame},
7+
encode::complete::{extend_encode as resp3_encode, extend_encode_borrowed as resp3_encode_borrowed},
8+
types::{BorrowedFrame, BytesFrame as Resp3Frame, StreamedFrame},
99
},
1010
};
1111
use bytes::BytesMut;
@@ -40,10 +40,10 @@ mod resp3 {
4040
/// let socket = TcpStream::connect("127.0.0.1:6379").await.unwrap();
4141
/// let mut framed = Framed::new(socket, Resp3::default());
4242
///
43-
/// let hello = Resp3Frame::Hello {
44-
/// version: RespVersion::RESP3,
45-
/// username: Some("foo".into()),
46-
/// password: Some("bar".into()),
43+
/// let hello = BytesFrame::Hello {
44+
/// version: RespVersion::RESP3,
45+
/// auth: Some(("foo".into(), "bar".into())),
46+
/// setname: None,
4747
/// };
4848
/// // or use the shorthand, but this likely only works for simple use cases
4949
/// let get_foo = resp3_encode_command("GET foo");
@@ -60,14 +60,38 @@ mod resp3 {
6060
/// ```
6161
#[derive(Debug, Default)]
6262
pub struct Resp3 {
63-
streaming: Option<StreamedFrame<Resp3Frame>>,
63+
streaming: Option<StreamedFrame<Resp3Frame>>,
64+
int_as_blobstring: bool,
65+
}
66+
67+
impl Resp3 {
68+
/// Create a new codec with the provided flag describing whether the encoder logic should send integers as blob
69+
/// strings.
70+
pub fn new(int_as_blobstring: bool) -> Resp3 {
71+
Resp3 {
72+
int_as_blobstring,
73+
streaming: None,
74+
}
75+
}
6476
}
6577

6678
impl Encoder<Resp3Frame> for Resp3 {
6779
type Error = RedisProtocolError;
6880

6981
fn encode(&mut self, item: Resp3Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
70-
resp3_encode(dst, &item).map(|_| ()).map_err(RedisProtocolError::from)
82+
resp3_encode(dst, &item, self.int_as_blobstring)
83+
.map(|_| ())
84+
.map_err(RedisProtocolError::from)
85+
}
86+
}
87+
88+
impl Encoder<BorrowedFrame<'_>> for Resp3 {
89+
type Error = RedisProtocolError;
90+
91+
fn encode(&mut self, item: BorrowedFrame, dst: &mut BytesMut) -> Result<(), Self::Error> {
92+
resp3_encode_borrowed(dst, &item, self.int_as_blobstring)
93+
.map(|_| ())
94+
.map_err(RedisProtocolError::from)
7195
}
7296
}
7397

@@ -125,8 +149,8 @@ mod resp2 {
125149
error::RedisProtocolError,
126150
resp2::{
127151
decode::decode_bytes_mut as resp2_decode,
128-
encode::extend_encode as resp2_encode,
129-
types::BytesFrame as Resp2Frame,
152+
encode::{extend_encode as resp2_encode, extend_encode_borrowed as resp2_encode_borrowed},
153+
types::{BorrowedFrame, BytesFrame as Resp2Frame},
130154
},
131155
};
132156
use bytes::BytesMut;
@@ -169,13 +193,35 @@ mod resp2 {
169193
/// }
170194
/// ```
171195
#[derive(Clone, Debug, Default)]
172-
pub struct Resp2;
196+
pub struct Resp2 {
197+
int_as_bulkstring: bool,
198+
}
199+
200+
impl Resp2 {
201+
/// Create a new codec with the provided flag describing whether the encoder logic should send integers as blob
202+
/// strings.
203+
pub fn new(int_as_bulkstring: bool) -> Resp2 {
204+
Resp2 { int_as_bulkstring }
205+
}
206+
}
173207

174208
impl Encoder<Resp2Frame> for Resp2 {
175209
type Error = RedisProtocolError;
176210

177211
fn encode(&mut self, item: Resp2Frame, dst: &mut BytesMut) -> Result<(), Self::Error> {
178-
resp2_encode(dst, &item).map(|_| ()).map_err(RedisProtocolError::from)
212+
resp2_encode(dst, &item, self.int_as_bulkstring)
213+
.map(|_| ())
214+
.map_err(RedisProtocolError::from)
215+
}
216+
}
217+
218+
impl Encoder<BorrowedFrame<'_>> for Resp2 {
219+
type Error = RedisProtocolError;
220+
221+
fn encode(&mut self, item: BorrowedFrame, dst: &mut BytesMut) -> Result<(), Self::Error> {
222+
resp2_encode_borrowed(dst, &item, self.int_as_bulkstring)
223+
.map(|_| ())
224+
.map_err(RedisProtocolError::from)
179225
}
180226
}
181227

0 commit comments

Comments
 (0)