More comments and tweak details (#33)
Co-authored-by: Alice Ryhl <alice@ryhl.io>
This commit is contained in:
@@ -20,10 +20,10 @@ impl Get {
|
||||
Get { key: key.to_string() }
|
||||
}
|
||||
|
||||
/// Parse a `Get` instance from received data.
|
||||
/// Parse a `Get` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor like API to read fields from a
|
||||
/// received `Frame`. At this point, the data has already been received from
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `GET` string has already been consumed.
|
||||
|
||||
@@ -110,7 +110,7 @@ impl Command {
|
||||
Command::Set(_) => "set",
|
||||
Command::Subscribe(_) => "subscribe",
|
||||
Command::Unsubscribe(_) => "unsubscribe",
|
||||
Command::Unknown(cmd) => &cmd.command_name,
|
||||
Command::Unknown(cmd) => cmd.get_name(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,15 +2,59 @@ use crate::{Connection, Db, Frame, Parse};
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
/// Posts a message to the given channel.
|
||||
///
|
||||
/// Send a message into a channel without any knowledge of individual consumers.
|
||||
/// Consumers may subscribe to channels in order to receive the messages.
|
||||
///
|
||||
/// Channel names have no relation to the key-value namespace. Publishing on a
|
||||
/// channel named "foo" has no relation to setting the "foo" key.
|
||||
#[derive(Debug)]
|
||||
pub struct Publish {
|
||||
pub(crate) channel: String,
|
||||
pub(crate) message: Bytes,
|
||||
/// Name of the channel on which the message should be published.
|
||||
channel: String,
|
||||
|
||||
/// The message to publish.
|
||||
message: Bytes,
|
||||
}
|
||||
|
||||
impl Publish {
|
||||
/// Create a new `Publish` command which sends `message` on `channel`.
|
||||
pub(crate) fn new(channel: impl ToString, message: Bytes) -> Publish {
|
||||
Publish {
|
||||
channel: channel.to_string(),
|
||||
message,
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `Publish` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `PUBLISH` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the `Publish` value is returned. If the frame is malformed,
|
||||
/// `Err` is returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing three entries.
|
||||
///
|
||||
/// ```text
|
||||
/// PUBLISH channel message
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result<Publish> {
|
||||
// The `PUBLISH` string has already been consumed. Extract the `channel`
|
||||
// and `message` values from the frame.
|
||||
//
|
||||
// The `channel` must be a valid string.
|
||||
let channel = parse.next_string()?;
|
||||
|
||||
// The `message` is arbitrary bytes.
|
||||
let message = parse.next_bytes()?;
|
||||
|
||||
Ok(Publish { channel, message })
|
||||
@@ -21,14 +65,31 @@ impl Publish {
|
||||
/// The response is written to `dst`. This is called by the server in order
|
||||
/// to execute a received command.
|
||||
pub(crate) async fn apply(self, db: &Db, dst: &mut Connection) -> crate::Result<()> {
|
||||
// Set the value
|
||||
// The shared state contains the `tokio::sync::broadcast::Sender` for
|
||||
// all active channels. Calling `db.publish` dispatches the message into
|
||||
// the appropriate channel.
|
||||
//
|
||||
// The number of subscribers currently listening on the channel is
|
||||
// returned. This does not mean that `num_subscriber` channels will
|
||||
// receive the message. Subscribers may drop before receiving the
|
||||
// message. Given this, `num_subscribers` should only be used as a
|
||||
// "hint".
|
||||
let num_subscribers = db.publish(&self.channel, self.message);
|
||||
|
||||
// The number of subscribers is returned as the response to the publish
|
||||
// request.
|
||||
let response = Frame::Integer(num_subscribers as u64);
|
||||
|
||||
// Write the frame to the client.
|
||||
dst.write_frame(&response).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding a `Publish` command to send
|
||||
/// to the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("publish".as_bytes()));
|
||||
|
||||
@@ -42,10 +42,10 @@ impl Set {
|
||||
}
|
||||
}
|
||||
|
||||
/// Parse a `Set` instance from received data.
|
||||
/// Parse a `Set` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor like API to read fields from a
|
||||
/// received `Frame`. At this point, the data has already been received from
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `SET` string has already been consumed.
|
||||
|
||||
@@ -5,27 +5,75 @@ use bytes::Bytes;
|
||||
use tokio::select;
|
||||
use tokio::stream::{StreamExt, StreamMap};
|
||||
|
||||
/// Subscribes the client to one or more channels.
|
||||
///
|
||||
/// Once the client enters the subscribed state, it is not supposed to issue any
|
||||
/// other commands, except for additional SUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE,
|
||||
/// PUNSUBSCRIBE, PING and QUIT commands.
|
||||
#[derive(Debug)]
|
||||
pub struct Subscribe {
|
||||
pub(crate) channels: Vec<String>,
|
||||
channels: Vec<String>,
|
||||
}
|
||||
|
||||
/// Unsubscribes the client from one or more channels.
|
||||
///
|
||||
/// When no channels are specified, the client is unsubscribed from all the
|
||||
/// previously subscribed channels.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Unsubscribe {
|
||||
pub(crate) channels: Vec<String>,
|
||||
channels: Vec<String>,
|
||||
}
|
||||
|
||||
impl Subscribe {
|
||||
/// Creates a new `Subscribe` command to listen on the specified channels.
|
||||
pub(crate) fn new(channels: &[String]) -> Subscribe {
|
||||
Subscribe { channels: channels.to_vec() }
|
||||
}
|
||||
|
||||
/// Parse a `Subscribe` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `SUBSCRIBE` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the `Subscribe` value is returned. If the frame is
|
||||
/// malformed, `Err` is returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing two or more entries.
|
||||
///
|
||||
/// ```text
|
||||
/// SUBSCRIBE channel [channel ...]
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> crate::Result<Subscribe> {
|
||||
use ParseError::EndOfStream;
|
||||
|
||||
// There must be at least one channel
|
||||
// The `SUBSCRIBE` string has already been consumed. At this point,
|
||||
// there is one or more strings remaining in `parse`. These represent
|
||||
// the channels to subscribe to.
|
||||
//
|
||||
// Extract the first string. If there is none, the the frame is
|
||||
// malformed and the error is bubbled up.
|
||||
let mut channels = vec![parse.next_string()?];
|
||||
|
||||
// Now, the remainder of the frame is consumed. Each value must be a
|
||||
// string or the frame is malformed. Once all values in the frame have
|
||||
// been consumed, the command is fully parsed.
|
||||
loop {
|
||||
match parse.next_string() {
|
||||
// A string has been consumed from the `parse`, push it into the
|
||||
// list of channels to subscribe to.
|
||||
Ok(s) => channels.push(s),
|
||||
// The `EndOfStream` error indicates there is no further data to
|
||||
// parse.
|
||||
Err(EndOfStream) => break,
|
||||
// All other errors are bubbled up, resulting in the connection
|
||||
// being terminated.
|
||||
Err(err) => return Err(err.into()),
|
||||
}
|
||||
}
|
||||
@@ -87,11 +135,18 @@ impl Subscribe {
|
||||
select! {
|
||||
// Receive messages from subscribed channels
|
||||
Some((channel, msg)) = subscriptions.next() => {
|
||||
use tokio::sync::broadcast::RecvError;
|
||||
|
||||
let msg = match msg {
|
||||
Ok(msg) => msg,
|
||||
Err(RecvError::Lagged(_)) => continue,
|
||||
Err(RecvError::Closed) => unreachable!(),
|
||||
};
|
||||
|
||||
let mut response = Frame::array();
|
||||
response.push_bulk(Bytes::from_static(b"message"));
|
||||
response.push_bulk(Bytes::copy_from_slice(channel.as_bytes()));
|
||||
// TODO: handle lag error
|
||||
response.push_bulk(msg.unwrap());
|
||||
response.push_bulk(msg);
|
||||
|
||||
dst.write_frame(&response).await?;
|
||||
}
|
||||
@@ -149,6 +204,10 @@ impl Subscribe {
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding a `Subscribe` command to send
|
||||
/// to the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("subscribe".as_bytes()));
|
||||
@@ -160,16 +219,50 @@ impl Subscribe {
|
||||
}
|
||||
|
||||
impl Unsubscribe {
|
||||
/// Create a new `Unsubscribe` command with the given `channels`.
|
||||
pub(crate) fn new(channels: &[String]) -> Unsubscribe {
|
||||
Unsubscribe { channels: channels.to_vec() }
|
||||
}
|
||||
|
||||
/// Parse a `Unsubscribe` instance from a received frame.
|
||||
///
|
||||
/// The `Parse` argument provides a cursor-like API to read fields from the
|
||||
/// `Frame`. At this point, the entire frame has already been received from
|
||||
/// the socket.
|
||||
///
|
||||
/// The `UNSUBSCRIBE` string has already been consumed.
|
||||
///
|
||||
/// # Returns
|
||||
///
|
||||
/// On success, the `Unsubscribe` value is returned. If the frame is
|
||||
/// malformed, `Err` is returned.
|
||||
///
|
||||
/// # Format
|
||||
///
|
||||
/// Expects an array frame containing at least one entry.
|
||||
///
|
||||
/// ```text
|
||||
/// UNSUBSCRIBE [channel [channel ...]]
|
||||
/// ```
|
||||
pub(crate) fn parse_frames(parse: &mut Parse) -> Result<Unsubscribe, ParseError> {
|
||||
use ParseError::EndOfStream;
|
||||
|
||||
// There may be no channels listed.
|
||||
// There may be no channels listed, so start with an empty vec.
|
||||
let mut channels = vec![];
|
||||
|
||||
// Each entry in the frame must be a string or the frame is malformed.
|
||||
// Once all values in the frame have been consumed, the command is fully
|
||||
// parsed.
|
||||
loop {
|
||||
match parse.next_string() {
|
||||
// A string has been consumed from the `parse`, push it into the
|
||||
// list of channels to unsubscribe from.
|
||||
Ok(s) => channels.push(s),
|
||||
// The `EndOfStream` error indicates there is no further data to
|
||||
// parse.
|
||||
Err(EndOfStream) => break,
|
||||
// All other errors are bubbled up, resulting in the connection
|
||||
// being terminated.
|
||||
Err(err) => return Err(err),
|
||||
}
|
||||
}
|
||||
@@ -177,12 +270,18 @@ impl Unsubscribe {
|
||||
Ok(Unsubscribe { channels })
|
||||
}
|
||||
|
||||
/// Converts the command into an equivalent `Frame`.
|
||||
///
|
||||
/// This is called by the client when encoding an `Unsubscribe` command to
|
||||
/// send to the server.
|
||||
pub(crate) fn into_frame(self) -> Frame {
|
||||
let mut frame = Frame::array();
|
||||
frame.push_bulk(Bytes::from("unsubscribe".as_bytes()));
|
||||
|
||||
for channel in self.channels {
|
||||
frame.push_bulk(Bytes::from(channel.into_bytes()));
|
||||
}
|
||||
|
||||
frame
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,9 +2,10 @@ use crate::{Connection, Frame};
|
||||
|
||||
use tracing::{debug, instrument};
|
||||
|
||||
/// Represents an "unknown" command. This is not a real `Redis` command.
|
||||
#[derive(Debug)]
|
||||
pub struct Unknown {
|
||||
pub command_name: String,
|
||||
command_name: String,
|
||||
}
|
||||
|
||||
impl Unknown {
|
||||
@@ -14,6 +15,14 @@ impl Unknown {
|
||||
Unknown { command_name: key.to_string() }
|
||||
}
|
||||
|
||||
/// Returns the command name
|
||||
pub(crate) fn get_name(&self) -> &str {
|
||||
&self.command_name
|
||||
}
|
||||
|
||||
/// Responds to the client, indicating the command is not recognized.
|
||||
///
|
||||
/// This usually means the command is not yet implemented by `mini-redis`.
|
||||
#[instrument(skip(self, dst))]
|
||||
pub(crate) async fn apply(self, dst: &mut Connection) -> crate::Result<()> {
|
||||
let response = Frame::Error(format!("ERR unknown command '{}'", self.command_name));
|
||||
|
||||
Reference in New Issue
Block a user