use std::{
io::{Read, Write},
net::{TcpStream, ToSocketAddrs},
ops::Deref,
};
mod err;
pub use err::{Error, Result};
const NIL: &str = "$-1";
#[derive(Debug)]
pub struct Client {
conn: Conn,
}
#[derive(Debug)]
pub struct Conn(TcpStream);
impl Conn {
fn send(&mut self, cmd: &Command) -> Result<Response> {
self.0.write_all(cmd.build().as_bytes())?;
self.0.flush()?;
let mut buf = [0; 512];
let size = self.0.read(&mut buf)?;
let resp = String::from_utf8_lossy(&buf[..size]).to_string();
if resp.starts_with('-') {
return Err(Error::Valkey(resp));
}
Ok(Response(resp))
}
fn auth(&mut self, password: &str) -> Result<()> {
let cmd = Command::new("AUTH").arg(password);
self.send(&cmd)?;
Ok(())
}
}
impl From<TcpStream> for Conn {
fn from(value: TcpStream) -> Self {
Self(value)
}
}
pub struct Command {
name: String,
args: Vec<String>,
}
impl Command {
pub fn new(name: &str) -> Self {
Self {
name: name.to_owned(),
args: Vec::new(),
}
}
pub fn arg(mut self, arg: &str) -> Self {
self.args.push(arg.to_owned());
self
}
pub fn build(&self) -> String {
let mut s = format!(
"*{n_args}\r\n${name_len}\r\n{name}\r\n",
n_args = 1 + self.args.len(),
name_len = self.name.len(),
name = self.name
);
for arg in &self.args {
s.push_str(&format!("${}\r\n{}\r\n", arg.len(), arg));
}
s
}
}
impl Client {
pub fn connect<A: ToSocketAddrs>(addr: A) -> Result<Self> {
let stream = TcpStream::connect(addr)?;
Ok(Self {
conn: stream.into(),
})
}
pub fn builder() -> ClientBuilder {
ClientBuilder::default()
}
fn send(&mut self, cmd: &Command) -> Result<Response> {
self.conn.send(cmd)
}
pub fn set(&mut self, k: &str, v: &str) -> Result<()> {
let cmd = Command::new("SET").arg(k).arg(v);
self.send(&cmd)?;
Ok(())
}
pub fn get(&mut self, k: &str) -> Result<Option<String>> {
let cmd = Command::new("GET").arg(k);
let resp = self.send(&cmd)?;
if resp.starts_with(NIL) {
Ok(None)
} else {
let parts = resp.split("\r\n").collect::<Vec<_>>();
Ok(Some(parts[1].to_string()))
}
}
}
#[derive(Default)]
pub struct ClientBuilder {
password: Option<String>,
}
impl ClientBuilder {
pub fn password(mut self, password: &str) -> Self {
self.password = Some(password.to_owned());
self
}
pub fn connect<A: ToSocketAddrs>(&self, addr: A) -> Result<Client> {
let mut conn = Conn(TcpStream::connect(addr)?);
if let Some(password) = &self.password {
conn.auth(password)?;
}
Ok(Client { conn })
}
}
pub struct Response(String);
impl Deref for Response {
type Target = String;
fn deref(&self) -> &Self::Target {
&self.0
}
}