valkey 0.0.0-alpha5

An ergonomic, synchronous Valkey driver
Documentation
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 {
    /// Sends the command to the server.
    fn send(&mut self, cmd: &Command) -> Result<Response> {
        self.0.write_all(cmd.build().as_bytes())?;
        self.0.flush()?;

        // read response
        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))
    }

    /// Authenticates the current connection.
    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(),
        })
    }

    /// Creates a `ClientBuilder` for advanced configuration of a `Client`.
    pub fn builder() -> ClientBuilder {
        ClientBuilder::default()
    }

    /// Sends the command to the server.
    fn send(&mut self, cmd: &Command) -> Result<Response> {
        self.conn.send(cmd)
    }

    /// Sets the string value of a key.
    pub fn set(&mut self, k: &str, v: &str) -> Result<()> {
        let cmd = Command::new("SET").arg(k).arg(v);
        self.send(&cmd)?;
        Ok(())
    }

    /// Gets the value of a key.
    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()))
        }
    }
}

/// A `ClientBuilder` can be used for advanced configuration of a `Client`.
#[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 })
    }
}

/// A response from the server.
pub struct Response(String);

impl Deref for Response {
    type Target = String;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}