diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f7648dd..cad47a5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,7 +4,7 @@ on: push: concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true jobs: diff --git a/src/client.rs b/src/client.rs index 66e0df8..13412e5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -188,19 +188,43 @@ impl Client { } /// Sends a GETEX command to the Redis server. - #[allow(unused_variables)] - pub async fn get_ex(&mut self, key: &str, seconds: i64) -> Result>> { - todo!("GETEX command is not implemented yet"); - // let frame: Frame = GetEx::new(key, seconds).into_stream(); + /// + /// # Description + /// The GETEX command retrieves the value of a key stored on the Redis server and sets an expiry time. + /// + /// # Arguments + /// + /// * `key` - A required key to send to the server + /// * `expiry` - An optional expiry time to set + /// + /// # Returns + /// + /// * `Ok(Some(String))` if the key to GETEX exists + /// * `Ok(None)` if the key to GETEX does not exist + /// * `Err(RedisError)` if an error occurs + /// + /// # Examples + /// + /// ```ignore + /// use async_redisx::{Client, Expiry}; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut client = Client::connect("127.0.0.1:6379").await.unwrap(); + /// let resp = client.get_ex("mykey", Some(Expirt::EX(1_u64))).await?; + /// } + /// ``` + pub async fn get_ex(&mut self, key: &str, expiry: Option) -> Result>> { + let frame: Frame = GetEx::new(key, expiry).into_stream(); - // self.conn.write_frame(&frame).await?; + self.conn.write_frame(&frame).await?; - // match self.read_response().await? { - // Response::Simple(data) => Ok(Some(data)), - // Response::Null => Ok(None), - // Response::Error(err) => Err(err), - // _ => Err(RedisError::UnexpectedResponseType), - // } + match self.read_response().await? { + Response::Simple(data) => Ok(Some(data)), + Response::Null => Ok(None), + Response::Error(err) => Err(err), + _ => Err(RedisError::UnexpectedResponseType), + } } /// Sends a MGET command to the Redis server. diff --git a/src/cmd/getex.rs b/src/cmd/getex.rs new file mode 100644 index 0000000..728a582 --- /dev/null +++ b/src/cmd/getex.rs @@ -0,0 +1,118 @@ +/// A Redis GETEX command. +use crate::cmd::Command; +use crate::frame::Frame; +use bytes::Bytes; + +#[derive(Debug)] +pub enum Expiry { + EX(u64), + PX(u64), + EXAT(u64), + PXAT(u64), + PERSIST, +} + +#[derive(Debug)] +pub struct GetEx { + key: String, + expiry: Option, +} + +impl GetEx { + /// Creates a new GetEx command. + /// + /// # Arguments + /// + /// * `key` - The key to get from the Redis server + /// * `expiry` - The expiry time for the key + /// + /// # Returns + /// + /// A new GetEx command + /// + /// # Examples + /// + /// ```ignore + /// let getex = GetEx::new("mykey"); + /// ``` + pub fn new(key: &str, expiry: Option) -> Self { + Self { + key: key.to_string(), + expiry, + } + } +} + +impl Command for GetEx { + fn into_stream(self) -> Frame { + let mut frame: Frame = Frame::array(); + frame + .push_frame_to_array(Frame::BulkString("GETEX".into())) + .unwrap(); + frame + .push_frame_to_array(Frame::BulkString(Bytes::from(self.key))) + .unwrap(); + + if let Some(expiry) = self.expiry { + match expiry { + Expiry::EX(seconds) => { + frame + .push_frame_to_array(Frame::BulkString("EX".into())) + .unwrap(); + frame + .push_frame_to_array(Frame::Integer(seconds as i64)) + .unwrap(); + } + Expiry::PX(milliseconds) => { + frame + .push_frame_to_array(Frame::BulkString("PX".into())) + .unwrap(); + frame + .push_frame_to_array(Frame::Integer(milliseconds as i64)) + .unwrap(); + } + Expiry::EXAT(timestamp) => { + frame + .push_frame_to_array(Frame::BulkString("EXAT".into())) + .unwrap(); + frame + .push_frame_to_array(Frame::Integer(timestamp as i64)) + .unwrap(); + } + Expiry::PXAT(timestamp) => { + frame + .push_frame_to_array(Frame::BulkString("PXAT".into())) + .unwrap(); + frame + .push_frame_to_array(Frame::Integer(timestamp as i64)) + .unwrap(); + } + Expiry::PERSIST => { + frame + .push_frame_to_array(Frame::BulkString("PERSIST".into())) + .unwrap(); + } + } + } + frame + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_get() { + let getex = GetEx::new("mykey", None); + let frame = getex.into_stream(); + + assert_eq!( + frame, + Frame::Array(vec![ + Frame::BulkString("GETEX".into()), + Frame::BulkString("mykey".into()), + ]) + ) + } +} diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs index f646710..ab02d5d 100644 --- a/src/cmd/mod.rs +++ b/src/cmd/mod.rs @@ -10,6 +10,9 @@ pub use ping::Ping; mod get; pub use get::Get; +mod getex; +pub use getex::{Expiry, GetEx}; + mod set; pub use set::Set; diff --git a/src/lib.rs b/src/lib.rs index af42a9e..b7e881d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -32,6 +32,7 @@ mod frame; pub use frame::Frame; mod cmd; +pub use cmd::Expiry; mod client; pub use client::Client;