Skip to main content

yo_resp/
lib.rs

1//! The RESP2 and RESP3 codec.
2//!
3//! Requests come in as ranges into the connection's own read buffer, and
4//! replies go out as the bytes that go on the socket. Nothing in between is
5//! materialised, because everything in between is what the lineage's profiles
6//! kept finding at the top.
7//!
8//! # Reading
9//!
10//! [`Argv`] decodes commands. It is per connection, it remembers where it got
11//! to when a command arrives in pieces, and after the first few commands it
12//! stops allocating. Multibulk and inline requests both land in the same place,
13//! so the command layer never learns which one a client used.
14//!
15//! ```
16//! use yo_resp::{Argv, Limits, Step};
17//!
18//! let buf = b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n";
19//! let mut argv = Argv::new();
20//! match argv.decode(buf, &Limits::default())? {
21//!     Step::Command { consumed } => {
22//!         assert_eq!(consumed, buf.len());
23//!         assert_eq!(argv.arg(buf, 0), Some(&b"SET"[..]));
24//!         assert_eq!(argv.arg(buf, 2), Some(&b"v"[..]));
25//!     }
26//!     Step::Incomplete => unreachable!("the whole command is here"),
27//! }
28//! # Ok::<(), yo_resp::ProtocolError>(())
29//! ```
30//!
31//! # Writing
32//!
33//! [`Out`] is the reply buffer, and it knows which protocol the connection is
34//! speaking. A command writes the richer form once and the RESP2 downgrade
35//! happens here rather than in the command:
36//!
37//! ```
38//! use yo_resp::{Out, Proto};
39//!
40//! fn hgetall(out: &mut Out) {
41//!     out.map(1);
42//!     out.bulk(b"field");
43//!     out.bulk(b"value");
44//! }
45//!
46//! let mut two = Out::new(Proto::Resp2);
47//! hgetall(&mut two);
48//! assert_eq!(two.as_slice(), b"*2\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");
49//!
50//! let mut three = Out::new(Proto::Resp3);
51//! hgetall(&mut three);
52//! assert_eq!(three.as_slice(), b"%1\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");
53//! ```
54//!
55//! # Reading replies
56//!
57//! [`frame`] decodes a reply into a borrowed [`Frame`]. The server has no use
58//! for it. The replication client, the differential harness and this crate's
59//! own round trip tests do.
60//!
61//! # Running a command
62//!
63//! [`dispatch`] is the layer above both halves. It looks a command name up,
64//! checks its arity, and calls the same `yo-kv` method the embedded API calls,
65//! which is the placement rule Y23 is about: one implementation of `INCR`, two
66//! ways to reach it.
67//!
68//! ```
69//! use yo_resp::{Argv, Limits, Out, Proto};
70//! use yo_resp::dispatch::{Args, Server, Session, execute};
71//!
72//! let mut server = Server::new();
73//! let mut session = Session::new(1);
74//! let mut out = Out::new(Proto::Resp2);
75//! let wire = b"*1\r\n$4\r\nPING\r\n";
76//! let mut argv = Argv::new();
77//! argv.decode(wire, &Limits::default())?;
78//! execute(&mut server, &mut session, Args::new(&argv, wire), &mut out);
79//! assert_eq!(out.as_slice(), b"+PONG\r\n");
80//! # Ok::<(), yo_resp::ProtocolError>(())
81//! ```
82//!
83//! # Driving it from the loop
84//!
85//! [`engine`] is the piece between the two: connections, read buffers, framing
86//! and one write per connection per batch, put on `yo_reactor::Engine` so the
87//! loop can run commands without knowing what a command is. It is where a
88//! server becomes possible, and it works over anything that implements
89//! [`engine::Sink`], which is a socket in production and a `Vec` in a test.
90//!
91//! ```
92//! use yo_reactor::Reactor;
93//! use yo_resp::engine::{Recorder, Wire, pump};
94//!
95//! let mut r = Reactor::inline(Wire::new(Recorder::new()));
96//! let conn = r.engine_mut().accept();
97//!
98//! r.engine_mut().feed(conn, b"*1\r\n$4\r\nPING\r\n");
99//! pump(&mut r, &mut Vec::new());
100//! assert_eq!(r.engine().sink().sent(conn), b"+PONG\r\n");
101//! ```
102//!
103//! # What is not here
104//!
105//! Sockets. This crate turns bytes into arguments, runs them, turns values into
106//! bytes, and says which connection they belong to. Reading and writing the
107//! bytes themselves is the ring's job, and `04` section 7 owns the ring.
108
109#![deny(missing_docs)]
110
111pub mod cap;
112pub mod dispatch;
113pub mod engine;
114pub mod error;
115pub mod frame;
116pub mod proto;
117pub mod reply;
118pub mod request;
119
120pub use cap::Cap;
121pub use engine::{Cmd, ConnId, Sink, Wire};
122pub use error::ProtocolError;
123pub use frame::Frame;
124pub use proto::{Limits, Proto};
125pub use reply::Out;
126pub use request::{Argv, Step};
127/// Redis's own integer and double text, shared with the string type.
128///
129/// This module lives in `yo-common` because the codec is not the only thing
130/// that needs it: whether a string is stored int encoded is decided by the same
131/// `string2ll` rules that decide whether a bulk length parses. Re-exported here
132/// so that `yo_resp::num` keeps meaning what it meant.
133pub use yo_common::num;
134
135#[cfg(test)]
136mod tests {
137    use super::*;
138
139    /// The shape a connection actually runs: read some bytes, decode what is
140    /// whole, reply to each, keep the remainder. Written here rather than in
141    /// either half because it is the only test that exercises both against each
142    /// other in the order the reactor will.
143    #[test]
144    fn a_connection_reads_commands_and_writes_replies() {
145        // Two whole commands and the front of a third, which is what a read
146        // that lands in the middle of a pipeline looks like.
147        let wire = b"*1\r\n$4\r\nPING\r\n*2\r\n$3\r\nGET\r\n$7\r\nmissing\r\n*2\r\n$3\r\nGE";
148
149        let mut argv = Argv::new();
150        let mut out = Out::new(Proto::Resp2);
151        let mut at = 0;
152        loop {
153            match argv.decode(&wire[at..], &Limits::default()).unwrap() {
154                Step::Incomplete => break,
155                Step::Command { consumed } => {
156                    let buf = &wire[at..];
157                    match argv.arg(buf, 0) {
158                        Some(b"PING") => out.simple(b"PONG"),
159                        Some(b"GET") => out.nil(),
160                        _ => out.error(b"ERR unknown command"),
161                    }
162                    at += consumed;
163                }
164            }
165        }
166
167        assert_eq!(out.as_slice(), b"+PONG\r\n$-1\r\n");
168        // The partial third command is still waiting, and it is waiting at the
169        // right place: everything before it has been accounted for.
170        assert_eq!(&wire[at..], b"*2\r\n$3\r\nGE");
171    }
172
173    /// The same exchange on RESP3, where only the null is spelled differently.
174    /// The command bodies above did not change and that is the point.
175    #[test]
176    fn the_same_replies_come_out_in_resp3_spelling() {
177        let mut out = Out::new(Proto::Resp3);
178        out.simple(b"PONG");
179        out.nil();
180        assert_eq!(out.as_slice(), b"+PONG\r\n_\r\n");
181    }
182}