Skip to content

Commit

Permalink
Merge pull request #19 from Miaxos/feat-add-hello
Browse files Browse the repository at this point in the history
FEAT: Add hello command
  • Loading branch information
Miaxos committed Feb 2, 2024
2 parents 92cb31c + 0f5b41d commit dc62112
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 2 deletions.
118 changes: 118 additions & 0 deletions app/roster/src/application/server/cmd/hello.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use bytes::Bytes;
use indexmap::IndexMap;

use super::CommandExecution;
use crate::application::server::cmd::Parse;
use crate::application::server::connection::WriteConnection;
use crate::application::server::context::Context;
use crate::application::server::frame::Frame;

/// Switch to a different protocol, optionally authenticating and setting the
/// connection's name, or provide a contextual client report.
///
/// HELLO always replies with a list of current server and connection
/// properties, such as: versions, modules loaded, client ID, replication role
/// and so forth.
///
/// In Roster we only reply in RESP 3.
#[derive(Debug, Default)]
pub struct Hello {}

impl Hello {
pub fn new() -> Hello {
Hello {}
}

pub(crate) fn parse_frames(parse: &mut Parse) -> anyhow::Result<Hello> {
parse.finish()?;
Ok(Hello::new())
}
}

impl CommandExecution for Hello {
async fn apply(
self,
dst: &mut WriteConnection,
ctx: Context,
) -> anyhow::Result<()> {
let id = ctx.connection.id();

let map = IndexMap::from_iter([
(
Frame::Bulk(Bytes::from_static(b"server")),
Frame::Bulk(Bytes::from_static(b"roster")),
),
(
Frame::Bulk(Bytes::from_static(b"version")),
Frame::Bulk(Bytes::from_static(crate::VERSION.as_bytes())),
),
(Frame::Bulk(Bytes::from_static(b"proto")), Frame::Integer(3)),
(Frame::Bulk(Bytes::from_static(b"id")), Frame::Integer(id)),
(
Frame::Bulk(Bytes::from_static(b"mode")),
Frame::Bulk(Bytes::from_static(b"standalone")),
),
(
Frame::Bulk(Bytes::from_static(b"role")),
Frame::Bulk(Bytes::from_static(b"undefined")),
),
(
Frame::Bulk(Bytes::from_static(b"modules")),
Frame::Array(Vec::new()),
),
]);

let response = Frame::Map(map);
dst.write_frame(&response).await?;

Ok(())
}
}

