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/// Append the base-10 representation of `n` without allocating an intermediate
64/// String. Handles `i64::MIN` correctly.
65fn push_int(out: &mut Vec<u8>, n: i64) {
66    if n == 0 {
67        out.push(b'0');
68        return;
69    }
70    let mut tmp = [0u8; 20];
71    let mut i = tmp.len();
72    // Work in the negative domain so i64::MIN doesn't overflow.
73    let neg = n < 0;
74    let mut v = n;
75    while v != 0 {
76        let digit = (v % 10).unsigned_abs() as u8;
77        i -= 1;
78        tmp[i] = b'0' + digit;
79        v /= 10;
80    }
81    if neg {
82        out.push(b'-');
83    }
84    out.extend_from_slice(&tmp[i..]);
85}
86
87#[cfg(test)]
88mod tests {
89    use super::*;
90
91    #[test]
92    fn encoders_match_resp2() {
93        let mut out = Vec::new();
94        encode_simple_string(&mut out, "PONG");
95        assert_eq!(out, b"+PONG\r\n");
96
97        out.clear();
98        encode_bulk(&mut out, b"hello");
99        assert_eq!(out, b"$5\r\nhello\r\n");
100
101        out.clear();
102        encode_error(&mut out, "ERR nope");
103        assert_eq!(out, b"-ERR nope\r\n");
104
105        out.clear();
106        encode_integer(&mut out, -1234);
107        assert_eq!(out, b":-1234\r\n");
108
109        out.clear();
110        encode_integer(&mut out, i64::MIN);
111        assert_eq!(out, b":-9223372036854775808\r\n");
112
113        out.clear();
114        encode_null_bulk(&mut out);
115        assert_eq!(out, b"$-1\r\n");
116    }
117}