Skip to main content

Crate yo_resp

Crate yo_resp 

Source
Expand description

The RESP2 and RESP3 codec.

Requests come in as ranges into the connection’s own read buffer, and replies go out as the bytes that go on the socket. Nothing in between is materialised, because everything in between is what the lineage’s profiles kept finding at the top.

§Reading

Argv decodes commands. It is per connection, it remembers where it got to when a command arrives in pieces, and after the first few commands it stops allocating. Multibulk and inline requests both land in the same place, so the command layer never learns which one a client used.

use yo_resp::{Argv, Limits, Step};

let buf = b"*3\r\n$3\r\nSET\r\n$1\r\nk\r\n$1\r\nv\r\n";
let mut argv = Argv::new();
match argv.decode(buf, &Limits::default())? {
    Step::Command { consumed } => {
        assert_eq!(consumed, buf.len());
        assert_eq!(argv.arg(buf, 0), Some(&b"SET"[..]));
        assert_eq!(argv.arg(buf, 2), Some(&b"v"[..]));
    }
    Step::Incomplete => unreachable!("the whole command is here"),
}

§Writing

Out is the reply buffer, and it knows which protocol the connection is speaking. A command writes the richer form once and the RESP2 downgrade happens here rather than in the command:

use yo_resp::{Out, Proto};

fn hgetall(out: &mut Out) {
    out.map(1);
    out.bulk(b"field");
    out.bulk(b"value");
}

let mut two = Out::new(Proto::Resp2);
hgetall(&mut two);
assert_eq!(two.as_slice(), b"*2\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");

let mut three = Out::new(Proto::Resp3);
hgetall(&mut three);
assert_eq!(three.as_slice(), b"%1\r\n$5\r\nfield\r\n$5\r\nvalue\r\n");

§Reading replies

frame decodes a reply into a borrowed Frame. The server has no use for it. The replication client, the differential harness and this crate’s own round trip tests do.

§Running a command

dispatch is the layer above both halves. It looks a command name up, checks its arity, and calls the same yo-kv method the embedded API calls, which is the placement rule Y23 is about: one implementation of INCR, two ways to reach it.

use yo_resp::{Argv, Limits, Out, Proto};
use yo_resp::dispatch::{Args, Server, Session, execute};

let mut server = Server::new();
let mut session = Session::new(1);
let mut out = Out::new(Proto::Resp2);
let wire = b"*1\r\n$4\r\nPING\r\n";
let mut argv = Argv::new();
argv.decode(wire, &Limits::default())?;
execute(&mut server, &mut session, Args::new(&argv, wire), &mut out);
assert_eq!(out.as_slice(), b"+PONG\r\n");

§Driving it from the loop

engine is the piece between the two: connections, read buffers, framing and one write per connection per batch, put on yo_reactor::Engine so the loop can run commands without knowing what a command is. It is where a server becomes possible, and it works over anything that implements engine::Sink, which is a socket in production and a Vec in a test.

use yo_reactor::Reactor;
use yo_resp::engine::{Recorder, Wire, pump};

let mut r = Reactor::inline(Wire::new(Recorder::new()));
let conn = r.engine_mut().accept();

r.engine_mut().feed(conn, b"*1\r\n$4\r\nPING\r\n");
pump(&mut r, &mut Vec::new());
assert_eq!(r.engine().sink().sent(conn), b"+PONG\r\n");

§What is not here

Sockets. This crate turns bytes into arguments, runs them, turns values into bytes, and says which connection they belong to. Reading and writing the bytes themselves is the ring’s job, and 04 section 7 owns the ring.

Re-exports§

pub use engine::Cmd;
pub use engine::ConnId;
pub use engine::Sink;
pub use engine::Wire;
pub use error::ProtocolError;
pub use frame::Frame;
pub use proto::Limits;
pub use proto::Proto;
pub use reply::Out;
pub use request::Argv;
pub use request::Step;

Modules§

dispatch
From a decoded command to a written reply.
engine
Connections, framing and buffers: the seam between the loop and the commands.
error
What the codec refuses, and the exact words it refuses it in.
frame
Replies in: the general decoder, for the side of the wire that reads them.
num
Redis’s own integer and double text, shared with the string type.
proto
The protocol version a connection is speaking, and the limits it is held to.
reply
Replies out: wire bytes, written once.
request
Requests in: the multibulk decoder and the inline decoder.