use crate::ValkeyFuture;
use crate::ValkeyResult;
use crate::cmd::cmd;
use crate::commands::core::AsyncCommands;
use crate::value::FromValkeyValue;
use crate::value::ValkeyValue;
use crate::write::ToValkeyArgs;
use glide_core::scripts_container::add_script;
use glide_core::scripts_container::remove_script;
#[doc(hidden)]
#[sealed::sealed(pub(crate))]
pub trait ScriptInvoke {
fn glide_invoke_script<'a>(
&'a self,
hash: &'a str,
keys: &'a [Vec<u8>],
args: &'a [Vec<u8>],
) -> ValkeyFuture<'a, ValkeyValue>;
}
#[cfg(feature = "sync")]
#[doc(hidden)]
#[sealed::sealed(pub(crate))]
pub trait ScriptInvokeSync {
fn glide_invoke_script(
&self,
hash: &str,
keys: &[Vec<u8>],
args: &[Vec<u8>],
) -> ValkeyResult<ValkeyValue>;
}
#[derive(Debug)]
pub struct Script {
code: String,
hash: String,
}
impl Script {
pub fn new(code: &str) -> Script {
let hash = add_script(code.as_bytes());
Script {
code: code.to_string(),
hash,
}
}
pub fn get_hash(&self) -> &str {
&self.hash
}
#[must_use]
pub fn arg<'a, T: ToValkeyArgs>(&'a self, arg: T) -> ScriptInvocation<'a> {
let mut invocation = self.prepare_invoke();
invocation.arg(arg);
invocation
}
#[must_use]
pub fn key<'a, T: ToValkeyArgs>(&'a self, key: T) -> ScriptInvocation<'a> {
let mut invocation = self.prepare_invoke();
invocation.key(key);
invocation
}
#[must_use]
pub fn prepare_invoke(&self) -> ScriptInvocation<'_> {
ScriptInvocation {
script: self,
args: Vec::new(),
keys: Vec::new(),
}
}
pub async fn invoke_async<C: ScriptInvoke, T: FromValkeyValue>(
&self,
con: &C,
) -> ValkeyResult<T> {
self.prepare_invoke().invoke_async(con).await
}
#[cfg(feature = "sync")]
pub fn invoke<C: ScriptInvokeSync, T: FromValkeyValue>(&self, con: &C) -> ValkeyResult<T> {
self.prepare_invoke().invoke(con)
}
pub async fn load_async<C: AsyncCommands>(&self, con: &C) -> ValkeyResult<String> {
let mut load = cmd("SCRIPT");
load.arg("LOAD").arg(self.code.as_bytes());
String::from_owned_valkey_value(con.glide_send_command(load).await?)
}
#[cfg(feature = "sync")]
pub fn load<C: crate::commands::core::Commands>(&self, con: &C) -> ValkeyResult<String> {
let mut load = cmd("SCRIPT");
load.arg("LOAD").arg(self.code.as_bytes());
String::from_owned_valkey_value(con.glide_send_command(load)?)
}
}
impl Clone for Script {
fn clone(&self) -> Self {
add_script(self.code.as_bytes());
Script {
code: self.code.clone(),
hash: self.hash.clone(),
}
}
}
impl Drop for Script {
fn drop(&mut self) {
remove_script(&self.hash);
}
}
#[derive(Debug, Clone)]
pub struct ScriptInvocation<'a> {
script: &'a Script,
args: Vec<Vec<u8>>,
keys: Vec<Vec<u8>>,
}
impl ScriptInvocation<'_> {
pub fn arg<T: ToValkeyArgs>(&mut self, arg: T) -> &mut Self {
arg.write_valkey_args(&mut self.args);
self
}
pub fn key<T: ToValkeyArgs>(&mut self, key: T) -> &mut Self {
key.write_valkey_args(&mut self.keys);
self
}
pub async fn invoke_async<C: ScriptInvoke, T: FromValkeyValue>(
&self,
con: &C,
) -> ValkeyResult<T> {
let value = con
.glide_invoke_script(&self.script.hash, &self.keys, &self.args)
.await?;
T::from_owned_valkey_value(value)
}
#[cfg(feature = "sync")]
pub fn invoke<C: ScriptInvokeSync, T: FromValkeyValue>(&self, con: &C) -> ValkeyResult<T> {
let value = con.glide_invoke_script(&self.script.hash, &self.keys, &self.args)?;
T::from_owned_valkey_value(value)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha1_matches_server_semantics() {
let script = Script::new("return 1");
assert_eq!(
script.get_hash(),
"e0e1f9fabfc9d4800c877a703b823ac0578ff8db"
);
}
#[test]
fn invocation_collects_keys_and_args() {
let script = Script::new("return KEYS[1]");
let mut invocation = script.prepare_invoke();
invocation.key("k1").key("k2").arg("a1").arg(2);
assert_eq!(invocation.keys, vec![b"k1".to_vec(), b"k2".to_vec()]);
assert_eq!(invocation.args, vec![b"a1".to_vec(), b"2".to_vec()]);
}
}