Skip to content

Commit dc62112

Browse files
authored
Merge pull request #19 from Miaxos/feat-add-hello
FEAT: Add hello command
2 parents 92cb31c + 0f5b41d commit dc62112

File tree

6 files changed

+168
-2
lines changed

6 files changed

+168
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
use bytes::Bytes;
2+
use indexmap::IndexMap;
3+
4+
use super::CommandExecution;
5+
use crate::application::server::cmd::Parse;
6+
use crate::application::server::connection::WriteConnection;
7+
use crate::application::server::context::Context;
8+
use crate::application::server::frame::Frame;
9+
10+
/// Switch to a different protocol, optionally authenticating and setting the
11+
/// connection's name, or provide a contextual client report.
12+
///
13+
/// HELLO always replies with a list of current server and connection
14+
/// properties, such as: versions, modules loaded, client ID, replication role
15+
/// and so forth.
16+
///
17+
/// In Roster we only reply in RESP 3.
18+
#[derive(Debug, Default)]
19+
pub struct Hello {}
20+
21+
impl Hello {
22+
pub fn new() -> Hello {
23+
Hello {}
24+
}
25+
26+
pub(crate) fn parse_frames(parse: &mut Parse) -> anyhow::Result<Hello> {
27+
parse.finish()?;
28+
Ok(Hello::new())
29+
}
30+
}
31+
32+
impl CommandExecution for Hello {
33+
async fn apply(
34+
self,
35+
dst: &mut WriteConnection,
36+
ctx: Context,
37+
) -> anyhow::Result<()> {
38+
let id = ctx.connection.id();
39+
40+
let map = IndexMap::from_iter([
41+
(
42+
Frame::Bulk(Bytes::from_static(b"server")),
43+
Frame::Bulk(Bytes::from_static(b"roster")),
44+
),
45+
(
46+
Frame::Bulk(Bytes::from_static(b"version")),
47+
Frame::Bulk(Bytes::from_static(crate::VERSION.as_bytes())),
48+
),
49+
(Frame::Bulk(Bytes::from_static(b"proto")), Frame::Integer(3)),
50+
(Frame::Bulk(Bytes::from_static(b"id")), Frame::Integer(id)),
51+
(
52+
Frame::Bulk(Bytes::from_static(b"mode")),
53+
Frame::Bulk(Bytes::from_static(b"standalone")),
54+
),
55+
(
56+
Frame::Bulk(Bytes::from_static(b"role")),
57+
Frame::Bulk(Bytes::from_static(b"undefined")),
58+
),
59+
(
60+
Frame::Bulk(Bytes::from_static(b"modules")),
61+
Frame::Array(Vec::new()),
62+
),
63+
]);
64+
65+
let response = Frame::Map(map);
66+
dst.write_frame(&response).await?;
67+
68+
Ok(())
69+
}
70+
}
71+
72+
#[cfg(test)]
73+
mod tests {
74+
use std::io::Cursor;
75+
76+
use bytes::BytesMut;
77+
use redis_async::resp::{RespCodec, RespValue};
78+
use redis_async::resp_array;
79+
use tokio_util::codec::Encoder;
80+
81+
use crate::application::server::cmd::Command;
82+
use crate::application::server::frame::Frame;
83+
84+
fn parse_cmd(obj: RespValue) -> anyhow::Result<Command> {
85+
let mut bytes = BytesMut::new();
86+
let mut codec = RespCodec;
87+
codec.encode(obj, &mut bytes).unwrap();
88+
89+
let mut bytes = Cursor::new(bytes.freeze());
90+
let frame = Frame::parse(&mut bytes)?;
91+
let client_list = Command::from_frame(frame)?;
92+
Ok(client_list)
93+
}
94+
95+
#[test]
96+
fn ensure_parsing() {
97+
let entry: RespValue = resp_array!["HELLO"];
98+
let client_cmd = parse_cmd(entry).unwrap();
99+
insta::assert_debug_snapshot!(client_cmd, @r###"
100+
Hello(
101+
Hello,
102+
)
103+
"###);
104+
}
105+
106+
#[test]
107+
fn ensure_parsing_too_much() {
108+
let entry: RespValue = resp_array!["HELLO", "BLBL"];
109+
let client_cmd = parse_cmd(entry);
110+
assert!(client_cmd.is_err());
111+
let client_cmd = client_cmd.unwrap_err();
112+
insta::assert_debug_snapshot!(client_cmd, @r###"
113+
Other(
114+
"protocol error; expected end of frame, but there was more",
115+
)
116+
"###);
117+
}
118+
}

app/roster/src/application/server/cmd/mod.rs

+6
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use self::acl::Acl;
22
use self::client::Client;
33
use self::get::Get;
4+
use self::hello::Hello;
45
use self::parse::Parse;
56
use self::ping::Ping;
67
use self::set::Set;
@@ -14,6 +15,7 @@ mod parse;
1415
mod acl;
1516
mod client;
1617
mod get;
18+
mod hello;
1719
mod ping;
1820
mod set;
1921
mod unknown;
@@ -25,6 +27,7 @@ mod unknown;
2527
pub enum Command {
2628
Acl(Acl),
2729
Client(Client),
30+
Hello(Hello),
2831
Ping(Ping),
2932
Set(Set),
3033
Get(Get),
@@ -100,6 +103,7 @@ impl Command {
100103
return Client::from_parse(parse);
101104
}
102105
"ping" => Command::Ping(Ping::parse_frames(&mut parse)?),
106+
"hello" => Command::Hello(Hello::parse_frames(&mut parse)?),
103107
"set" => Command::Set(Set::parse_frames(&mut parse)?),
104108
"get" => Command::Get(Get::parse_frames(&mut parse)?),
105109
_ => {
@@ -136,6 +140,7 @@ impl CommandExecution for Command {
136140
Ping(cmd) => cmd.apply(dst, ctx).await,
137141
Unknown(cmd) => cmd.apply(dst, ctx).await,
138142
Client(cmd) => cmd.apply(dst, ctx).await,
143+
Hello(cmd) => cmd.apply(dst, ctx).await,
139144
Set(cmd) => cmd.apply(dst, ctx).await,
140145
Get(cmd) => cmd.apply(dst, ctx).await,
141146
}
@@ -149,6 +154,7 @@ impl CommandExecution for Command {
149154
Ping(cmd) => cmd.hash_key(),
150155
Unknown(cmd) => cmd.hash_key(),
151156
Client(cmd) => cmd.hash_key(),
157+
Hello(cmd) => cmd.hash_key(),
152158
Set(cmd) => cmd.hash_key(),
153159
Get(cmd) => cmd.hash_key(),
154160
}

app/roster/src/application/server/frame/write.rs

+17
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,21 @@ mod tests {
189189
write_frame(&mut v, &frame).await.unwrap();
190190
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""###);
191191
}
192+
193+
#[monoio::test]
194+
async fn simple_decimal_write_value_hashmap_string() {
195+
let mut v = TestUtilVec(Vec::new());
196+
let frame = Frame::Map(IndexMap::from_iter([
197+
(
198+
Frame::Simple(ByteString::from_static("first")),
199+
Frame::Simple(ByteString::from_static("one")),
200+
),
201+
(
202+
Frame::Simple(ByteString::from_static("second")),
203+
Frame::Integer(2),
204+
),
205+
]));
206+
write_frame(&mut v, &frame).await.unwrap();
207+
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""###);
208+
}
192209
}

app/roster/src/lib.rs

+7
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,10 @@ pub mod domain;
44
pub mod infrastructure;
55

66
pub use application::server::ServerConfigBuilder;
7+
8+
#[cfg(debug_assertions)]
9+
pub const VERSION: &str =
10+
concat!("(dev) ", env!("CARGO_PKG_VERSION"), "-", env!("GIT_HASH"),);
11+
12+
#[cfg(not(debug_assertions))]
13+
pub const VERSION: &str = env!("CARGO_PKG_VERSION");

app/roster/tests/hello.rs

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
mod utils;
2+
use std::collections::HashMap;
3+
4+
use redis_async::resp::RespValue;
5+
use redis_async::resp_array;
6+
7+
#[tokio::test]
8+
#[ignore = "redis-async doesn't support map from resp 3 properly"]
9+
pub async fn hello() {
10+
let addr = utils::start_simple_server();
11+
12+
let connection = utils::connect_without_auth(addr).await;
13+
14+
let _res_f: HashMap<String, RespValue> =
15+
connection.send(resp_array!["HELLO"]).await.unwrap();
16+
17+
assert!(false);
18+
}

docs/cmd_list.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# Redis Compatibility Commands list
22

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

66
If a command is not properly working, feel free to check the associated issue of
77
the command or open an issue.
@@ -152,7 +152,7 @@ the command or open an issue.
152152
- [ ] GETRANGE
153153
- [ ] GETSET
154154
- [ ] HDEL
155-
- [ ] HELLO
155+
- [x] HELLO
156156
- [ ] HEXISTS
157157
- [ ] HGET
158158
- [ ] HGETALL

0 commit comments

Comments
 (0)