use std::sync::Mutex;
use std::time::Duration;
pub(crate) struct RespConn {
addr: String,
password: Option<String>,
stream: Mutex<Option<std::net::TcpStream>>,
}
pub(crate) enum RespReply {
Ok,
Int(i64),
Bulk(Option<Vec<u8>>),
Error(String),
}
impl RespConn {
pub(crate) fn new(addr: impl Into<String>, password: Option<String>) -> Self {
RespConn { addr: addr.into(), password, stream: Mutex::new(None) }
}
fn connect(&self) -> std::io::Result<std::net::TcpStream> {
let stream = std::net::TcpStream::connect(&self.addr)?;
stream.set_read_timeout(Some(Duration::from_secs(5)))?;
stream.set_write_timeout(Some(Duration::from_secs(5)))?;
Ok(stream)
}
pub(crate) fn cmd(&self, args: &[&[u8]]) -> std::io::Result<RespReply> {
use std::io::Write;
let mut guard = self.stream.lock().unwrap();
if guard.is_none() {
let mut s = self.connect()?;
if let Some(ref pw) = self.password {
let auth_frame = resp_array(&[b"AUTH", pw.as_bytes()]);
s.write_all(&auth_frame)?;
read_reply(&mut s)?; }
*guard = Some(s);
}
let frame = resp_array(args);
let stream = guard.as_mut().unwrap();
if stream.write_all(&frame).is_err() {
*guard = None;
drop(guard);
return self.cmd(args);
}
match read_reply(stream)? {
RespReply::Error(msg) => Err(std::io::Error::new(std::io::ErrorKind::Other, msg)),
reply => Ok(reply),
}
}
}
fn resp_array(args: &[&[u8]]) -> Vec<u8> {
let mut out = format!("*{}\r\n", args.len()).into_bytes();
for arg in args {
out.extend_from_slice(format!("${}\r\n", arg.len()).as_bytes());
out.extend_from_slice(arg);
out.extend_from_slice(b"\r\n");
}
out
}
fn read_reply(stream: &mut std::net::TcpStream) -> std::io::Result<RespReply> {
use std::io::{BufRead, BufReader, Read};
let mut reader = BufReader::new(stream);
let mut line = String::new();
reader.read_line(&mut line)?;
let line = line.trim_end_matches("\r\n");
match line.chars().next() {
Some('+') => Ok(RespReply::Ok),
Some(':') => {
let n = line[1..].parse::<i64>().unwrap_or(0);
Ok(RespReply::Int(n))
}
Some('-') => Ok(RespReply::Error(line[1..].to_string())),
Some('$') => {
let len = line[1..].parse::<i64>().unwrap_or(-1);
if len < 0 {
return Ok(RespReply::Bulk(None));
}
let mut buf = vec![0u8; len as usize + 2]; reader.read_exact(&mut buf)?;
buf.truncate(len as usize);
Ok(RespReply::Bulk(Some(buf)))
}
_ => Ok(RespReply::Ok), }
}