-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented abstraction for HGETALL command
- Loading branch information
1 parent
73b24ef
commit 7e7d640
Showing
7 changed files
with
526 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,95 @@ | ||
use crate::commands::auth::AuthCommand; | ||
use crate::commands::builder::{CommandBuilder, ToBytesMap}; | ||
use crate::commands::hello::HelloCommand; | ||
use crate::commands::{Command, ResponseTypeError}; | ||
use crate::network::protocol::Protocol; | ||
use crate::network::{Client, CommandErrors, Future}; | ||
use alloc::collections::BTreeMap; | ||
use bytes::Bytes; | ||
use embedded_nal::TcpClientStack; | ||
use embedded_time::Clock; | ||
|
||
/// Abstraction for HGETALL command | ||
pub struct HashGetAllCommand { | ||
/// Hash key | ||
key: Bytes, | ||
} | ||
|
||
impl HashGetAllCommand { | ||
pub fn new<K>(key: K) -> Self | ||
where | ||
Bytes: From<K>, | ||
{ | ||
Self { key: key.into() } | ||
} | ||
} | ||
|
||
pub struct HashResponse { | ||
/// Field/Value map | ||
inner: BTreeMap<Bytes, Bytes>, | ||
} | ||
|
||
impl HashResponse { | ||
/// Extracts inner map | ||
#[allow(clippy::wrong_self_convention)] | ||
pub fn to_map(self) -> BTreeMap<Bytes, Bytes> { | ||
self.inner | ||
} | ||
|
||
/// Returns the given field as &str. Returns None in case field is missing or value has invalid UTF8 encoding | ||
pub fn get_str<F>(&self, field: F) -> Option<&str> | ||
where | ||
Bytes: From<F>, | ||
{ | ||
let field: Bytes = field.into(); | ||
|
||
match self.inner.get(&field) { | ||
None => None, | ||
Some(value) => match core::str::from_utf8(value) { | ||
Ok(value) => Some(value), | ||
Err(_) => None, | ||
}, | ||
} | ||
} | ||
} | ||
|
||
impl<F> Command<F> for HashGetAllCommand | ||
where | ||
F: From<CommandBuilder> + ToBytesMap, | ||
{ | ||
type Response = Option<HashResponse>; | ||
|
||
fn encode(&self) -> F { | ||
CommandBuilder::new("HGETALL").arg(&self.key).into() | ||
} | ||
|
||
fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> { | ||
let map = frame.to_map(); | ||
|
||
if map.is_none() { | ||
return Err(ResponseTypeError {}); | ||
} | ||
|
||
if map.as_ref().unwrap().is_empty() { | ||
return Ok(None); | ||
} | ||
|
||
Ok(Some(HashResponse { inner: map.unwrap() })) | ||
} | ||
} | ||
|
||
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 [HashGetAllCommand] | ||
pub fn hgetall<K>(&'a self, key: K) -> Result<Future<'a, N, C, P, HashGetAllCommand>, CommandErrors> | ||
where | ||
<P as Protocol>::FrameType: ToBytesMap, | ||
<P as Protocol>::FrameType: From<CommandBuilder>, | ||
Bytes: From<K>, | ||
{ | ||
self.send(HashGetAllCommand::new(key)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,184 @@ | ||
use crate::commands::builder::ToBytesMap; | ||
use bytes::Bytes; | ||
use redis_protocol::resp2::types::Frame as Resp2Frame; | ||
use redis_protocol::resp3::types::{Frame as Resp3Frame, FrameMap}; | ||
|
||
#[test] | ||
fn to_bytes_map_resp2_simple_string() { | ||
let frame: Resp2Frame = Resp2Frame::Array(vec![ | ||
Resp2Frame::SimpleString("color".into()), | ||
Resp2Frame::SimpleString("green".into()), | ||
Resp2Frame::SimpleString("material".into()), | ||
Resp2Frame::SimpleString("wood".into()), | ||
]); | ||
let map = frame.to_map().unwrap(); | ||
|
||
assert_eq!(2, map.len()); | ||
assert_eq!("green", map.get(&Bytes::from_static(b"color")).unwrap()); | ||
assert_eq!("wood", map.get(&Bytes::from_static(b"material")).unwrap()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp2_bulk_string() { | ||
let frame: Resp2Frame = Resp2Frame::Array(vec![ | ||
Resp2Frame::BulkString("color".into()), | ||
Resp2Frame::BulkString("green".into()), | ||
Resp2Frame::BulkString("material".into()), | ||
Resp2Frame::BulkString("wood".into()), | ||
]); | ||
let map = frame.to_map().unwrap(); | ||
|
||
assert_eq!(2, map.len()); | ||
assert_eq!("green", map.get(&Bytes::from_static(b"color")).unwrap()); | ||
assert_eq!("wood", map.get(&Bytes::from_static(b"material")).unwrap()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp2_no_array() { | ||
assert!(Resp2Frame::SimpleString("test".into()).to_map().is_none()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp2_missing_value() { | ||
let frame: Resp2Frame = Resp2Frame::Array(vec![ | ||
Resp2Frame::SimpleString("color".into()), | ||
Resp2Frame::SimpleString("green".into()), | ||
Resp2Frame::SimpleString("material".into()), | ||
]); | ||
|
||
assert!(frame.to_map().is_none()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp2_field_not_string() { | ||
let frame: Resp2Frame = Resp2Frame::Array(vec![ | ||
Resp2Frame::Array(vec![]), | ||
Resp2Frame::SimpleString("green".into()), | ||
]); | ||
|
||
assert!(frame.to_map().is_none()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp2_value_not_string() { | ||
let frame: Resp2Frame = Resp2Frame::Array(vec![ | ||
Resp2Frame::SimpleString("color".into()), | ||
Resp2Frame::Array(vec![]), | ||
]); | ||
|
||
assert!(frame.to_map().is_none()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp3_simple_string() { | ||
let frame: Resp3Frame = Resp3Frame::Map { | ||
data: FrameMap::from([ | ||
( | ||
Resp3Frame::SimpleString { | ||
data: "color".into(), | ||
attributes: None, | ||
}, | ||
Resp3Frame::SimpleString { | ||
data: "green".into(), | ||
attributes: None, | ||
}, | ||
), | ||
( | ||
Resp3Frame::SimpleString { | ||
data: "material".into(), | ||
attributes: None, | ||
}, | ||
Resp3Frame::SimpleString { | ||
data: "wood".into(), | ||
attributes: None, | ||
}, | ||
), | ||
]), | ||
attributes: None, | ||
}; | ||
let map = frame.to_map().unwrap(); | ||
|
||
assert_eq!(2, map.len()); | ||
assert_eq!("green", map.get(&Bytes::from_static(b"color")).unwrap()); | ||
assert_eq!("wood", map.get(&Bytes::from_static(b"material")).unwrap()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp3_blob_string() { | ||
let frame: Resp3Frame = Resp3Frame::Map { | ||
data: FrameMap::from([ | ||
( | ||
Resp3Frame::BlobString { | ||
data: "color".into(), | ||
attributes: None, | ||
}, | ||
Resp3Frame::BlobString { | ||
data: "green".into(), | ||
attributes: None, | ||
}, | ||
), | ||
( | ||
Resp3Frame::BlobString { | ||
data: "material".into(), | ||
attributes: None, | ||
}, | ||
Resp3Frame::BlobString { | ||
data: "wood".into(), | ||
attributes: None, | ||
}, | ||
), | ||
]), | ||
attributes: None, | ||
}; | ||
let map = frame.to_map().unwrap(); | ||
|
||
assert_eq!(2, map.len()); | ||
assert_eq!("green", map.get(&Bytes::from_static(b"color")).unwrap()); | ||
assert_eq!("wood", map.get(&Bytes::from_static(b"material")).unwrap()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp3_no_array() { | ||
assert!(Resp3Frame::BlobString { | ||
data: "test".into(), | ||
attributes: None, | ||
} | ||
.to_map() | ||
.is_none()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp3_field_not_string() { | ||
let frame: Resp3Frame = Resp3Frame::Map { | ||
data: FrameMap::from([( | ||
Resp3Frame::Number { | ||
data: 0, | ||
attributes: None, | ||
}, | ||
Resp3Frame::SimpleString { | ||
data: "green".into(), | ||
attributes: None, | ||
}, | ||
)]), | ||
attributes: None, | ||
}; | ||
assert!(frame.to_map().is_none()); | ||
} | ||
|
||
#[test] | ||
fn to_bytes_map_resp3_value_not_string() { | ||
let frame: Resp3Frame = Resp3Frame::Map { | ||
data: FrameMap::from([( | ||
Resp3Frame::SimpleString { | ||
data: "color".into(), | ||
attributes: None, | ||
}, | ||
Resp3Frame::Number { | ||
data: 0, | ||
attributes: None, | ||
}, | ||
)]), | ||
attributes: None, | ||
}; | ||
assert!(frame.to_map().is_none()); | ||
} |
Oops, something went wrong.