Skip to main content

synap_sdk/
bitmap.rs

1//! Bitmap operations (SETBIT/GETBIT/BITCOUNT/BITPOS/BITOP)
2
3use crate::client::SynapClient;
4use crate::error::Result;
5use serde::{Deserialize, Serialize};
6use serde_json::json;
7
8/// Bitmap operation types
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
10pub enum BitmapOperation {
11    And,
12    Or,
13    Xor,
14    Not,
15}
16
17impl BitmapOperation {
18    fn as_str(&self) -> &'static str {
19        match self {
20            BitmapOperation::And => "AND",
21            BitmapOperation::Or => "OR",
22            BitmapOperation::Xor => "XOR",
23            BitmapOperation::Not => "NOT",
24        }
25    }
26}
27
28/// Bitmap statistics
29#[derive(Debug, Clone, Serialize, Deserialize, Default)]
30pub struct BitmapStats {
31    pub total_bitmaps: usize,
32    pub total_bits: usize,
33    pub setbit_count: usize,
34    pub getbit_count: usize,
35    pub bitcount_count: usize,
36    pub bitop_count: usize,
37    pub bitpos_count: usize,
38    pub bitfield_count: usize,
39}
40
41#[derive(Clone)]
42pub struct BitmapManager {
43    client: SynapClient,
44}
45
46impl BitmapManager {
47    pub(crate) fn new(client: SynapClient) -> Self {
48        Self { client }
49    }
50
51    /// Set bit at offset to value (SETBIT)
52    ///
53    /// # Arguments
54    ///
55    /// * `key` - Bitmap key
56    /// * `offset` - Bit offset (0-based)
57    /// * `value` - Bit value (0 or 1)
58    ///
59    /// # Returns
60    ///
61    /// Previous bit value (0 or 1)
62    pub async fn setbit(&self, key: &str, offset: usize, value: u8) -> Result<u8> {
63        if value > 1 {
64            return Err(crate::error::SynapError::ServerError(
65                "Bitmap value must be 0 or 1".to_string(),
66            ));
67        }
68
69        let payload = json!({
70            "key": key,
71            "offset": offset,
72            "value": value,
73        });
74
75        let response = self.client.send_command("bitmap.setbit", payload).await?;
76        Ok(response["old_value"].as_u64().unwrap_or(0) as u8)
77    }
78
79    /// Get bit at offset (GETBIT)
80    ///
81    /// # Arguments
82    ///
83    /// * `key` - Bitmap key
84    /// * `offset` - Bit offset (0-based)
85    ///
86    /// # Returns
87    ///
88    /// Bit value (0 or 1)
89    pub async fn getbit(&self, key: &str, offset: usize) -> Result<u8> {
90        let payload = json!({
91            "key": key,
92            "offset": offset,
93        });
94
95        let response = self.client.send_command("bitmap.getbit", payload).await?;
96        Ok(response["value"].as_u64().unwrap_or(0) as u8)
97    }
98
99    /// Count set bits in bitmap (BITCOUNT)
100    ///
101    /// # Arguments
102    ///
103    /// * `key` - Bitmap key
104    /// * `start` - Optional start offset (inclusive)
105    /// * `end` - Optional end offset (inclusive)
106    ///
107    /// # Returns
108    ///
109    /// Number of set bits
110    pub async fn bitcount(
111        &self,
112        key: &str,
113        start: Option<usize>,
114        end: Option<usize>,
115    ) -> Result<usize> {
116        let mut payload = json!({"key": key});
117        if let Some(start_val) = start {
118            payload["start"] = json!(start_val);
119        }
120        if let Some(end_val) = end {
121            payload["end"] = json!(end_val);
122        }
123
124        let response = self.client.send_command("bitmap.bitcount", payload).await?;
125        Ok(response["count"].as_u64().unwrap_or(0) as usize)
126    }
127
128    /// Find first bit set to value (BITPOS)
129    ///
130    /// # Arguments
131    ///
132    /// * `key` - Bitmap key
133    /// * `value` - Bit value to search for (0 or 1)
134    /// * `start` - Optional start offset (inclusive)
135    /// * `end` - Optional end offset (inclusive)
136    ///
137    /// # Returns
138    ///
139    /// Position of first matching bit, or None if not found
140    pub async fn bitpos(
141        &self,
142        key: &str,
143        value: u8,
144        start: Option<usize>,
145        end: Option<usize>,
146    ) -> Result<Option<usize>> {
147        if value > 1 {
148            return Err(crate::error::SynapError::ServerError(
149                "Bitmap value must be 0 or 1".to_string(),
150            ));
151        }
152
153        let mut payload = json!({
154            "key": key,
155            "value": value,
156        });
157        if let Some(start_val) = start {
158            payload["start"] = json!(start_val);
159        }
160        if let Some(end_val) = end {
161            payload["end"] = json!(end_val);
162        }
163
164        let response = self.client.send_command("bitmap.bitpos", payload).await?;
165
166        if let Some(pos) = response["position"].as_u64() {
167            Ok(Some(pos as usize))
168        } else {
169            Ok(None)
170        }
171    }
172
173    /// Perform bitwise operation on multiple bitmaps (BITOP)
174    ///
175    /// # Arguments
176    ///
177    /// * `operation` - Bitwise operation (AND, OR, XOR, NOT)
178    /// * `destination` - Destination key for result
179    /// * `source_keys` - Source bitmap keys (NOT requires exactly 1 source)
180    ///
181    /// # Returns
182    ///
183    /// Length of resulting bitmap in bits
184    ///
185    /// # Errors
186    ///
187    /// Returns an error if:
188    /// - NOT operation is used with more than one source key
189    /// - No source keys provided
190    pub async fn bitop<S>(
191        &self,
192        operation: BitmapOperation,
193        destination: &str,
194        source_keys: &[S],
195    ) -> Result<usize>
196    where
197        S: AsRef<str>,
198    {
199        if operation == BitmapOperation::Not && source_keys.len() != 1 {
200            return Err(crate::error::SynapError::ServerError(
201                "NOT operation requires exactly one source key".to_string(),
202            ));
203        }
204
205        if source_keys.is_empty() {
206            return Err(crate::error::SynapError::ServerError(
207                "BITOP requires at least one source key".to_string(),
208            ));
209        }
210
211        let payload = json!({
212            "destination": destination,
213            "operation": operation.as_str(),
214            "source_keys": source_keys.iter().map(|s| s.as_ref()).collect::<Vec<_>>(),
215        });
216
217        let response = self.client.send_command("bitmap.bitop", payload).await?;
218        Ok(response["length"].as_u64().unwrap_or(0) as usize)
219    }
220
221    /// Execute bitfield operations (BITFIELD)
222    ///
223    /// # Arguments
224    ///
225    /// * `key` - Bitmap key
226    /// * `operations` - Vector of bitfield operations
227    ///
228    /// # Returns
229    ///
230    /// Vector of result values (one per operation)
231    ///
232    /// # Example
233    ///
234    /// ```rust,no_run
235    /// use synap_sdk::bitmap::BitmapManager;
236    /// use serde_json::json;
237    ///
238    /// # async fn example(bitmap: BitmapManager) -> Result<(), Box<dyn std::error::Error>> {
239    /// let operations = vec![
240    ///     json!({
241    ///         "operation": "SET",
242    ///         "offset": 0,
243    ///         "width": 8,
244    ///         "signed": false,
245    ///         "value": 42
246    ///     }),
247    ///     json!({
248    ///         "operation": "GET",
249    ///         "offset": 0,
250    ///         "width": 8,
251    ///         "signed": false
252    ///     }),
253    ///     json!({
254    ///         "operation": "INCRBY",
255    ///         "offset": 0,
256    ///         "width": 8,
257    ///         "signed": false,
258    ///         "increment": 10,
259    ///         "overflow": "WRAP"
260    ///     }),
261    /// ];
262    ///
263    /// let results = bitmap.bitfield("mybitmap", &operations).await?;
264    /// # Ok(())
265    /// # }
266    /// ```
267    pub async fn bitfield(&self, key: &str, operations: &[serde_json::Value]) -> Result<Vec<i64>> {
268        let payload = json!({
269            "key": key,
270            "operations": operations,
271        });
272
273        let response = self.client.send_command("bitmap.bitfield", payload).await?;
274        let results = response["results"]
275            .as_array()
276            .ok_or_else(|| {
277                crate::error::SynapError::ServerError(
278                    "Invalid response format for bitfield".to_string(),
279                )
280            })?
281            .iter()
282            .map(|v| v.as_i64().unwrap_or(0))
283            .collect();
284
285        Ok(results)
286    }
287
288    /// Retrieve bitmap statistics
289    pub async fn stats(&self) -> Result<BitmapStats> {
290        let response = self.client.send_command("bitmap.stats", json!({})).await?;
291        let stats: BitmapStats = serde_json::from_value(response)?;
292        Ok(stats)
293    }
294}