netget/server/redis/resp_value.rs
1//! Replies the server composes itself — pub/sub confirmations and messages, `PUBSUB`, and the
2//! `CLUSTER` introspection family — as a small typed tree, encoded for the connection's RESP
3//! version.
4//!
5//! The model-facing verbs encode JSON (`resp3.rs`), where a nested array in RESP2 has to fall
6//! back to JSON text. These shapes are fixed by Redis — `CLUSTER SLOTS` is an array of arrays,
7//! `CLUSTER SHARDS` an array of maps — so they need real nesting on both versions. Every tree
8//! built here is a literal shape a few levels deep; nothing a peer or the model sends decides
9//! its depth.
10
11/// One RESP value.
12#[derive(Debug, Clone, PartialEq)]
13pub enum Resp {
14 /// A bulk string (`$`).
15 Bulk(Vec<u8>),
16 /// A simple string (`+`).
17 Simple(String),
18 /// An integer (`:`).
19 Int(i64),
20 /// A null: `$-1` on RESP2, `_` on RESP3.
21 Null,
22 /// An array (`*`).
23 Array(Vec<Resp>),
24 /// A map (`%`); a flat key/value array on RESP2.
25 Map(Vec<(Resp, Resp)>),
26 /// An out-of-band push (`>`); on RESP2, where there is no push frame, an array — which is
27 /// exactly how Redis frames pub/sub messages there.
28 Push(Vec<Resp>),
29 /// Text a client shows as is: a RESP3 verbatim string (`=`, format `txt`), a bulk string on
30 /// RESP2. Redis answers `CLUSTER NODES` and `CLUSTER INFO` this way.
31 Verbatim(String),
32}
33
34impl Resp {
35 pub fn bulk(s: impl AsRef<[u8]>) -> Resp {
36 Resp::Bulk(s.as_ref().to_vec())
37 }
38
39 /// Encode for a connection speaking RESP `proto` (2 or 3).
40 pub fn encode(&self, proto: u8) -> Vec<u8> {
41 let mut out = Vec::new();
42 self.encode_into(proto, &mut out);
43 out
44 }
45
46 fn encode_into(&self, proto: u8, out: &mut Vec<u8>) {
47 use super::actions::{encode_bulk_string, encode_integer, encode_simple_string};
48 match self {
49 Resp::Bulk(b) => out.extend_from_slice(&encode_bulk_string(b)),
50 Resp::Simple(s) => out.extend_from_slice(&encode_simple_string(s)),
51 Resp::Int(i) => out.extend_from_slice(&encode_integer(*i)),
52 Resp::Null => out.extend_from_slice(&super::resp3::null(proto)),
53 Resp::Array(items) => {
54 out.extend_from_slice(format!("*{}\r\n", items.len()).as_bytes());
55 for item in items {
56 item.encode_into(proto, out);
57 }
58 }
59 Resp::Push(items) => {
60 let marker = if proto == 3 { '>' } else { '*' };
61 out.extend_from_slice(format!("{marker}{}\r\n", items.len()).as_bytes());
62 for item in items {
63 item.encode_into(proto, out);
64 }
65 }
66 Resp::Map(entries) => {
67 if proto == 3 {
68 out.extend_from_slice(format!("%{}\r\n", entries.len()).as_bytes());
69 } else {
70 out.extend_from_slice(format!("*{}\r\n", entries.len() * 2).as_bytes());
71 }
72 for (k, v) in entries {
73 k.encode_into(proto, out);
74 v.encode_into(proto, out);
75 }
76 }
77 Resp::Verbatim(text) => {
78 if proto == 3 {
79 let body = format!("txt:{text}");
80 out.extend_from_slice(format!("={}\r\n", body.len()).as_bytes());
81 out.extend_from_slice(body.as_bytes());
82 out.extend_from_slice(b"\r\n");
83 } else {
84 out.extend_from_slice(&encode_bulk_string(text.as_bytes()));
85 }
86 }
87 }
88 }
89}