pub mod config;
pub mod connection;
pub mod pool;
pub mod resp;
pub use config::RedisConfig;
pub use connection::Connection;
pub use pool::{Pool, PooledConnection};
pub use resp::Value;
use crate::store::{BoxFuture, Cache, decode, prefixed, record};
use rustlavel_core::{Error, Json, Result};
use std::time::Duration;
#[derive(Clone)]
pub struct RedisStore {
pool: Pool,
prefix: String,
}
impl RedisStore {
pub fn connect(url: &str) -> Result<Self> {
Ok(RedisStore::new(RedisConfig::from_url(url)?, ""))
}
pub fn new(config: RedisConfig, prefix: impl Into<String>) -> Self {
RedisStore { pool: Pool::new(config), prefix: prefix.into() }
}
pub fn pool(&self) -> &Pool {
&self.pool
}
pub async fn verify(&self) -> Result<()> {
self.pool.verify().await
}
pub async fn ping(&self) -> Result<String> {
let reply = self.pool.command(&[b"PING"]).await?.into_result()?;
reply
.as_str()
.map(str::to_string)
.ok_or_else(|| Error::msg("Redis answered PING with something other than a status"))
}
pub async fn expire(&self, key: &str, seconds: u64) -> Result<bool> {
let full = prefixed(&self.prefix, key);
let seconds = seconds.to_string();
let reply = self
.pool
.command(&[b"EXPIRE", full.as_bytes(), seconds.as_bytes()])
.await?
.into_result()?;
Ok(reply.as_i64() == Some(1))
}
pub async fn pexpire(&self, key: &str, ttl: Duration) -> Result<bool> {
let full = prefixed(&self.prefix, key);
let millis = (ttl.as_millis() as u64).max(1).to_string();
let reply = self
.pool
.command(&[b"PEXPIRE", full.as_bytes(), millis.as_bytes()])
.await?
.into_result()?;
Ok(reply.as_i64() == Some(1))
}
pub async fn command(&self, args: &[&[u8]]) -> Result<Value> {
self.pool.command(args).await?.into_result()
}
async fn integer(&self, args: &[&[u8]]) -> Result<i64> {
let reply = self.pool.command(args).await?.into_result()?;
reply.as_i64().ok_or_else(|| {
Error::msg(format!("expected an integer reply from Redis, got {reply:?}"))
})
}
}
impl Cache for RedisStore {
fn driver(&self) -> &'static str {
"redis"
}
fn get<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Json>>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
let reply = self.pool.command(&[b"GET", full.as_bytes()]).await?.into_result()?;
let found = reply.as_str().and_then(decode);
record(found.is_some(), "redis", key);
Ok(found)
})
}
fn put<'a>(&'a self, key: &'a str, value: Json, ttl: Duration) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
if ttl.is_zero() {
self.pool.command(&[b"DEL", full.as_bytes()]).await?.into_result()?;
return Ok(());
}
let millis = (ttl.as_millis() as u64).max(1).to_string();
let payload = value.to_string();
self.pool
.command(&[b"SET", full.as_bytes(), payload.as_bytes(), b"PX", millis.as_bytes()])
.await?
.into_result()?;
Ok(())
})
}
fn forever<'a>(&'a self, key: &'a str, value: Json) -> BoxFuture<'a, Result<()>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
let payload = value.to_string();
self.pool
.command(&[b"SET", full.as_bytes(), payload.as_bytes()])
.await?
.into_result()?;
Ok(())
})
}
fn forget<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
Ok(self.integer(&[b"DEL", full.as_bytes()]).await? > 0)
})
}
fn flush(&self) -> BoxFuture<'_, Result<()>> {
Box::pin(async move {
self.pool.command(&[b"FLUSHDB"]).await?.into_result()?;
Ok(())
})
}
fn has<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<bool>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
Ok(self.integer(&[b"EXISTS", full.as_bytes()]).await? > 0)
})
}
fn increment<'a>(&'a self, key: &'a str, by: i64) -> BoxFuture<'a, Result<i64>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
let by = by.to_string();
self.integer(&[b"INCRBY", full.as_bytes(), by.as_bytes()]).await
})
}
fn decrement<'a>(&'a self, key: &'a str, by: i64) -> BoxFuture<'a, Result<i64>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
let by = by.to_string();
self.integer(&[b"DECRBY", full.as_bytes(), by.as_bytes()]).await
})
}
fn increment_within<'a>(
&'a self,
key: &'a str,
by: i64,
ttl: Duration,
) -> BoxFuture<'a, Result<i64>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
let millis = (ttl.as_millis() as u64).max(1).to_string();
self.pool
.command(&[b"SET", full.as_bytes(), b"0", b"PX", millis.as_bytes(), b"NX"])
.await?
.into_result()?;
let by = by.to_string();
self.integer(&[b"INCRBY", full.as_bytes(), by.as_bytes()]).await
})
}
fn ttl<'a>(&'a self, key: &'a str) -> BoxFuture<'a, Result<Option<Duration>>> {
Box::pin(async move {
let full = prefixed(&self.prefix, key);
let millis = self.integer(&[b"PTTL", full.as_bytes()]).await?;
Ok((millis >= 0).then(|| Duration::from_millis(millis as u64)))
})
}
}