diff --git a/src/bin/cli.rs b/src/bin/cli.rs index 9fac134..4e7d354 100644 --- a/src/bin/cli.rs +++ b/src/bin/cli.rs @@ -26,6 +26,10 @@ struct Cli { #[derive(Subcommand, Debug)] enum Command { + Ping { + /// Message to ping + msg: Option, + }, /// Get the value of key. Get { /// Name of key to get @@ -85,6 +89,14 @@ async fn main() -> mini_redis::Result<()> { // Process the requested command match cli.command { + Command::Ping { msg } => { + let value = client.ping(msg).await?; + if let Ok(string) = str::from_utf8(&value) { + println!("\"{}\"", string); + } else { + println!("{:?}", value); + } + } Command::Get { key } => { if let Some(value) = client.get(&key).await? { if let Ok(string) = str::from_utf8(&value) { diff --git a/src/client.rs b/src/client.rs index 08223b2..2c749fb 100644 --- a/src/client.rs +++ b/src/client.rs @@ -2,7 +2,7 @@ //! //! Provides an async connect and methods for issuing the supported commands. -use crate::cmd::{Get, Publish, Set, Subscribe, Unsubscribe}; +use crate::cmd::{Get, Ping, Publish, Set, Subscribe, Unsubscribe}; use crate::{Connection, Frame}; use async_stream::try_stream; @@ -87,6 +87,42 @@ pub async fn connect(addr: T) -> crate::Result { } impl Client { + /// Ping to the server. + /// + /// Returns PONG if no argument is provided, otherwise + /// return a copy of the argument as a bulk. + /// + /// This command is often used to test if a connection + /// is still alive, or to measure latency. + /// + /// # Examples + /// + /// Demonstrates basic usage. + /// ```no_run + /// use mini_redis::client; + /// + /// #[tokio::main] + /// async fn main() { + /// let mut client = client::connect("localhost:6379").await.unwrap(); + /// + /// let pong = client.ping(None).await.unwrap(); + /// assert_eq!(b"PONG", &pong[..]); + /// } + /// ``` + #[instrument(skip(self))] + pub async fn ping(&mut self, msg: Option) -> crate::Result { + let frame = Ping::new(msg).into_frame(); + debug!(request = ?frame); + + self.connection.write_frame(&frame).await?; + + match self.read_response().await? { + Frame::Simple(value) => Ok(value.into()), + Frame::Bulk(value) => Ok(value), + frame => Err(frame.to_error()), + } + } + /// Get the value of key. /// /// If the key does not exist the special value `None` is returned. diff --git a/src/cmd/ping.rs b/src/cmd/ping.rs index 14a467e..54657c4 100644 --- a/src/cmd/ping.rs +++ b/src/cmd/ping.rs @@ -63,4 +63,17 @@ impl Ping { Ok(()) } + + /// Converts the command into an equivalent `Frame`. + /// + /// This is called by the client when encoding a `Ping` command to send + /// to the server. + pub(crate) fn into_frame(self) -> Frame { + let mut frame = Frame::array(); + frame.push_bulk(Bytes::from("ping".as_bytes())); + if let Some(msg) = self.msg { + frame.push_bulk(Bytes::from(msg)); + } + frame + } } diff --git a/tests/client.rs b/tests/client.rs index 2d5208d..4d8d127 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -3,6 +3,28 @@ use std::net::SocketAddr; use tokio::net::TcpListener; use tokio::task::JoinHandle; +/// A PING PONG test without message provided. +/// It should return "PONG". +#[tokio::test] +async fn ping_pong_without_message() { + let (addr, _) = start_server().await; + let mut client = client::connect(addr).await.unwrap(); + + let pong = client.ping(None).await.unwrap(); + assert_eq!(b"PONG", &pong[..]); +} + +/// A PING PONG test with message provided. +/// It should return the message. +#[tokio::test] +async fn ping_pong_with_message() { + let (addr, _) = start_server().await; + let mut client = client::connect(addr).await.unwrap(); + + let pong = client.ping(Some("你好世界".to_string())).await.unwrap(); + assert_eq!("你好世界".as_bytes(), &pong[..]); +} + /// A basic "hello world" style test. A server instance is started in a /// background task. A client instance is then established and set and get /// commands are sent to the server. The response is then evaluated