Skip to main content

synap_sdk/
hash.rs

1//! Hash data structure operations
2use crate::client::SynapClient;
3use crate::error::Result;
4use serde_json::json;
5use std::collections::HashMap;
6
7/// Hash data structure interface (Redis-compatible)
8///
9/// Hash is a field-value map, ideal for storing objects.
10#[derive(Clone)]
11pub struct HashManager {
12    client: SynapClient,
13}
14
15impl HashManager {
16    /// Create a new Hash manager interface
17    pub(crate) fn new(client: SynapClient) -> Self {
18        Self { client }
19    }
20
21    /// Set field in hash
22    pub async fn set<K, F, V>(&self, key: K, field: F, value: V) -> Result<bool>
23    where
24        K: AsRef<str>,
25        F: AsRef<str>,
26        V: ToString,
27    {
28        let payload = json!({
29            "key": key.as_ref(),
30            "field": field.as_ref(),
31            "value": value.to_string(),
32        });
33
34        let response = self.client.send_command("hash.set", payload).await?;
35        Ok(response
36            .get("success")
37            .and_then(|v| v.as_bool())
38            .unwrap_or(false))
39    }
40
41    /// Get field from hash
42    pub async fn get<K, F>(&self, key: K, field: F) -> Result<Option<String>>
43    where
44        K: AsRef<str>,
45        F: AsRef<str>,
46    {
47        let payload = json!({
48            "key": key.as_ref(),
49            "field": field.as_ref(),
50        });
51
52        let response = self.client.send_command("hash.get", payload).await?;
53        Ok(response
54            .get("value")
55            .and_then(|v| v.as_str())
56            .map(String::from))
57    }
58
59    /// Get all fields and values from hash
60    pub async fn get_all<K>(&self, key: K) -> Result<HashMap<String, String>>
61    where
62        K: AsRef<str>,
63    {
64        let payload = json!({"key": key.as_ref()});
65        let response = self.client.send_command("hash.getall", payload).await?;
66
67        let fields = response
68            .get("fields")
69            .and_then(|v| serde_json::from_value(v.clone()).ok())
70            .unwrap_or_default();
71
72        Ok(fields)
73    }
74
75    /// Delete field from hash
76    pub async fn del<K, F>(&self, key: K, field: F) -> Result<i64>
77    where
78        K: AsRef<str>,
79        F: AsRef<str>,
80    {
81        let payload = json!({
82            "key": key.as_ref(),
83            "field": field.as_ref(),
84            "fields": [field.as_ref()],
85        });
86
87        let response = self.client.send_command("hash.del", payload).await?;
88        Ok(response
89            .get("deleted")
90            .and_then(|v| v.as_i64())
91            .unwrap_or(0))
92    }
93
94    /// Check if field exists in hash
95    pub async fn exists<K, F>(&self, key: K, field: F) -> Result<bool>
96    where
97        K: AsRef<str>,
98        F: AsRef<str>,
99    {
100        let payload = json!({
101            "key": key.as_ref(),
102            "field": field.as_ref(),
103        });
104
105        let response = self.client.send_command("hash.exists", payload).await?;
106        Ok(response
107            .get("exists")
108            .and_then(|v| v.as_bool())
109            .unwrap_or(false))
110    }
111
112    /// Get all field names in hash
113    pub async fn keys<K>(&self, key: K) -> Result<Vec<String>>
114    where
115        K: AsRef<str>,
116    {
117        let payload = json!({"key": key.as_ref()});
118        let response = self.client.send_command("hash.keys", payload).await?;
119
120        let keys = response
121            .get("fields")
122            .and_then(|v| serde_json::from_value(v.clone()).ok())
123            .unwrap_or_default();
124
125        Ok(keys)
126    }
127
128    /// Get all values in hash
129    pub async fn values<K>(&self, key: K) -> Result<Vec<String>>
130    where
131        K: AsRef<str>,
132    {
133        let payload = json!({"key": key.as_ref()});
134        let response = self.client.send_command("hash.values", payload).await?;
135
136        let values = response
137            .get("values")
138            .and_then(|v| serde_json::from_value(v.clone()).ok())
139            .unwrap_or_default();
140
141        Ok(values)
142    }
143
144    /// Get number of fields in hash
145    pub async fn len<K>(&self, key: K) -> Result<usize>
146    where
147        K: AsRef<str>,
148    {
149        let payload = json!({"key": key.as_ref()});
150        let response = self.client.send_command("hash.len", payload).await?;
151        Ok(response.get("length").and_then(|v| v.as_u64()).unwrap_or(0) as usize)
152    }
153
154    /// Set multiple fields in hash
155    ///
156    /// Supports both HashMap format (backward compatible) and array format (Redis-compatible)
157    ///
158    /// # Example
159    /// ```no_run
160    /// # use synap_sdk::SynapClient;
161    /// # use std::collections::HashMap;
162    /// # async fn example(client: &SynapClient) -> synap_sdk::Result<()> {
163    /// // HashMap format (backward compatible)
164    /// let mut fields = HashMap::new();
165    /// fields.insert("name".to_string(), "Alice".to_string());
166    /// fields.insert("age".to_string(), "30".to_string());
167    /// client.hash().mset("user:1", fields).await?;
168    /// # Ok(())
169    /// # }
170    /// ```
171    pub async fn mset<K>(&self, key: K, fields: HashMap<String, String>) -> Result<bool>
172    where
173        K: AsRef<str>,
174    {
175        // Convert HashMap to object format (backward compatible)
176        let payload = json!({
177            "key": key.as_ref(),
178            "fields": fields,
179        });
180
181        let response = self.client.send_command("hash.mset", payload).await?;
182        Ok(response
183            .get("success")
184            .and_then(|v| v.as_bool())
185            .unwrap_or(false))
186    }
187
188    /// Set multiple fields in hash using array format (Redis-compatible)
189    ///
190    /// # Example
191    /// ```no_run
192    /// # use synap_sdk::SynapClient;
193    /// # async fn example(client: &SynapClient) -> synap_sdk::Result<()> {
194    /// let fields = vec![
195    ///     ("name".to_string(), "Alice".to_string()),
196    ///     ("age".to_string(), "30".to_string()),
197    /// ];
198    /// client.hash().mset_array("user:1", fields).await?;
199    /// # Ok(())
200    /// # }
201    /// ```
202    pub async fn mset_array<K>(&self, key: K, fields: Vec<(String, String)>) -> Result<bool>
203    where
204        K: AsRef<str>,
205    {
206        // Convert to array format: [{"field": "...", "value": "..."}, ...]
207        let fields_array: Vec<_> = fields
208            .into_iter()
209            .map(|(field, value)| {
210                json!({
211                    "field": field,
212                    "value": value,
213                })
214            })
215            .collect();
216
217        let payload = json!({
218            "key": key.as_ref(),
219            "fields": fields_array,
220        });
221
222        let response = self.client.send_command("hash.mset", payload).await?;
223        Ok(response
224            .get("success")
225            .and_then(|v| v.as_bool())
226            .unwrap_or(false))
227    }
228
229    /// Get multiple fields from hash
230    pub async fn mget<K>(
231        &self,
232        key: K,
233        fields: Vec<String>,
234    ) -> Result<HashMap<String, Option<String>>>
235    where
236        K: AsRef<str>,
237    {
238        let payload = json!({
239            "key": key.as_ref(),
240            "fields": fields,
241        });
242
243        let response = self.client.send_command("hash.mget", payload).await?;
244
245        let values = response
246            .get("values")
247            .and_then(|v| serde_json::from_value(v.clone()).ok())
248            .unwrap_or_default();
249
250        Ok(values)
251    }
252
253    /// Increment field value by integer
254    pub async fn incr_by<K, F>(&self, key: K, field: F, increment: i64) -> Result<i64>
255    where
256        K: AsRef<str>,
257        F: AsRef<str>,
258    {
259        let payload = json!({
260            "key": key.as_ref(),
261            "field": field.as_ref(),
262            "increment": increment,
263        });
264
265        let response = self.client.send_command("hash.incrby", payload).await?;
266        Ok(response.get("value").and_then(|v| v.as_i64()).unwrap_or(0))
267    }
268
269    /// Increment field value by float
270    pub async fn incr_by_float<K, F>(&self, key: K, field: F, increment: f64) -> Result<f64>
271    where
272        K: AsRef<str>,
273        F: AsRef<str>,
274    {
275        let payload = json!({
276            "key": key.as_ref(),
277            "field": field.as_ref(),
278            "increment": increment,
279        });
280
281        let response = self
282            .client
283            .send_command("hash.incrbyfloat", payload)
284            .await?;
285        Ok(response
286            .get("value")
287            .and_then(|v| v.as_f64())
288            .unwrap_or(0.0))
289    }
290
291    /// Set field only if it doesn't exist
292    pub async fn set_nx<K, F, V>(&self, key: K, field: F, value: V) -> Result<bool>
293    where
294        K: AsRef<str>,
295        F: AsRef<str>,
296        V: ToString,
297    {
298        let payload = json!({
299            "key": key.as_ref(),
300            "field": field.as_ref(),
301            "value": value.to_string(),
302        });
303
304        let response = self.client.send_command("hash.setnx", payload).await?;
305        Ok(response
306            .get("created")
307            .and_then(|v| v.as_bool())
308            .unwrap_or(false))
309    }
310}