Skip to main content

synap_sdk/
scripting.rs

1//! Lua scripting support
2
3use crate::client::SynapClient;
4use crate::error::Result;
5use serde::de::DeserializeOwned;
6use serde::{Deserialize, Serialize};
7use serde_json::{Value, json};
8
9/// Options for executing scripts (keys, args, timeout)
10#[derive(Debug, Clone, Default, Serialize, Deserialize)]
11pub struct ScriptEvalOptions {
12    #[serde(default)]
13    pub keys: Vec<String>,
14    #[serde(default)]
15    pub args: Vec<Value>,
16    #[serde(rename = "timeout_ms")]
17    pub timeout_ms: Option<u64>,
18}
19
20/// Response returned by EVAL/EVALSHA commands
21#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct ScriptEvalResponse<T> {
23    pub result: T,
24    pub sha1: String,
25}
26
27/// Response for SCRIPT EXISTS
28#[derive(Debug, Clone, Serialize, Deserialize)]
29pub struct ScriptExistsResponse {
30    pub exists: Vec<bool>,
31}
32
33/// Response for SCRIPT FLUSH
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ScriptFlushResponse {
36    pub cleared: u64,
37}
38
39/// Response for SCRIPT KILL
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ScriptKillResponse {
42    pub terminated: bool,
43}
44
45/// Lua scripting manager
46#[derive(Clone)]
47pub struct ScriptManager {
48    client: SynapClient,
49}
50
51impl ScriptManager {
52    pub(crate) fn new(client: SynapClient) -> Self {
53        Self { client }
54    }
55
56    /// Execute a Lua script using EVAL
57    pub async fn eval<T>(
58        &self,
59        script: &str,
60        options: ScriptEvalOptions,
61    ) -> Result<ScriptEvalResponse<T>>
62    where
63        T: DeserializeOwned,
64    {
65        let payload = json!({
66            "script": script,
67            "keys": options.keys,
68            "args": options.args,
69            "timeout_ms": options.timeout_ms,
70        });
71
72        let response = self.client.send_command("script.eval", payload).await?;
73        self.parse_eval_response(response)
74    }
75
76    /// Execute a cached script using SHA1 hash
77    pub async fn evalsha<T>(
78        &self,
79        sha1: &str,
80        options: ScriptEvalOptions,
81    ) -> Result<ScriptEvalResponse<T>>
82    where
83        T: DeserializeOwned,
84    {
85        let payload = json!({
86            "sha1": sha1,
87            "keys": options.keys,
88            "args": options.args,
89            "timeout_ms": options.timeout_ms,
90        });
91
92        let response = self.client.send_command("script.evalsha", payload).await?;
93        self.parse_eval_response(response)
94    }
95
96    /// Load a script into the cache and return its SHA1 hash
97    pub async fn load(&self, script: &str) -> Result<String> {
98        let payload = json!({ "script": script });
99        let response = self.client.send_command("script.load", payload).await?;
100        Ok(response["sha1"].as_str().unwrap_or_default().to_string())
101    }
102
103    /// Check whether scripts exist in cache
104    pub async fn exists(&self, hashes: &[impl AsRef<str>]) -> Result<Vec<bool>> {
105        let payload = json!({
106            "hashes": hashes.iter().map(|h| h.as_ref()).collect::<Vec<_>>()
107        });
108        let response = self.client.send_command("script.exists", payload).await?;
109        let parsed: ScriptExistsResponse = serde_json::from_value(response)?;
110        Ok(parsed.exists)
111    }
112
113    /// Flush all cached scripts
114    pub async fn flush(&self) -> Result<u64> {
115        let response = self.client.send_command("script.flush", json!({})).await?;
116        let parsed: ScriptFlushResponse = serde_json::from_value(response)?;
117        Ok(parsed.cleared)
118    }
119
120    /// Kill the currently running script (if any)
121    pub async fn kill(&self) -> Result<bool> {
122        let response = self.client.send_command("script.kill", json!({})).await?;
123        let parsed: ScriptKillResponse = serde_json::from_value(response)?;
124        Ok(parsed.terminated)
125    }
126
127    fn parse_eval_response<T>(&self, response: Value) -> Result<ScriptEvalResponse<T>>
128    where
129        T: DeserializeOwned,
130    {
131        let sha1 = response["sha1"].as_str().unwrap_or_default().to_string();
132        let result_value = response.get("result").cloned().unwrap_or(Value::Null);
133        let result: T = serde_json::from_value(result_value)?;
134        Ok(ScriptEvalResponse { result, sha1 })
135    }
136}