Skip to content

Commit

Permalink
Implemented abstraction for HGET command
Browse files Browse the repository at this point in the history
  • Loading branch information
marius-meissner committed Feb 3, 2024
1 parent 74e26af commit fd6459d
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 7 deletions.
22 changes: 15 additions & 7 deletions src/commands/get.rs
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,20 @@ impl GetResponse {

Some(result.unwrap())
}

/// Constructs the response object from frame
pub(crate) fn from_frame<F>(frame: F) -> Result<Option<Self>, ResponseTypeError>
where
F: IsNullFrame + ToStringBytes,
{
if frame.is_null_frame() {
return Ok(None);
}

Ok(Some(GetResponse::new(
frame.to_string_bytes().ok_or(ResponseTypeError {})?,
)))
}
}

impl<F> Command<F> for GetCommand
Expand All @@ -176,13 +190,7 @@ where
}

fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
if frame.is_null_frame() {
return Ok(None);
}

Ok(Some(GetResponse::new(
frame.to_string_bytes().ok_or(ResponseTypeError {})?,
)))
GetResponse::from_frame(frame)
}
}

Expand Down
69 changes: 69 additions & 0 deletions src/commands/hget.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
use crate::commands::auth::AuthCommand;
use crate::commands::builder::{CommandBuilder, IsNullFrame, ToStringBytes};
use crate::commands::get::GetResponse;
use crate::commands::hello::HelloCommand;
use crate::commands::{Command, ResponseTypeError};
use crate::network::protocol::Protocol;
use crate::network::{Client, CommandErrors, Future};
use bytes::Bytes;
use embedded_nal::TcpClientStack;
use embedded_time::Clock;

/// Abstraction for HGET command
pub struct HashGetCommand {
/// Hash key
key: Bytes,

/// Hash field to receive
field: Bytes,
}

impl HashGetCommand {
pub fn new<K, F>(key: K, field: F) -> Self
where
Bytes: From<K>,
Bytes: From<F>,
{
Self {
key: key.into(),
field: field.into(),
}
}
}

impl<F> Command<F> for HashGetCommand
where
F: From<CommandBuilder> + IsNullFrame + ToStringBytes,
{
type Response = Option<GetResponse>;

fn encode(&self) -> F {
CommandBuilder::new("HGET").arg(&self.key).arg(&self.field).into()
}

fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
GetResponse::from_frame(frame)
}
}

impl<'a, N: TcpClientStack, C: Clock, P: Protocol> Client<'a, N, C, P>
where
AuthCommand: Command<<P as Protocol>::FrameType>,
HelloCommand: Command<<P as Protocol>::FrameType>,
{
/// Shorthand for [GetCommand]
pub fn hget<K, F>(
&'a self,
key: K,
field: F,
) -> Result<Future<'a, N, C, P, HashGetCommand>, CommandErrors>
where
<P as Protocol>::FrameType: ToStringBytes,
<P as Protocol>::FrameType: IsNullFrame,
<P as Protocol>::FrameType: From<CommandBuilder>,
Bytes: From<K>,
Bytes: From<F>,
{
self.send(HashGetCommand::new(key, field))
}
}
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pub mod custom;
pub mod get;
pub mod hello;
pub mod helpers;
mod hget;
pub mod hset;
pub mod ping;
pub mod publish;
Expand Down
80 changes: 80 additions & 0 deletions src/commands/tests/hget.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
use crate::commands::helpers::CmdStr;
use crate::commands::hget::HashGetCommand;
use crate::commands::Command;
use redis_protocol::resp2::types::Frame as Resp2Frame;
use redis_protocol::resp3::types::Frame as Resp3Frame;

#[test]
fn test_encode_resp2() {
let frame: Resp2Frame = HashGetCommand::new("my_hash", "color").encode();

assert!(frame.is_array());
if let Resp2Frame::Array(array) = frame {
assert_eq!(3, array.len());
assert_eq!("HGET", array[0].to_string().unwrap());
assert_eq!("my_hash", array[1].to_string().unwrap());
assert_eq!("color", array[2].to_string().unwrap());
}
}

