ember_protocol/types.rs
1//! RESP3 frame types.
2//!
3//! The [`Frame`] enum represents a single parsed RESP3 value.
4//! Blob strings use `Bytes` for efficient, reference-counted storage
5//! that avoids unnecessary copies when moving data through the pipeline.
6
7use bytes::Bytes;
8
9/// A single RESP3 protocol frame.
10///
11/// Covers the core types needed for basic Redis-compatible commands:
12/// strings, errors, integers, bulk data, arrays, null, and maps.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Frame {
15 /// Simple string response, e.g. `+OK\r\n`.
16 /// Used for short, non-binary status replies.
17 Simple(String),
18
19 /// Error response, e.g. `-ERR unknown command\r\n`.
20 Error(String),
21
22 /// 64-bit signed integer, e.g. `:42\r\n`.
23 Integer(i64),
24
25 /// Bulk (binary-safe) string, e.g. `$5\r\nhello\r\n`.
26 /// Uses `Bytes` for zero-copy-friendly handling.
27 Bulk(Bytes),
28
29 /// Ordered array of frames, e.g. `*2\r\n+hello\r\n+world\r\n`.
30 Array(Vec<Frame>),
31
32 /// Null value, e.g. `_\r\n`.
33 Null,
34
35 /// Ordered map of key-value frame pairs, e.g. `%1\r\n+key\r\n+val\r\n`.
36 ///
37 /// Uses `Vec` instead of `HashMap` to preserve insertion order (RESP3
38 /// maps are ordered) and because typical maps in Redis responses are
39 /// small enough that linear scan beats hashing overhead.
40 Map(Vec<(Frame, Frame)>),
41}
42
43/// Pre-computed wire bytes for the most common responses.
44/// Writing these directly to the output buffer avoids per-field
45/// serialization overhead.
46pub mod wire {
47 /// `+OK\r\n`
48 pub const OK: &[u8] = b"+OK\r\n";
49 /// `+PONG\r\n`
50 pub const PONG: &[u8] = b"+PONG\r\n";
51 /// `_\r\n`
52 pub const NULL: &[u8] = b"_\r\n";
53 /// `:0\r\n`
54 pub const ZERO: &[u8] = b":0\r\n";
55 /// `:1\r\n`
56 pub const ONE: &[u8] = b":1\r\n";
57 /// `:-1\r\n`
58 pub const NEG_ONE: &[u8] = b":-1\r\n";
59}
60
61impl Frame {
62 /// Returns `true` if this frame is a null value.
63 pub fn is_null(&self) -> bool {
64 matches!(self, Frame::Null)
65 }
66}
67
68#[cfg(test)]
69mod tests {
70 use super::*;
71
72 #[test]
73 fn frame_equality() {
74 assert_eq!(Frame::Simple("OK".into()), Frame::Simple("OK".into()));
75 assert_ne!(Frame::Simple("OK".into()), Frame::Simple("ERR".into()));
76 assert_eq!(Frame::Integer(42), Frame::Integer(42));
77 assert_eq!(Frame::Null, Frame::Null);
78 }
79
80 #[test]
81 fn is_null() {
82 assert!(Frame::Null.is_null());
83 assert!(!Frame::Simple("OK".into()).is_null());
84 assert!(!Frame::Integer(0).is_null());
85 }
86
87 #[test]
88 fn clone_bulk() {
89 let frame = Frame::Bulk(Bytes::from_static(b"hello"));
90 let cloned = frame.clone();
91 assert_eq!(frame, cloned);
92 }
93}