Skip to main content

kevy_resp/
reply_encode.rs

1//! Reply encoders: append RESP2-shaped bytes to a caller-owned `Vec<u8>`. The
2//! caller (typically the reactor's per-cmd reply buffer) keeps amortised
3//! capacity across commands, so these functions never allocate beyond the
4//! initial reserve.
5
6/// `+<s>\r\n`
7pub fn encode_simple_string(out: &mut Vec<u8>, s: &str) {
8    out.push(b'+');
9    out.extend_from_slice(s.as_bytes());
10    out.extend_from_slice(b"\r\n");
11}
12
13/// `-<s>\r\n`
14pub fn encode_error(out: &mut Vec<u8>, s: &str) {
15    out.push(b'-');
16    out.extend_from_slice(s.as_bytes());
17    out.extend_from_slice(b"\r\n");
18}
19
20/// `:<n>\r\n`
21pub fn encode_integer(out: &mut Vec<u8>, n: i64) {
22    out.push(b':');
23    push_int(out, n);
24    out.extend_from_slice(b"\r\n");
25}
26
27/// `$<len>\r\n<data>\r\n`
28pub fn encode_bulk(out: &mut Vec<u8>, data: &[u8]) {
29    // Reserve the whole frame up front so a fresh reply buffer (the common case:
30    // dispatch hands each command an empty `Vec`) fills without repeated reallocs
31    // as it grows — the bulk reply is the hot GET path. 16 covers '$', the length
32    // digits, and both CRLFs.
33    out.reserve(data.len() + 16);
34    out.push(b'$');
35    push_int(out, data.len() as i64);
36    out.extend_from_slice(b"\r\n");
37    out.extend_from_slice(data);
38    out.extend_from_slice(b"\r\n");
39}
40
41/// `$-1\r\n` — the RESP2 null bulk string.
42pub fn encode_null_bulk(out: &mut Vec<u8>) {
43    out.extend_from_slice(b"$-1\r\n");
44}
45
46/// `*<len>\r\n` — an array header; follow with `len` encoded elements.
47pub fn encode_array_len(out: &mut Vec<u8>, len: i64) {
48    out.push(b'*');
49    push_int(out, len);
50    out.extend_from_slice(b"\r\n");
51}
52
53/// Encode a command as a RESP multi-bulk request (client → server):
54/// `*N\r\n$len\r\n<arg>\r\n…`. The inverse of
55/// [`parse_command`](crate::parse_command).
56pub fn encode_command(out: &mut Vec<u8>, args: &[Vec<u8>]) {
57    encode_array_len(out, args.len() as i64);
58    for a in args {
59        encode_bulk(out, a);
60    }
61}
62
63/// Generic version of [`encode_command`] that takes any slice-of-bytes-
64/// like argv (`&[Vec<u8>]`, `&[&[u8]]`, `&[[u8; N]]`, …). Lets a Rust
65/// caller pass a stack-allocated `[&[u8]; N]` and skip the per-call
66/// `Vec<Vec<u8>>` argv allocation — measured win on the
67/// `kevy-client::Connection` single-connection path.
68pub fn encode_command_borrowed<A: AsRef<[u8]>>(out: &mut Vec<u8>, args: &[A]) {
69    encode_array_len(out, args.len() as i64);
70    for a in args {
71        encode_bulk(out, a.as_ref());
72    }
73}
74
75/// Append the base-10 representation of `n` without allocating an intermediate
76/// String. Handles `i64::MIN` correctly.
77fn push_int(out: &mut Vec<u8>, n: i64) {
78    if n == 0 {
79        out.push(b'0');
80        return;
81    }
82    let mut tmp = [0u8; 20];
83    let mut i = tmp.len();
84    // Work in the negative domain so i64::MIN doesn't overflow.
85    let neg = n < 0;
86    let mut v = n;
87    while v != 0 {
88        let digit = (v % 10).unsigned_abs() as u8;
89        i -= 1;
90        tmp[i] = b'0' + digit;
91        v /= 10;
92    }
93    if neg {
94        out.push(b'-');
95    }
96    out.extend_from_slice(&tmp[i..]);
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn encoders_match_resp2() {
105        let mut out = Vec::new();
106        encode_simple_string(&mut out, "PONG");
107        assert_eq!(out, b"+PONG\r\n");
108
109        out.clear();
110        encode_bulk(&mut out, b"hello");
111        assert_eq!(out, b"$5\r\nhello\r\n");
112
113        out.clear();
114        encode_error(&mut out, "ERR nope");
115        assert_eq!(out, b"-ERR nope\r\n");
116
117        out.clear();
118        encode_integer(&mut out, -1234);
119        assert_eq!(out, b":-1234\r\n");
120
121        out.clear();
122        encode_integer(&mut out, i64::MIN);
123        assert_eq!(out, b":-9223372036854775808\r\n");
124
125        out.clear();
126        encode_null_bulk(&mut out);
127        assert_eq!(out, b"$-1\r\n");
128    }
129}