use crate::config::{GlideClientConfiguration, GlideClusterClientConfiguration};
use crate::error::{GlideError, Result};
use crate::executor::CommandExecutor;
use crate::pipeline_options::{PipelineOptions, run_pipeline};
use crate::routes::Route;
use async_trait::async_trait;
use bytes::Bytes;
use glide_core::client::Client as CoreClient;
use glide_core::cluster_scan_container::get_cluster_scan_cursor;
use redis::cluster_routing::RoutingInfo;
use redis::{ClusterScanArgs, Cmd, PushInfo, PushKind, ScanStateRC, Value};
use std::sync::Arc;
use tokio::sync::Mutex as AsyncMutex;
use tokio::sync::mpsc::{UnboundedReceiver, UnboundedSender};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PubSubMessageKind {
Message,
PMessage,
SMessage,
}
#[derive(Debug, Clone)]
pub struct PubSubMessage {
pub kind: PubSubMessageKind,
pub channel: Bytes,
pub payload: Bytes,
pub pattern: Option<Bytes>,
}
type PushRx = Arc<AsyncMutex<UnboundedReceiver<PushInfo>>>;
fn make_push_channel(
has_subscriptions: bool,
) -> (Option<UnboundedSender<PushInfo>>, Option<PushRx>) {
if has_subscriptions {
let (tx, rx) = tokio::sync::mpsc::unbounded_channel();
(Some(tx), Some(Arc::new(AsyncMutex::new(rx))))
} else {
(None, None)
}
}
fn pubsub_rx(rx: &Option<PushRx>) -> Result<&PushRx> {
rx.as_ref()
.ok_or_else(|| GlideError::Request("client has no configured pub/sub subscriptions".into()))
}
async fn recv_pubsub_message(rx: &Option<PushRx>) -> Result<PubSubMessage> {
let mut guard = pubsub_rx(rx)?.lock().await;
loop {
match guard.recv().await {
Some(push) => {
if let Some(msg) = push_to_message(push) {
return Ok(msg);
}
}
None => return Err(GlideError::Request("pub/sub channel closed".into())),
}
}
}
async fn try_recv_pubsub_message(rx: &Option<PushRx>) -> Result<Option<PubSubMessage>> {
let mut guard = pubsub_rx(rx)?.lock().await;
loop {
match guard.try_recv() {
Ok(push) => {
if let Some(msg) = push_to_message(push) {
return Ok(Some(msg));
}
}
Err(tokio::sync::mpsc::error::TryRecvError::Empty) => return Ok(None),
Err(tokio::sync::mpsc::error::TryRecvError::Disconnected) => {
return Err(GlideError::Request("pub/sub channel closed".into()));
}
}
}
}
fn push_to_message(push: PushInfo) -> Option<PubSubMessage> {
let (kind, has_pattern) = match push.kind {
PushKind::Message => (PubSubMessageKind::Message, false),
PushKind::SMessage => (PubSubMessageKind::SMessage, false),
PushKind::PMessage => (PubSubMessageKind::PMessage, true),
_ => return None,
};
let mut data = push.data.into_iter();
if has_pattern {
let pattern = value_to_bytes(data.next()?);
let channel = value_to_bytes(data.next()?);
let payload = value_to_bytes(data.next()?);
Some(PubSubMessage {
kind,
channel,
payload,
pattern: Some(pattern),
})
} else {
let channel = value_to_bytes(data.next()?);
let payload = value_to_bytes(data.next()?);
Some(PubSubMessage {
kind,
channel,
payload,
pattern: None,
})
}
}
fn value_to_bytes(v: Value) -> Bytes {
match v {
Value::BulkString(b) => Bytes::from(b),
Value::SimpleString(s) => Bytes::from(s.into_bytes()),
other => Bytes::from(format!("{other:?}").into_bytes()),
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ClusterScanCursor(String);
impl ClusterScanCursor {
const FINISHED: &'static str = "finished";
pub fn new() -> Self {
ClusterScanCursor(String::new())
}
pub fn from_id(id: impl Into<String>) -> Self {
ClusterScanCursor(id.into())
}
pub fn id(&self) -> &str {
&self.0
}
pub fn is_finished(&self) -> bool {
self.0 == Self::FINISHED
}
}
impl Default for ClusterScanCursor {
fn default() -> Self {
Self::new()
}
}
#[derive(Clone)]
pub struct GlideClient {
inner: CoreClient,
pubsub_rx: Option<PushRx>,
db: i64,
}
impl GlideClient {
pub async fn connect(config: GlideClientConfiguration) -> Result<Self> {
let db = config.database_id;
let request = config.to_request();
let has_subs = config
.pubsub_subscriptions
.as_ref()
.is_some_and(|s| !s.is_empty());
let (sender, pubsub_rx) = make_push_channel(has_subs || config.force_pubsub_channel);
let inner = CoreClient::new(request, sender)
.await
.map_err(GlideError::from)?;
Ok(GlideClient {
inner,
pubsub_rx,
db,
})
}
pub fn core(&self) -> &CoreClient {
&self.inner
}
pub(crate) fn db(&self) -> i64 {
self.db
}
pub async fn get_pubsub_message(&self) -> Result<PubSubMessage> {
recv_pubsub_message(&self.pubsub_rx).await
}
pub async fn try_get_pubsub_message(&self) -> Result<Option<PubSubMessage>> {
try_recv_pubsub_message(&self.pubsub_rx).await
}
pub async fn execute_pipeline(
&self,
pipeline: &redis::Pipeline,
raise_on_error: bool,
options: &PipelineOptions,
) -> Result<Vec<Value>> {
run_pipeline(&self.inner, pipeline, None, raise_on_error, options).await
}
pub async fn update_connection_password(
&self,
password: Option<String>,
immediate_auth: bool,
) -> Result<()> {
let mut client = self.inner.clone();
client
.update_connection_password(password, immediate_auth)
.await
.map_err(GlideError::from)?;
Ok(())
}
}
#[async_trait]
impl CommandExecutor for GlideClient {
async fn execute_command(&self, mut cmd: Cmd, routing: Option<RoutingInfo>) -> Result<Value> {
let mut client = self.inner.clone();
client
.send_command(&mut cmd, routing)
.await
.map_err(GlideError::from)
}
}
#[derive(Clone)]
pub struct GlideClusterClient {
inner: CoreClient,
pubsub_rx: Option<PushRx>,
}
impl GlideClusterClient {
pub async fn connect(config: GlideClusterClientConfiguration) -> Result<Self> {
let request = config.to_request();
let has_subs = config
.pubsub_subscriptions
.as_ref()
.is_some_and(|s| !s.is_empty());
let (sender, pubsub_rx) = make_push_channel(has_subs || config.force_pubsub_channel);
let inner = CoreClient::new(request, sender)
.await
.map_err(GlideError::from)?;
Ok(GlideClusterClient { inner, pubsub_rx })
}
pub fn core(&self) -> &CoreClient {
&self.inner
}
pub async fn get_pubsub_message(&self) -> Result<PubSubMessage> {
recv_pubsub_message(&self.pubsub_rx).await
}
pub async fn try_get_pubsub_message(&self) -> Result<Option<PubSubMessage>> {
try_recv_pubsub_message(&self.pubsub_rx).await
}
pub async fn route_command(&self, mut cmd: Cmd, route: Route) -> Result<Value> {
let routing = route.to_routing_info(Some(&cmd));
let mut client = self.inner.clone();
client
.send_command(&mut cmd, Some(routing))
.await
.map_err(GlideError::from)
}
pub async fn execute_pipeline(
&self,
pipeline: &redis::Pipeline,
raise_on_error: bool,
route: Option<Route>,
options: &PipelineOptions,
) -> Result<Vec<Value>> {
let routing = route.map(|r| r.to_routing_info(None));
run_pipeline(&self.inner, pipeline, routing, raise_on_error, options).await
}
pub async fn update_connection_password(
&self,
password: Option<String>,
immediate_auth: bool,
) -> Result<()> {
let mut client = self.inner.clone();
client
.update_connection_password(password, immediate_auth)
.await
.map_err(GlideError::from)?;
Ok(())
}
pub async fn cluster_scan(
&self,
cursor: &ClusterScanCursor,
match_pattern: Option<&[u8]>,
count: Option<u32>,
object_type: Option<crate::commands::options::ObjectType>,
) -> Result<(ClusterScanCursor, Vec<Bytes>)> {
let scan_state = if cursor.0.is_empty() || cursor.0 == "0" {
ScanStateRC::new()
} else {
get_cluster_scan_cursor(cursor.0.clone()).map_err(GlideError::from)?
};
let mut builder = ClusterScanArgs::builder();
if let Some(p) = match_pattern {
builder = builder.with_match_pattern(p.to_vec());
}
if let Some(c) = count {
builder = builder.with_count(c);
}
if let Some(t) = object_type {
builder = builder.with_object_type(t.to_redis());
}
let args = builder.build();
let mut client = self.inner.clone();
let reply = client
.cluster_scan(&scan_state, args)
.await
.map_err(GlideError::from)?;
let items = match reply {
Value::Array(items) => items,
other => {
return Err(GlideError::Request(format!(
"unexpected cluster scan reply: {other:?}"
)));
}
};
let [cursor_val, keys_val] = <[Value; 2]>::try_from(items).map_err(|items| {
GlideError::Request(format!("unexpected cluster scan reply arity: {items:?}"))
})?;
let next = ClusterScanCursor(crate::value::to_string(cursor_val)?);
let keys = match keys_val {
Value::Array(elems) => elems
.into_iter()
.map(crate::value::to_bytes)
.collect::<Result<Vec<_>>>()?,
Value::Nil => Vec::new(),
other => vec![crate::value::to_bytes(other)?],
};
Ok((next, keys))
}
}
#[async_trait]
impl CommandExecutor for GlideClusterClient {
async fn execute_command(&self, mut cmd: Cmd, routing: Option<RoutingInfo>) -> Result<Value> {
let mut client = self.inner.clone();
client
.send_command(&mut cmd, routing)
.await
.map_err(GlideError::from)
}
}
mod connection;
pub use connection::{GlidePipelineTarget, PipelineExt};
impl crate::commands::core::AsyncCommands for GlideClient {
fn glide_send_owned<'a>(&'a self, mut cmd: Cmd) -> redis::RedisFuture<'a, Value> {
let mut client = self.inner.clone();
Box::pin(async move { client.send_command(&mut cmd, None).await })
}
}
impl crate::commands::core::AsyncCommands for GlideClusterClient {
fn glide_send_owned<'a>(&'a self, mut cmd: Cmd) -> redis::RedisFuture<'a, Value> {
let mut client = self.inner.clone();
Box::pin(async move { client.send_command(&mut cmd, None).await })
}
}