apply client/cli polish (#15)

Continuation of #11. Refines the client structure and implements GET.

`clap` is decoupled from the lib code. This is done to avoid any CLI
parsing concerns to leak into the lib. The main motivation for this is
to allow the reader to focus on Tokio concerns and not CLI parsing
concerns.
This commit is contained in:
Carl Lerche
2020-04-01 16:09:41 -07:00
committed by GitHub
parent 7bd7086d41
commit bbb80c341e
17 changed files with 297 additions and 210 deletions

98
src/bin/cli.rs Normal file
View File

@@ -0,0 +1,98 @@
use mini_redis::{client, DEFAULT_PORT};
use bytes::Bytes;
use clap::Clap;
use std::num::ParseIntError;
use std::str;
use std::time::Duration;
#[derive(Clap, Debug)]
#[clap(name = "mini-redis-cli", version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"), about = "Issue Redis commands")]
struct Cli {
#[clap(subcommand)]
command: Command,
#[clap(name = "hostname", long = "--host", default_value = "127.0.0.1")]
host: String,
#[clap(name = "port", long = "--port", default_value = DEFAULT_PORT)]
port: String,
}
#[derive(Clap, Debug)]
enum Command {
/// Get the value of key.
Get {
/// Name of key to get
key: String
},
/// Set key to hold the string value.
Set {
/// Name of key to set
key: String,
/// Value to set.
#[clap(parse(from_str = bytes_from_str))]
value: Bytes,
/// Expire the value after specified amount of time
#[clap(parse(try_from_str = duration_from_ms_str))]
expires: Option<Duration>,
},
}
/// Entry point for CLI tool.
///
/// The `[tokio::main]` annotation signals that the Tokio runtime should be
/// started when the function is called. The body of the function is executed
/// within the newly spawned runtime.
///
/// `basic_scheduler` is used here to avoid spawning background threads. The CLI
/// tool use case benefits more by being lighter instead of multi-threaded.
#[tokio::main(basic_scheduler)]
async fn main() -> mini_redis::Result<()> {
// Enable logging
tracing_subscriber::fmt::try_init()?;
// Parse command line arguments
let cli = Cli::parse();
// Get the remote address to connect to
let addr = format!("{}:{}", cli.host, cli.port);
// Establish a connection
let mut client = client::connect(&addr).await?;
match cli.command {
Command::Get { key } => {
if let Some(value) = client.get(&key).await? {
if let Ok(string) = str::from_utf8(&value) {
println!("\"{}\"", string);
} else {
println!("{:?}", value);
}
} else {
println!("(nil)");
}
}
Command::Set { key, value, expires: None } => {
client.set(&key, value).await?;
println!("OK");
}
Command::Set { key, value, expires: Some(expires) } => {
client.set_expires(&key, value, expires).await?;
println!("OK");
}
}
Ok(())
}
fn duration_from_ms_str(src: &str) -> Result<Duration, ParseIntError> {
let ms = src.parse::<u64>()?;
Ok(Duration::from_millis(ms))
}
fn bytes_from_str(src: &str) -> Bytes {
Bytes::from(src.to_string())
}

View File

@@ -1,49 +0,0 @@
use clap::Clap;
use mini_redis::{client, cmd::Set, DEFAULT_PORT};
use std::str;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let cli = Cli::parse();
let port = cli.port.unwrap_or(DEFAULT_PORT.to_string());
let mut client = client::connect(&format!("127.0.0.1:{}", port)).await?;
match cli.command {
Client::Get { key } => {
let result = client.get(&key).await?;
if let Some(result) = result {
println!("\"{}\"", str::from_utf8(&result).unwrap());
} else {
println!("(nil)");
}
Ok(())
}
Client::Set(opts) => match client.set_with_opts(opts).await {
Ok(_) => {
println!("OK");
Ok(())
}
Err(e) => {
eprintln!("{}", e);
Err(e)
}
},
}
}
#[derive(Clap, Debug)]
#[clap(name = "mini-redis-client", version = env!("CARGO_PKG_VERSION"), author = env!("CARGO_PKG_AUTHORS"), about = "Opens a connection to a Redis server")]
struct Cli {
#[clap(subcommand)]
command: Client,
#[clap(name = "port", long = "--port")]
port: Option<String>,
}
#[derive(Clap, Debug)]
enum Client {
/// Gets a value associated with a key
Get { key: String },
/// Associates a value with a key
Set(Set),
}

View File

@@ -1,12 +1,12 @@
use anyhow::{anyhow, Result};
use clap::Clap;
use mini_redis::{server, DEFAULT_PORT};
use clap::Clap;
#[tokio::main]
pub async fn main() -> Result<()> {
pub async fn main() -> mini_redis::Result<()> {
// enable logging
// see https://docs.rs/tracing for more info
tracing_subscriber::fmt::try_init().map_err(|e| anyhow!("{:?}", e))?;
tracing_subscriber::fmt::try_init()?;
let cli = Cli::parse();
let port = cli.port.unwrap_or(DEFAULT_PORT.to_string());