use std::env;
use std::io::{self, Write};
use std::net::SocketAddr;
use bytes::BytesMut;
use tokio::io::{AsyncReadExt, AsyncWriteExt};
use tokio::net::TcpStream;
use rust_cache_server::protocol::{encode_frame, parse_frame, RespFrame};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let mut host = "127.0.0.1".to_string();
let mut port: u16 = 7777;
let args: Vec<String> = env::args().collect();
if args.len() >= 2 {
host = args[1].clone();
}
if args.len() >= 3 {
if let Ok(p) = args[2].parse::<u16>() {
port = p;
}
}
let addr: SocketAddr = format!("{}:{}", host, port).parse()?;
println!("Connecting to {} ...", addr);
let stream = TcpStream::connect(addr).await?;
println!("Connected. Type commands like redis-cli, e.g.:");
println!(" PING");
println!(" SET foo bar");
println!(" GET foo");
println!("Ctrl+C to exit.");
repl(stream).await?;
Ok(())
}
async fn repl(mut stream: TcpStream) -> anyhow::Result<()> {
let mut input = String::new();
let stdin = io::stdin();
let mut stdout = io::stdout();
let mut buf = BytesMut::with_capacity(4096);
loop {
input.clear();
print!("rust-cache> ");
stdout.flush()?;
if stdin.read_line(&mut input)? == 0 {
break;
}
let line = input.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.split_whitespace().collect();
let frame = RespFrame::Array(Some(
parts
.into_iter()
.map(|p| RespFrame::BulkString(Some(p.as_bytes().to_vec())))
.collect(),
));
let encoded = encode_frame(&frame);
stream.write_all(&encoded).await?;
buf.clear();
let mut tmp = [0u8; 4096];
let n = stream.read(&mut tmp).await?;
if n == 0 {
println!("\nConnection closed by server.");
break;
}
buf.extend_from_slice(&tmp[..n]);
match parse_frame(&mut buf) {
Ok(Some(resp)) => {
println!("{}", format_resp(&resp));
}
Ok(None) => {
println!("(no reply)");
}
Err(e) => {
println!("(error) {}", e);
}
}
}
Ok(())
}
fn format_resp(resp: &RespFrame) -> String {
match resp {
RespFrame::SimpleString(s) => s.clone(),
RespFrame::Error(s) => format!("(error) {s}"),
RespFrame::Integer(i) => format!("(integer) {i}"),
RespFrame::BulkString(Some(b)) => {
let s = String::from_utf8_lossy(b);
s.to_string()
}
RespFrame::BulkString(None) => "(nil)".to_string(),
RespFrame::Array(Some(items)) => {
let mut out = String::new();
for (i, item) in items.iter().enumerate() {
out.push_str(&format!("{}) {}\n", i + 1, format_resp(item)));
}
if out.ends_with('\n') {
out.pop();
}
out
}
RespFrame::Array(None) => "(nil)".to_string(),
}
}