Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Abstraction of HGETALL command #11

Merged
merged 3 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 69 additions & 0 deletions src/commands/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@
//! .into();
//! ```
use crate::commands::custom::CustomCommand;
use alloc::collections::BTreeMap;
use alloc::string::{String, ToString};
use alloc::vec;
use alloc::vec::Vec;
use bytes::Bytes;
use redis_protocol::resp2::prelude::Frame;
use redis_protocol::resp2::types::Frame as Resp2Frame;
use redis_protocol::resp3::types::Frame as Resp3Frame;

Expand Down Expand Up @@ -208,3 +210,70 @@ impl ToStringBytes for Resp3Frame {
}
}
}

/// Trait for converting RESP2 arrays or RESP3 maps
pub trait ToBytesMap {
/// Converts the frame to map
/// Returns None in case of protocol violation
fn to_map(&self) -> Option<BTreeMap<Bytes, Bytes>>;
}

impl ToBytesMap for Resp2Frame {
fn to_map(&self) -> Option<BTreeMap<Bytes, Bytes>> {
let mut map = BTreeMap::new();

match self {
Frame::Array(array) => {
for item in array.chunks(2) {
if item.len() < 2 {
return None;
}

let field = match &item[0] {
Frame::SimpleString(value) | Frame::BulkString(value) => value.clone(),
_ => return None,
};

let value = match &item[1] {
Frame::SimpleString(value) | Frame::BulkString(value) => value.clone(),
_ => return None,
};

map.insert(field, value);
}
}
_ => return None,
}

Some(map)
}
}

impl ToBytesMap for Resp3Frame {
fn to_map(&self) -> Option<BTreeMap<Bytes, Bytes>> {
let mut map = BTreeMap::new();

match self {
Resp3Frame::Map { data, attributes: _ } => {
for item in data {
let field = match item.0 {
Resp3Frame::BlobString { data, attributes: _ }
| Resp3Frame::SimpleString { data, attributes: _ } => data.clone(),
_ => return None,
};

let value = match item.1 {
Resp3Frame::BlobString { data, attributes: _ }
| Resp3Frame::SimpleString { data, attributes: _ } => data.clone(),
_ => return None,
};

map.insert(field, value);
}
}
_ => return None,
}

Some(map)
}
}
5 changes: 0 additions & 5 deletions src/commands/hget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::builder::CommandBuilder;
//!# use embedded_redis::commands::hget::HashGetCommand;
//!# use embedded_redis::commands::hset::HashSetCommand;
//!# use embedded_redis::commands::publish::PublishCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//! let mut stack = Stack::default();
Expand All @@ -36,8 +34,6 @@
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::builder::CommandBuilder;
//!# use embedded_redis::commands::hget::HashGetCommand;
//!# use embedded_redis::commands::hset::HashSetCommand;
//!# use embedded_redis::commands::publish::PublishCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//!# let mut stack = Stack::default();
Expand All @@ -61,7 +57,6 @@
//!# use std_embedded_nal::Stack;
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::hset::HashSetCommand;
//!# use embedded_redis::commands::set::SetCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//!# let mut stack = Stack::default();
Expand Down
179 changes: 179 additions & 0 deletions src/commands/hgetall.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
//! Abstraction of HGETALL command.
//!
//! For general information about this command, see the [Redis documentation](<https://redis.io/commands/hgetall/>).
//!
//! # Using command object
//! ```
//!# use core::str::FromStr;
//!# use embedded_nal::SocketAddr;
//!# use std_embedded_nal::Stack;
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::builder::CommandBuilder;
//!# use embedded_redis::commands::hgetall::HashGetAllCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//! let mut stack = Stack::default();
//! let clock = StandardClock::default();
//!
//! let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
//! let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
//! client.hset("test_all_hash", "color", "green").unwrap().wait().unwrap();
//! client.hset("test_all_hash", "material", "wood").unwrap().wait().unwrap();
//!
//! let command = HashGetAllCommand::new("test_all_hash");
//! let response = client.send(command).unwrap().wait().unwrap().unwrap();
//!
//! assert_eq!("green", response.get_str("color").unwrap());
//! assert_eq!("wood", response.get_str("material").unwrap());
//! ```
//!
//! # Missing key or field
//! In case key or field is missing. [None] is returned.
//! ```
//!# use core::str::FromStr;
//!# use embedded_nal::SocketAddr;
//!# use std_embedded_nal::Stack;
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::builder::CommandBuilder;
//!# use embedded_redis::commands::hgetall::HashGetAllCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//!# let mut stack = Stack::default();
//!# let clock = StandardClock::default();
//!#
//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
//!#
//! let command = HashGetAllCommand::new("not_existing");
//! let response = client.send(command).unwrap().wait().unwrap();
//!
//! assert!(response.is_none())
//! ```
//!
//! # Shorthand
//! [Client](Client#method.hgetall) provides a shorthand method for this command.
//! ```
//!# use core::str::FromStr;
//!# use bytes::Bytes;
//!# use embedded_nal::SocketAddr;
//!# use std_embedded_nal::Stack;
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::hset::HashSetCommand;
//!# use embedded_redis::commands::set::SetCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//!# let mut stack = Stack::default();
//!# let clock = StandardClock::default();
//!#
//!# let mut connection_handler = ConnectionHandler::resp2(SocketAddr::from_str("127.0.0.1:6379").unwrap());
//!# let client = connection_handler.connect(&mut stack, Some(&clock)).unwrap();
//!#
//!# let _ = client.send(HashSetCommand::new("multi_hash_key", "first_field", "green")).unwrap().wait();
//!# let _ = client.send(HashSetCommand::new("multi_hash_key", "second_field", "wood")).unwrap().wait();
//!#
//! // Using &str arguments
//! let response = client.hgetall("multi_hash_key").unwrap().wait().unwrap().unwrap();
//! assert_eq!("green", response.get_str("first_field").unwrap());
//! assert_eq!("wood", response.get_str("second_field").unwrap());
//!
//! // Using String arguments
//! let _ = client.hgetall("multi_hash_key".to_string());
//!
//! // Using Bytes arguments
//! let _ = client.hgetall(Bytes::from_static(b"multi_hash_key"));
//! ```
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))
}
}
3 changes: 0 additions & 3 deletions src/commands/hset.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::builder::CommandBuilder;
//! use embedded_redis::commands::hset::HashSetCommand;
//!# use embedded_redis::commands::publish::PublishCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//! let mut stack = Stack::default();
Expand All @@ -34,7 +33,6 @@
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::builder::CommandBuilder;
//!# use embedded_redis::commands::hset::HashSetCommand;
//!# use embedded_redis::commands::publish::PublishCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//!# let mut stack = Stack::default();
Expand All @@ -61,7 +59,6 @@
//!# use embedded_nal::SocketAddr;
//!# use std_embedded_nal::Stack;
//!# use std_embedded_time::StandardClock;
//!# use embedded_redis::commands::set::SetCommand;
//!# use embedded_redis::network::ConnectionHandler;
//!#
//!# let mut stack = Stack::default();
Expand Down
1 change: 1 addition & 0 deletions src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ pub mod get;
pub mod hello;
pub mod helpers;
pub mod hget;
pub mod hgetall;
pub mod hset;
pub mod ping;
pub mod publish;
Expand Down
Loading
Loading