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;
116mod front;
117pub mod proto;
118pub mod reply;
119pub mod request;
120
121pub use cap::Cap;
122pub use engine::{Cmd, ConnId, Sink, Wire};
123pub use error::ProtocolError;
124pub use frame::Frame;
125pub use proto::{Limits, Proto};
126pub use reply::Out;
127pub use request::{Argv, Step};
128/// Redis's own integer and double text, shared with the string type.
129///
130/// This module lives in `yo-common` because the codec is not the only thing
131/// that needs it: whether a string is stored int encoded is decided by the same
132/// `string2ll` rules that decide whether a bulk length parses. Re-exported here
133/// so that `yo_resp::num` keeps meaning what it meant.
134pub use yo_common::num;
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139
140 /// The shape a connection actually runs: read some bytes, decode what is
141 /// whole, reply to each, keep the remainder. Written here rather than in
142 /// either half because it is the only test that exercises both against each
143 /// other in the order the reactor will.
144 #[test]
145 fn a_connection_reads_commands_and_writes_replies() {
146 // Two whole commands and the front of a third, which is what a read
147 // that lands in the middle of a pipeline looks like.
148 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";
149
150 let mut argv = Argv::new();
151 let mut out = Out::new(Proto::Resp2);
152 let mut at = 0;
153 loop {
154 match argv.decode(&wire[at..], &Limits::default()).unwrap() {
155 Step::Incomplete => break,
156 Step::Command { consumed } => {
157 let buf = &wire[at..];
158 match argv.arg(buf, 0) {
159 Some(b"PING") => out.simple(b"PONG"),
160 Some(b"GET") => out.nil(),
161 _ => out.error(b"ERR unknown command"),
162 }
163 at += consumed;
164 }
165 }
166 }
167
168 assert_eq!(out.as_slice(), b"+PONG\r\n$-1\r\n");
169 // The partial third command is still waiting, and it is waiting at the
170 // right place: everything before it has been accounted for.
171 assert_eq!(&wire[at..], b"*2\r\n$3\r\nGE");
172 }
173
174 /// The same exchange on RESP3, where only the null is spelled differently.
175 /// The command bodies above did not change and that is the point.
176 #[test]
177 fn the_same_replies_come_out_in_resp3_spelling() {
178 let mut out = Out::new(Proto::Resp3);
179 out.simple(b"PONG");
180 out.nil();
181 assert_eq!(out.as_slice(), b"+PONG\r\n_\r\n");
182 }
183}