#[cfg(feature = "sha-1")]
use crate::util::sha1_hash;
use crate::{
clients::Client,
interfaces::{FunctionInterface, LuaInterface},
prelude::{Error, ErrorKind, FredResult, FromValue},
types::{MultipleKeys, MultipleValues, Value},
utils,
};
use bytes_utils::Str;
use std::{
cmp::Ordering,
collections::HashMap,
convert::TryInto,
fmt,
fmt::Formatter,
hash::{Hash, Hasher},
ops::Deref,
};
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum ScriptDebugFlag {
Yes,
No,
Sync,
}
impl ScriptDebugFlag {
#[cfg(feature = "i-scripts")]
pub(crate) fn to_str(&self) -> Str {
utils::static_str(match *self {
ScriptDebugFlag::Yes => "YES",
ScriptDebugFlag::No => "NO",
ScriptDebugFlag::Sync => "SYNC",
})
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum FnPolicy {
Flush,
Append,
Replace,
}
impl Default for FnPolicy {
fn default() -> Self {
FnPolicy::Append
}
}
impl FnPolicy {
#[cfg(feature = "i-scripts")]
pub(crate) fn to_str(&self) -> Str {
utils::static_str(match *self {
FnPolicy::Flush => "FLUSH",
FnPolicy::Append => "APPEND",
FnPolicy::Replace => "REPLACE",
})
}
pub(crate) fn from_str(s: &str) -> Result<Self, Error> {
Ok(match s {
"flush" | "FLUSH" => FnPolicy::Flush,
"append" | "APPEND" => FnPolicy::Append,
"replace" | "REPLACE" => FnPolicy::Replace,
_ => {
return Err(Error::new(
ErrorKind::InvalidArgument,
"Invalid function restore policy.",
))
},
})
}
}
impl TryFrom<&str> for FnPolicy {
type Error = Error;
fn try_from(value: &str) -> Result<Self, Self::Error> {
FnPolicy::from_str(value)
}
}
impl TryFrom<&String> for FnPolicy {
type Error = Error;
fn try_from(value: &String) -> Result<Self, Self::Error> {
FnPolicy::from_str(value.as_str())
}
}
impl TryFrom<String> for FnPolicy {
type Error = Error;
fn try_from(value: String) -> Result<Self, Self::Error> {
FnPolicy::from_str(value.as_str())
}
}
impl TryFrom<Str> for FnPolicy {
type Error = Error;
fn try_from(value: Str) -> Result<Self, Self::Error> {
FnPolicy::from_str(&value)
}
}
impl TryFrom<&Str> for FnPolicy {
type Error = Error;
fn try_from(value: &Str) -> Result<Self, Self::Error> {
FnPolicy::from_str(value)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Script {
lua: Option<Str>,
hash: Str,
}
impl fmt::Display for Script {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.hash)
}
}
impl Hash for Script {
fn hash<H: Hasher>(&self, state: &mut H) {
self.hash.hash(state);
}
}
impl Ord for Script {
fn cmp(&self, other: &Self) -> Ordering {
self.hash.cmp(&other.hash)
}
}
impl PartialOrd for Script {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Script {
#[cfg(feature = "sha-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha-1")))]
pub fn from_lua<S: Into<Str>>(lua: S) -> Self {
let lua: Str = lua.into();
let hash = Str::from(sha1_hash(&lua));
Script { lua: Some(lua), hash }
}
pub fn from_hash<S: Into<Str>>(hash: S) -> Self {
Script {
lua: None,
hash: hash.into(),
}
}
pub fn lua(&self) -> Option<&Str> {
self.lua.as_ref()
}
pub fn sha1(&self) -> &Str {
&self.hash
}
#[cfg(feature = "sha-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha-1")))]
pub async fn load(&self, client: &Client) -> FredResult<()> {
if let Some(ref lua) = self.lua {
client.script_load_cluster::<(), _>(lua.clone()).await
} else {
Err(Error::new(ErrorKind::Unknown, "Missing lua script contents."))
}
}
pub async fn evalsha<R, C, K, V>(&self, client: &C, keys: K, args: V) -> FredResult<R>
where
R: FromValue,
C: LuaInterface + Send + Sync,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<Error> + Send,
{
client.evalsha(self.hash.clone(), keys, args).await
}
#[cfg(feature = "sha-1")]
#[cfg_attr(docsrs, doc(cfg(feature = "sha-1")))]
pub async fn evalsha_with_reload<R, K, V>(&self, client: &Client, keys: K, args: V) -> FredResult<R>
where
R: FromValue,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<Error> + Send,
{
into!(keys);
try_into!(args);
match client.evalsha(self.hash.clone(), keys.clone(), args.clone()).await {
Err(error) if error.details().starts_with("NOSCRIPT") => {
self.load(client).await?;
client.evalsha(self.hash.clone(), keys, args).await
},
result => result,
}
}
}
#[derive(Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub enum FunctionFlag {
NoWrites,
AllowOOM,
NoCluster,
AllowCrossSlotKeys,
AllowStale,
}
impl FunctionFlag {
#[allow(clippy::should_implement_trait)]
pub fn from_str(s: &str) -> Option<Self> {
Some(match s {
"allow-oom" => FunctionFlag::AllowOOM,
"allow-stale" => FunctionFlag::AllowStale,
"allow-cross-slot-keys" => FunctionFlag::AllowCrossSlotKeys,
"no-writes" => FunctionFlag::NoWrites,
"no-cluster" => FunctionFlag::NoCluster,
_ => return None,
})
}
pub fn to_str(&self) -> &'static str {
match self {
FunctionFlag::AllowCrossSlotKeys => "allow-cross-slot-keys",
FunctionFlag::AllowOOM => "allow-oom",
FunctionFlag::NoCluster => "no-cluster",
FunctionFlag::NoWrites => "no-writes",
FunctionFlag::AllowStale => "allow-stale",
}
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Function {
pub(crate) name: Str,
pub(crate) flags: Vec<FunctionFlag>,
}
impl fmt::Display for Function {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl Hash for Function {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl Ord for Function {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for Function {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Function {
pub fn new<S: Into<Str>>(name: S, flags: Vec<FunctionFlag>) -> Self {
Function {
name: name.into(),
flags,
}
}
pub fn name(&self) -> &Str {
&self.name
}
pub fn flags(&self) -> &[FunctionFlag] {
&self.flags
}
pub async fn fcall<R, C, K, V>(&self, client: &C, keys: K, args: V) -> FredResult<R>
where
R: FromValue,
C: FunctionInterface + Send + Sync,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<Error> + Send,
{
client.fcall(self.name.clone(), keys, args).await
}
pub async fn fcall_ro<R, C, K, V>(&self, client: &C, keys: K, args: V) -> FredResult<R>
where
R: FromValue,
C: FunctionInterface + Send + Sync,
K: Into<MultipleKeys> + Send,
V: TryInto<MultipleValues> + Send,
V::Error: Into<Error> + Send,
{
client.fcall_ro(self.name.clone(), keys, args).await
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct Library {
name: Str,
functions: HashMap<Str, Function>,
}
impl fmt::Display for Library {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl Hash for Library {
fn hash<H: Hasher>(&self, state: &mut H) {
self.name.hash(state);
}
}
impl Ord for Library {
fn cmp(&self, other: &Self) -> Ordering {
self.name.cmp(&other.name)
}
}
impl PartialOrd for Library {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Library {
pub async fn from_code<S>(client: &Client, code: S) -> Result<Self, Error>
where
S: Into<Str>,
{
let code = code.into();
let name: Str = client.function_load_cluster(true, code).await?;
let functions = client
.function_list::<Value, _>(Some(name.deref()), false)
.await?
.as_functions(&name)?;
Ok(Library {
name,
functions: functions.into_iter().map(|f| (f.name.clone(), f)).collect(),
})
}
pub async fn from_name<S>(client: &Client, name: S) -> Result<Self, Error>
where
S: Into<Str>,
{
let name = name.into();
let functions = client
.function_list::<Value, _>(Some(name.deref()), false)
.await?
.as_functions(&name)?;
Ok(Library {
name,
functions: functions.into_iter().map(|f| (f.name.clone(), f)).collect(),
})
}
pub fn name(&self) -> &Str {
&self.name
}
pub fn functions(&self) -> &HashMap<Str, Function> {
&self.functions
}
}