use crate::commands::auth::AuthCommand;
use crate::commands::builder::{CommandBuilder, IsNullFrame, ToStringBytes, ToStringOption};
use crate::commands::hello::HelloCommand;
use crate::commands::{Command, ResponseTypeError};
use crate::network::client::{Client, CommandErrors};
use crate::network::future::Future;
use crate::network::protocol::Protocol;
use alloc::string::ToString;
use bytes::Bytes;
use core::marker::PhantomData;
use embedded_nal::TcpClientStack;
use embedded_time::Clock;
pub enum ExpirationPolicy {
Never,
Seconds(usize),
Milliseconds(usize),
TimestampSeconds(usize),
TimestampMilliseconds(usize),
Keep,
}
pub enum Exclusivity {
None,
SetIfExists,
SetIfMissing,
}
pub struct SetCommand<R> {
key: Bytes,
value: Bytes,
expiration: ExpirationPolicy,
exclusivity: Exclusivity,
return_old_value: bool,
response_type: PhantomData<R>,
}
impl SetCommand<ConfirmationResponse> {
pub fn new<K, V>(key: K, value: V) -> Self
where
Bytes: From<K>,
Bytes: From<V>,
{
SetCommand {
key: key.into(),
value: value.into(),
expiration: ExpirationPolicy::Never,
exclusivity: Exclusivity::None,
return_old_value: false,
response_type: PhantomData,
}
}
pub fn expires(mut self, policy: ExpirationPolicy) -> SetCommand<ConfirmationResponse> {
self.expiration = policy;
self
}
pub fn set_exclusive(self, option: Exclusivity) -> SetCommand<ExclusiveSetResponse> {
SetCommand {
key: self.key,
value: self.value,
expiration: self.expiration,
exclusivity: option,
return_old_value: self.return_old_value,
response_type: PhantomData,
}
}
}
impl<R> SetCommand<R> {
pub fn return_previous(self) -> SetCommand<ReturnPreviousResponse> {
SetCommand {
key: self.key,
value: self.value,
expiration: self.expiration,
exclusivity: self.exclusivity,
return_old_value: true,
response_type: PhantomData,
}
}
}
pub type ConfirmationResponse = ();
pub type ExclusiveSetResponse = Option<()>;
pub type ReturnPreviousResponse = Option<Bytes>;
impl<F> Command<F> for SetCommand<ConfirmationResponse>
where
F: From<CommandBuilder> + ToStringOption,
{
type Response = ConfirmationResponse;
fn encode(&self) -> F {
self.get_builder().into()
}
fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
if frame.to_string_option().ok_or(ResponseTypeError {})? != "OK" {
return Err(ResponseTypeError {});
}
Ok(())
}
}
impl<F> Command<F> for SetCommand<ExclusiveSetResponse>
where
F: From<CommandBuilder> + ToStringOption + IsNullFrame,
{
type Response = ExclusiveSetResponse;
fn encode(&self) -> F {
self.get_builder().into()
}
fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
if frame.is_null_frame() {
return Ok(None);
}
if frame.to_string_option().ok_or(ResponseTypeError {})? == "OK" {
return Ok(Some(()));
}
Err(ResponseTypeError {})
}
}
impl<F> Command<F> for SetCommand<ReturnPreviousResponse>
where
F: From<CommandBuilder> + IsNullFrame + ToStringBytes,
{
type Response = ReturnPreviousResponse;
fn encode(&self) -> F {
self.get_builder().into()
}
fn eval_response(&self, frame: F) -> Result<Self::Response, ResponseTypeError> {
if frame.is_null_frame() {
return Ok(None);
}
Ok(Some(frame.to_string_bytes().ok_or(ResponseTypeError {})?))
}
}
impl<R> SetCommand<R> {
fn get_builder(&self) -> CommandBuilder {
CommandBuilder::new("SET")
.arg(&self.key)
.arg(&self.value)
.arg_static_option(self.expiration_unit())
.arg_option(self.expiration_time().as_ref())
.arg_static_option(self.exclusive_option())
.arg_static_option(self.get_option())
}
fn expiration_unit(&self) -> Option<&'static str> {
match self.expiration {
ExpirationPolicy::Never => None,
ExpirationPolicy::Seconds(_) => Some("EX"),
ExpirationPolicy::Milliseconds(_) => Some("PX"),
ExpirationPolicy::TimestampSeconds(_) => Some("EXAT"),
ExpirationPolicy::TimestampMilliseconds(_) => Some("PXAT"),
ExpirationPolicy::Keep => Some("KEEPTTL"),
}
}
fn expiration_time(&self) -> Option<Bytes> {
match self.expiration {
ExpirationPolicy::Never => None,
ExpirationPolicy::Seconds(seconds)
| ExpirationPolicy::Milliseconds(seconds)
| ExpirationPolicy::TimestampSeconds(seconds)
| ExpirationPolicy::TimestampMilliseconds(seconds) => Some(seconds.to_string().into()),
ExpirationPolicy::Keep => None,
}
}
fn exclusive_option(&self) -> Option<&'static str> {
match self.exclusivity {
Exclusivity::None => None,
Exclusivity::SetIfExists => Some("XX"),
Exclusivity::SetIfMissing => Some("NX"),
}
}
fn get_option(&self) -> Option<&'static str> {
if self.return_old_value {
return Some("GET");
}
None
}
}
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>,
{
pub fn set<K, V>(
&'a self,
key: K,
value: V,
) -> Result<Future<'a, N, C, P, SetCommand<ConfirmationResponse>>, CommandErrors>
where
<P as Protocol>::FrameType: ToStringBytes,
<P as Protocol>::FrameType: ToStringOption,
<P as Protocol>::FrameType: IsNullFrame,
<P as Protocol>::FrameType: From<CommandBuilder>,
Bytes: From<K>,
Bytes: From<V>,
{
self.send(SetCommand::new(key, value))
}
}