use crate::{
Result,
client::{PreparedCommand, prepare_command},
commands::{ModuleInfo, RequestPolicy, ResponsePolicy},
resp::{Response, cmd, serialize_flag},
};
use serde::{Deserialize, Deserializer, Serialize, de};
use std::collections::HashMap;
pub trait ConnectionCommands<'a>: Sized {
#[must_use]
fn auth(
self,
username: impl Serialize,
password: impl Serialize,
) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("AUTH").arg(username).arg(password).cluster_info(
RequestPolicy::AllNodes,
ResponsePolicy::AllSucceeded,
1,
),
)
}
#[must_use]
fn client_caching(self, mode: ClientCachingMode) -> PreparedCommand<'a, Self, Option<()>> {
prepare_command(self, cmd("CLIENT").arg("CACHING").arg(mode))
}
#[must_use]
fn client_getname<R: Response>(self) -> PreparedCommand<'a, Self, R> {
prepare_command(self, cmd("CLIENT").arg("GETNAME"))
}
#[must_use]
fn client_getredir(self) -> PreparedCommand<'a, Self, i64> {
prepare_command(self, cmd("CLIENT").arg("GETREDIR"))
}
#[must_use]
fn client_help<R: Response>(self) -> PreparedCommand<'a, Self, R> {
prepare_command(self, cmd("CLIENT").arg("HELP"))
}
#[must_use]
fn client_id(self) -> PreparedCommand<'a, Self, i64> {
prepare_command(self, cmd("CLIENT").arg("ID"))
}
#[must_use]
fn client_info(self) -> PreparedCommand<'a, Self, ClientInfo> {
prepare_command(self, cmd("CLIENT").arg("INFO"))
}
#[must_use]
fn client_kill(self, options: ClientKillOptions) -> PreparedCommand<'a, Self, usize> {
prepare_command(self, cmd("CLIENT").arg("KILL").arg(options))
}
#[must_use]
fn client_list(
self,
options: ClientListOptions,
) -> PreparedCommand<'a, Self, ClientListResult> {
prepare_command(self, cmd("CLIENT").arg("LIST").arg(options))
}
#[must_use]
fn client_no_evict(self, no_evict: bool) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("CLIENT")
.arg("NO-EVICT")
.arg(if no_evict { "ON" } else { "OFF" })
.cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1),
)
}
#[must_use]
fn client_no_touch(self, no_touch: bool) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("CLIENT")
.arg("NO-TOUCH")
.arg(if no_touch { "ON" } else { "OFF" })
.cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1),
)
}
#[must_use]
fn client_pause(self, timeout: u64, mode: ClientPauseMode) -> PreparedCommand<'a, Self, ()> {
prepare_command(self, cmd("CLIENT").arg("PAUSE").arg(timeout).arg(mode))
}
#[must_use]
fn client_reply(self, mode: ClientReplyMode) -> PreparedCommand<'a, Self, ()> {
let command = cmd("CLIENT").arg("REPLY").arg(mode);
prepare_command(
self,
match mode {
ClientReplyMode::Skip => command,
ClientReplyMode::On | ClientReplyMode::Off => {
command.cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1)
}
},
)
}
#[must_use]
fn client_setname(self, connection_name: impl Serialize) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("CLIENT")
.arg("SETNAME")
.arg(connection_name)
.cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1),
)
}
#[must_use]
fn client_setinfo(
self,
attr: ClientInfoAttribute,
info: impl Serialize,
) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("CLIENT")
.arg("SETINFO")
.arg(attr)
.arg(info)
.cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1),
)
}
#[must_use]
fn client_tracking(
self,
status: ClientTrackingStatus,
options: ClientTrackingOptions,
) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("CLIENT")
.arg("TRACKING")
.arg(status)
.arg(options)
.cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1),
)
}
#[must_use]
fn client_trackinginfo(self) -> PreparedCommand<'a, Self, ClientTrackingInfo> {
prepare_command(self, cmd("CLIENT").arg("TRACKINGINFO"))
}
#[must_use]
fn client_unblock(
self,
client_id: i64,
mode: ClientUnblockMode,
) -> PreparedCommand<'a, Self, bool> {
prepare_command(self, cmd("CLIENT").arg("UNBLOCK").arg(client_id).arg(mode))
}
#[must_use]
fn client_unpause(self) -> PreparedCommand<'a, Self, bool> {
prepare_command(self, cmd("CLIENT").arg("UNPAUSE"))
}
#[must_use]
fn echo<R: Response>(self, message: impl Serialize) -> PreparedCommand<'a, Self, R> {
prepare_command(self, cmd("ECHO").arg(message))
}
#[must_use]
fn hello(self, options: HelloOptions) -> PreparedCommand<'a, Self, HelloResult> {
prepare_command(self, cmd("HELLO").arg(options))
}
#[must_use]
fn ping<R: Response>(self, message: impl Serialize) -> PreparedCommand<'a, Self, R> {
prepare_command(
self,
cmd("PING").arg(message).cluster_info(
RequestPolicy::AllShards,
ResponsePolicy::AllSucceeded,
1,
),
)
}
#[must_use]
fn quit(self) -> PreparedCommand<'a, Self, ()> {
prepare_command(self, cmd("QUIT"))
}
#[must_use]
fn reset(self) -> PreparedCommand<'a, Self, ()> {
prepare_command(
self,
cmd("RESET").cluster_info(RequestPolicy::AllNodes, ResponsePolicy::AllSucceeded, 1),
)
}
#[must_use]
fn select(self, index: usize) -> PreparedCommand<'a, Self, ()> {
prepare_command(self, cmd("SELECT").arg(index))
}
}
#[derive(Serialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ClientCachingMode {
Yes,
No,
}
#[derive(Debug)]
#[non_exhaustive]
pub struct ClientInfo {
pub id: i64,
pub addr: String,
pub laddr: String,
pub fd: u32,
pub name: String,
pub age: u32,
pub idle: u32,
pub flags: String,
pub db: usize,
pub sub: usize,
pub psub: usize,
pub ssub: usize,
pub multi: usize,
pub qbuf: usize,
pub qbuf_free: usize,
pub argv_mem: usize,
pub multi_mem: usize,
pub obl: usize,
pub oll: usize,
pub omem: usize,
pub tot_mem: usize,
pub events: String,
pub cmd: String,
pub user: String,
pub redir: i64,
pub resp: i32,
pub additional_arguments: HashMap<String, String>,
}
impl ClientInfo {
pub fn from_line(line: &str) -> Result<ClientInfo> {
let mut values: HashMap<String, String> = line
.trim_end()
.split(' ')
.map(|kvp| {
let mut iter = kvp.split('=');
match (iter.next(), iter.next()) {
(Some(key), None) => (key.to_owned(), "".to_owned()),
(Some(key), Some(value)) => (key.to_owned(), value.to_owned()),
_ => ("".to_owned(), "".to_owned()),
}
})
.collect();
Ok(ClientInfo {
id: values
.remove("id")
.map(|id| id.parse::<i64>().unwrap_or_default())
.unwrap_or_default(),
addr: values.remove("addr").unwrap_or_default(),
laddr: values.remove("laddr").unwrap_or_default(),
fd: values
.remove("fd")
.map(|id| id.parse::<u32>().unwrap_or_default())
.unwrap_or_default(),
name: values.remove("name").unwrap_or_default(),
age: values
.remove("age")
.map(|id| id.parse::<u32>().unwrap_or_default())
.unwrap_or_default(),
idle: values
.remove("idle")
.map(|id| id.parse::<u32>().unwrap_or_default())
.unwrap_or_default(),
flags: values.remove("flags").unwrap_or_default(),
db: values
.remove("db")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
sub: values
.remove("sub")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
psub: values
.remove("psub")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
ssub: values
.remove("ssub")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
multi: values
.remove("multi")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
qbuf: values
.remove("qbuf")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
qbuf_free: values
.remove("qbuf-free")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
argv_mem: values
.remove("argv-mem")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
multi_mem: values
.remove("multi-mem")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
obl: values
.remove("obl")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
oll: values
.remove("oll")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
omem: values
.remove("omem")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
tot_mem: values
.remove("tot-mem")
.map(|id| id.parse::<usize>().unwrap_or_default())
.unwrap_or_default(),
events: values.remove("events").unwrap_or_default(),
cmd: values.remove("cmd").unwrap_or_default(),
user: values.remove("user").unwrap_or_default(),
redir: values
.remove("redir")
.map(|id| id.parse::<i64>().unwrap_or_default())
.unwrap_or_default(),
resp: values
.remove("resp")
.map(|id| id.parse::<i32>().unwrap_or_default())
.unwrap_or_default(),
additional_arguments: values,
})
}
}
impl<'de> Deserialize<'de> for ClientInfo {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let line = <&str>::deserialize(deserializer)?;
ClientInfo::from_line(line).map_err(de::Error::custom)
}
}
#[derive(Serialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ClientType {
Normal,
Master,
Replica,
PubSub,
}
#[derive(Default, Serialize)]
pub struct ClientListOptions {
#[serde(rename = "TYPE", skip_serializing_if = "Option::is_none")]
client_type: Option<ClientType>,
#[serde(rename = "ID", skip_serializing_if = "Vec::is_empty")]
client_ids: Vec<i64>,
}
impl ClientListOptions {
#[must_use]
pub fn client_type(mut self, client_type: ClientType) -> Self {
self.client_type = Some(client_type);
self
}
pub fn client_ids(mut self, client_ids: impl IntoIterator<Item = i64>) -> Self {
self.client_ids.extend(client_ids);
self
}
pub fn client_id(mut self, client_id: i64) -> Self {
self.client_ids.push(client_id);
self
}
}
#[derive(Debug)]
#[non_exhaustive]
pub struct ClientListResult {
pub client_infos: Vec<ClientInfo>,
}
impl<'de> Deserialize<'de> for ClientListResult {
fn deserialize<D>(deserializer: D) -> std::result::Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let lines = <&str>::deserialize(deserializer)?;
let client_infos: Result<Vec<ClientInfo>> = lines
.split('\n')
.filter(|line| !line.trim().is_empty())
.map(ClientInfo::from_line)
.collect();
Ok(Self {
client_infos: client_infos.map_err(de::Error::custom)?,
})
}
}
#[derive(Serialize)]
#[serde(rename_all(serialize = "UPPERCASE"))]
enum YesNo {
Yes,
No,
}
#[derive(Default, Serialize)]
#[serde(rename_all(serialize = "UPPERCASE"))]
pub struct ClientKillOptions<'a> {
#[serde(skip_serializing_if = "Option::is_none")]
id: Option<i64>,
#[serde(rename = "TYPE", skip_serializing_if = "Option::is_none")]
client_type: Option<ClientType>,
#[serde(rename = "USER", skip_serializing_if = "Option::is_none")]
username: Option<&'a str>,
#[serde(skip_serializing_if = "Option::is_none")]
addr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
laddr: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
skipme: Option<YesNo>,
#[serde(skip_serializing_if = "Option::is_none")]
maxage: Option<u64>,
}
impl<'a> ClientKillOptions<'a> {
#[must_use]
pub fn id(mut self, client_id: i64) -> Self {
self.id = Some(client_id);
self
}
#[must_use]
pub fn client_type(mut self, client_type: ClientType) -> Self {
self.client_type = Some(client_type);
self
}
#[must_use]
pub fn user(mut self, username: &'a str) -> Self {
self.username = Some(username);
self
}
#[must_use]
pub fn addr(mut self, ip: &'a str, port: u16) -> Self {
self.addr = Some(format!("{ip}:{port}"));
self
}
#[must_use]
pub fn laddr(mut self, ip: &'a str, port: u16) -> Self {
self.laddr = Some(format!("{ip}:{port}"));
self
}
#[must_use]
pub fn skip_me(mut self, skip_me: bool) -> Self {
self.skipme = Some(if skip_me { YesNo::Yes } else { YesNo::No });
self
}
#[must_use]
pub fn max_age(mut self, seconds: u64) -> Self {
self.maxage = Some(seconds);
self
}
}
#[derive(Default, Serialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ClientPauseMode {
Write,
#[default]
All,
}
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ClientReplyMode {
On,
Off,
Skip,
}
#[derive(Serialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ClientTrackingStatus {
On,
Off,
}
#[derive(Clone, Default, Serialize)]
#[serde(rename_all(serialize = "UPPERCASE"))]
pub struct ClientTrackingOptions {
#[serde(skip_serializing_if = "Vec::is_empty")]
prefix: Vec<String>,
#[serde(
skip_serializing_if = "std::ops::Not::not",
serialize_with = "serialize_flag"
)]
bcast: bool,
#[serde(
skip_serializing_if = "std::ops::Not::not",
serialize_with = "serialize_flag"
)]
optin: bool,
#[serde(
skip_serializing_if = "std::ops::Not::not",
serialize_with = "serialize_flag"
)]
optout: bool,
#[serde(
skip_serializing_if = "std::ops::Not::not",
serialize_with = "serialize_flag"
)]
noloop: bool,
}
impl ClientTrackingOptions {
pub fn broadcasting(mut self) -> Self {
self.bcast = true;
self
}
pub fn prefix(mut self, prefix: impl Into<String>) -> Self {
self.prefix.push(prefix.into());
self
}
pub fn optin(mut self) -> Self {
self.optin = true;
self
}
pub fn optout(mut self) -> Self {
self.optout = true;
self
}
pub fn noloop(mut self) -> Self {
self.noloop = true;
self
}
}
#[derive(Deserialize)]
#[non_exhaustive]
pub struct ClientTrackingInfo {
pub flags: Vec<String>,
pub redirect: i64,
pub prefixes: Vec<String>,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "UPPERCASE")]
#[non_exhaustive]
pub enum ClientUnblockMode {
#[default]
Timeout,
Error,
}
#[derive(Default, Serialize)]
#[serde(rename_all = "UPPERCASE")]
pub struct HelloOptions<'a> {
#[serde(rename = "", skip_serializing_if = "Option::is_none")]
protover: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
auth: Option<(&'a str, &'a str)>,
#[serde(skip_serializing_if = "Option::is_none")]
setname: Option<&'a str>,
}
impl<'a> HelloOptions<'a> {
#[must_use]
pub fn new(protover: u32) -> Self {
Self {
protover: Some(protover),
..Default::default()
}
}
#[must_use]
pub fn auth(mut self, username: &'a str, password: &'a str) -> Self {
self.auth = Some((username, password));
self
}
#[must_use]
pub fn set_name(mut self, client_name: &'a str) -> Self {
self.setname = Some(client_name);
self
}
}
#[derive(Deserialize)]
#[non_exhaustive]
pub struct HelloResult {
pub server: String,
pub version: String,
pub proto: usize,
pub id: i64,
pub mode: String,
#[serde(default)]
pub role: String,
#[serde(default)]
pub modules: Vec<ModuleInfo>,
}
#[derive(Serialize)]
#[serde(rename_all = "SCREAMING-KEBAB-CASE")]
#[non_exhaustive]
pub enum ClientInfoAttribute {
LibName,
LibVer,
}