#[cfg(test)]
mod tests {
use std::io::Cursor;

use bytes::BytesMut;
use redis_async::resp::{RespCodec, RespValue};
use redis_async::resp_array;
use tokio_util::codec::Encoder;

use crate::application::server::cmd::Command;
use crate::application::server::frame::Frame;

fn parse_cmd(obj: RespValue) -> anyhow::Result<Command> {
let mut bytes = BytesMut::new();
let mut codec = RespCodec;
codec.encode(obj, &mut bytes).unwrap();

let mut bytes = Cursor::new(bytes.freeze());
let frame = Frame::parse(&mut bytes)?;
let client_list = Command::from_frame(frame)?;
Ok(client_list)
}

#[test]
fn ensure_parsing() {
let entry: RespValue = resp_array!["HELLO"];
let client_cmd = parse_cmd(entry).unwrap();
insta::assert_debug_snapshot!(client_cmd, @r###"
Hello(
Hello,
)
"###);
}

#[test]
fn ensure_parsing_too_much() {
let entry: RespValue = resp_array!["HELLO", "BLBL"];
let client_cmd = parse_cmd(entry);
assert!(client_cmd.is_err());
let client_cmd = client_cmd.unwrap_err();
insta::assert_debug_snapshot!(client_cmd, @r###"
Other(
"protocol error; expected end of frame, but there was more",
)
"###);
}
}
6 changes: 6 additions & 0 deletions app/roster/src/application/server/cmd/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use self::acl::Acl;
use self::client::Client;
use self::get::Get;
use self::hello::Hello;
use self::parse::Parse;
use self::ping::Ping;
use self::set::Set;
Expand All @@ -14,6 +15,7 @@ mod parse;
mod acl;
mod client;
mod get;
mod hello;
mod ping;
mod set;
mod unknown;
Expand All @@ -25,6 +27,7 @@ mod unknown;
pub enum Command {
Acl(Acl),
Client(Client),
Hello(Hello),
Ping(Ping),
Set(Set),
Get(Get),
Expand Down Expand Up @@ -100,6 +103,7 @@ impl Command {
return Client::from_parse(parse);
}
"ping" => Command::Ping(Ping::parse_frames(&mut parse)?),
"hello" => Command::Hello(Hello::parse_frames(&mut parse)?),
"set" => Command::Set(Set::parse_frames(&mut parse)?),
"get" => Command::Get(Get::parse_frames(&mut parse)?),
_ => {
Expand Down Expand Up @@ -136,6 +140,7 @@ impl CommandExecution for Command {
Ping(cmd) => cmd.apply(dst, ctx).await,
Unknown(cmd) => cmd.apply(dst, ctx).await,
Client(cmd) => cmd.apply(dst, ctx).await,
Hello(cmd) => cmd.apply(dst, ctx).await,
Set(cmd) => cmd.apply(dst, ctx).await,
Get(cmd) => cmd.apply(dst, ctx).await,
}
Expand All @@ -149,6 +154,7 @@ impl CommandExecution for Command {
Ping(cmd) => cmd.hash_key(),
Unknown(cmd) => cmd.hash_key(),
Client(cmd) => cmd.hash_key(),
Hello(cmd) => cmd.hash_key(),
Set(cmd) => cmd.hash_key(),
Get(cmd) => cmd.hash_key(),
}
Expand Down
17 changes: 17 additions & 0 deletions app/roster/src/application/server/frame/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -189,4 +189,21 @@ mod tests {
write_frame(&mut v, &frame).await.unwrap();
insta::assert_debug_snapshot!(String::from_utf8(v.0).unwrap(), @r###""%2\r\n+first\r\n:1\r\n+second\r\n:2\r\n""###);
}

#[monoio::test]
async fn simple_decimal_write_value_hashmap_string() {
let mut v = TestUtilVec(Vec::new());
let frame = Frame::Map(IndexMap::from_iter([
(
Frame::Simple(ByteString::from_static("first")),
Frame::Simple(ByteString::from_static("one")),
),
(
Frame::Simple(ByteString::from_static("second")),
Frame::Integer(2),
),
]));
write_frame(&mut v, &frame).await.unwrap();
insta::assert_debug_snapshot!(String::from_utf8(v.0).unwrap(), @r###""%2\r\n+first\r\n+one\r\n+second\r\n:2\r\n""###);
}
}
7 changes: 7 additions & 0 deletions app/roster/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ pub mod domain;
pub mod infrastructure;

pub use application::server::ServerConfigBuilder;

#[cfg(debug_assertions)]
pub const VERSION: &str =
concat!("(dev) ", env!("CARGO_PKG_VERSION"), "-", env!("GIT_HASH"),);

#[cfg(not(debug_assertions))]
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
18 changes: 18 additions & 0 deletions app/roster/tests/hello.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
mod utils;
use std::collections::HashMap;

use redis_async::resp::RespValue;
use redis_async::resp_array;

#[tokio::test]
#[ignore = "redis-async doesn't support map from resp 3 properly"]
pub async fn hello() {
let addr = utils::start_simple_server();

let connection = utils::connect_without_auth(addr).await;

let _res_f: HashMap<String, RespValue> =
connection.send(resp_array!["HELLO"]).await.unwrap();

assert!(false);
}
4 changes: 2 additions & 2 deletions docs/cmd_list.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Redis Compatibility Commands list

It only shows which commands are present in the code, not if the command is
properly working yet.
properly working yet. Let's say minimal features are available.

If a command is not properly working, feel free to check the associated issue of
the command or open an issue.
Expand Down Expand Up @@ -152,7 +152,7 @@ the command or open an issue.
- [ ] GETRANGE
- [ ] GETSET
- [ ] HDEL
- [ ] HELLO
- [x] HELLO
- [ ] HEXISTS
- [ ] HGET
- [ ] HGETALL
Expand Down

0 comments on commit dc62112

Please sign in to comment.