#[test]
fn test_encode_resp3() {
let frame: Resp3Frame = HashGetCommand::new("my_hash", "color").encode();

assert!(frame.is_array());
if let Resp3Frame::Array { data, attributes: _ } = frame {
assert_eq!(3, data.len());
assert_eq!("HGET", data[0].to_string().unwrap());
assert_eq!("my_hash", data[1].to_string().unwrap());
assert_eq!("color", data[2].to_string().unwrap());
}
}

#[test]
fn test_eval_response_resp2_key_existing() {
let response = HashGetCommand::new("my_hash", "color")
.eval_response(CmdStr::new("correct response1").to_bulk())
.unwrap();

assert_eq!("correct response1", response.unwrap().as_str().unwrap());
}

#[test]
fn test_eval_response_resp3_key_existing() {
let response = HashGetCommand::new("my_hash", "color")
.eval_response(CmdStr::new("correct").to_blob())
.unwrap();

assert_eq!("correct", response.unwrap().as_str().unwrap());
}

#[test]
fn test_eval_response_resp2_key_missing() {
let response = HashGetCommand::new("my_hash", "color").eval_response(Resp2Frame::Null).unwrap();

assert!(response.is_none());
}

#[test]
fn test_eval_response_resp3_key_missing() {
let response = HashGetCommand::new("my_hash", "color").eval_response(Resp3Frame::Null).unwrap();

assert!(response.is_none());
}

#[test]
fn test_eval_response_resp2_invalid_response() {
let response = HashGetCommand::new("my_hash", "color").eval_response(Resp2Frame::Array(vec![]));

assert!(response.is_err());
}

#[test]
fn test_eval_response_resp3_invalid_response() {
let response = HashGetCommand::new("my_hash", "color").eval_response(Resp3Frame::Array {
data: vec![],
attributes: None,
});

assert!(response.is_err());
}
1 change: 1 addition & 0 deletions src/commands/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod bgsave;
mod custom;
mod get;
pub(crate) mod hello;
mod hget;
mod hset;
mod ping;
mod publish;
Expand Down
60 changes: 60 additions & 0 deletions src/network/tests/client.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1054,3 +1054,63 @@ fn test_shorthand_hset_bytes_argument() {
.wait()
.unwrap();
}

#[test]
fn test_shorthand_hget_str_argument() {
let clock = TestClock::new(vec![]);

let mut network = NetworkMockBuilder::default()
.send(164, "*3\r\n$4\r\nHGET\r\n$7\r\nmy_hash\r\n$5\r\nfield\r\n")
.response_string("test_response")
.into_mock();

let mut socket = SocketMock::new(164);
let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {});

assert_eq!(
"test_response",
client
.hget("my_hash", "field")
.unwrap()
.wait()
.unwrap()
.unwrap()
.as_str()
.unwrap()
);
}

#[test]
fn test_shorthand_hget_string_argument() {
let clock = TestClock::new(vec![]);

let mut network = NetworkMockBuilder::default()
.send(164, "*3\r\n$4\r\nHGET\r\n$7\r\nmy_hash\r\n$5\r\nfield\r\n")
.response_string("test_response")
.into_mock();

let mut socket = SocketMock::new(164);
let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {});

let response = client.hget("my_hash".to_string(), "field".to_string()).unwrap().wait();
assert_eq!("test_response", response.unwrap().unwrap().as_str().unwrap());
}

#[test]
fn test_shorthand_hget_bytes_argument() {
let clock = TestClock::new(vec![]);

let mut network = NetworkMockBuilder::default()
.send(164, "*3\r\n$4\r\nHGET\r\n$7\r\nmy_hash\r\n$5\r\nfield\r\n")
.response_string("test_response")
.into_mock();

let mut socket = SocketMock::new(164);
let client = create_mocked_client(&mut network, &mut socket, &clock, Resp2 {});

let response = client
.hget(Bytes::from_static(b"my_hash"), Bytes::from_static(b"field"))
.unwrap()
.wait();
assert_eq!("test_response", response.unwrap().unwrap().as_str().unwrap());
}

0 comments on commit fd6459d

Please sign in to comment.