soblendwa 0.1.0

SoblendWA: a blazing fast WhatsApp Web multi-device client library written in Rust. Crafted by Soblend Development Studio.
Documentation
use ::redis::aio::ConnectionManager;
use async_trait::async_trait;

use super::batched::{Backend, BatchedStore, KeyWrite};
use super::{Creds, KeyKind};
use crate::error::{Error, Result};

fn failed(err: impl std::fmt::Display) -> Error {
    Error::Store(format!("redis: {err}"))
}

pub struct Redis {
    connection: ConnectionManager,
    prefix: String,
}

pub type RedisStore = BatchedStore<Redis>;

impl BatchedStore<Redis> {
    pub async fn connect(url: &str) -> Result<Self> {
        Self::connect_account(url, "default").await
    }

    pub async fn connect_account(url: &str, account: &str) -> Result<Self> {
        let client = ::redis::Client::open(url).map_err(failed)?;
        let connection = ConnectionManager::new(client).await.map_err(failed)?;
        Ok(BatchedStore::new(Redis { connection, prefix: format!("soblendwa:{account}") }))
    }
}

impl Redis {
    fn creds_key(&self) -> String {
        format!("{}:creds", self.prefix)
    }

    fn table(&self, kind: KeyKind) -> String {
        format!("{}:{}", self.prefix, kind.as_str())
    }
}

#[async_trait]
impl Backend for Redis {
    async fn load_creds(&self) -> Result<Option<Creds>> {
        let raw: Option<String> = ::redis::cmd("GET")
            .arg(self.creds_key())
            .query_async(&mut self.connection.clone())
            .await
            .map_err(failed)?;
        raw.map(|json| serde_json::from_str(&json).map_err(Error::from)).transpose()
    }

    async fn save_creds(&self, creds: &Creds) -> Result<()> {
        ::redis::cmd("SET")
            .arg(self.creds_key())
            .arg(serde_json::to_string(creds)?)
            .query_async::<()>(&mut self.connection.clone())
            .await
            .map_err(failed)
    }

    async fn fetch(&self, kind: KeyKind, key: &str) -> Result<Option<Vec<u8>>> {
        ::redis::cmd("HGET")
            .arg(self.table(kind))
            .arg(key)
            .query_async(&mut self.connection.clone())
            .await
            .map_err(failed)
    }

    async fn list(&self, kind: KeyKind) -> Result<Vec<String>> {
        ::redis::cmd("HKEYS").arg(self.table(kind)).query_async(&mut self.connection.clone()).await.map_err(failed)
    }

    async fn apply(&self, writes: &[KeyWrite]) -> Result<()> {
        let mut pipe = ::redis::pipe();
        pipe.atomic();
        for write in writes {
            match write {
                KeyWrite::Set(kind, key, value) => pipe.cmd("HSET").arg(self.table(*kind)).arg(key).arg(value).ignore(),
                KeyWrite::Remove(kind, key) => pipe.cmd("HDEL").arg(self.table(*kind)).arg(key).ignore(),
                KeyWrite::Clear => {
                    let mut keys: Vec<String> = KeyKind::ALL.into_iter().map(|kind| self.table(kind)).collect();
                    keys.push(self.creds_key());
                    pipe.cmd("DEL").arg(keys).ignore()
                }
            };
        }
        pipe.query_async::<()>(&mut self.connection.clone()).await.map_err(failed)
    }
}