Skip to main content

rabia_kvstore_example/
operations.rs

1//! # KVStore Operations and Error Types
2//!
3//! Defines the operations that can be performed on the KVStore and their error types.
4
5use serde::{Deserialize, Serialize};
6use thiserror::Error;
7
8/// Operations that can be performed on the KVStore
9#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10pub enum KVOperation {
11    /// Set a key-value pair
12    Set { key: String, value: String },
13    /// Get a value by key
14    Get { key: String },
15    /// Delete a key
16    Delete { key: String },
17    /// Check if a key exists
18    Exists { key: String },
19}
20
21impl KVOperation {
22    /// Get the key being operated on
23    pub fn key(&self) -> &str {
24        match self {
25            KVOperation::Set { key, .. } => key,
26            KVOperation::Get { key } => key,
27            KVOperation::Delete { key } => key,
28            KVOperation::Exists { key } => key,
29        }
30    }
31
32    /// Get the operation type as a string
33    pub fn operation_type(&self) -> &'static str {
34        match self {
35            KVOperation::Set { .. } => "SET",
36            KVOperation::Get { .. } => "GET",
37            KVOperation::Delete { .. } => "DELETE",
38            KVOperation::Exists { .. } => "EXISTS",
39        }
40    }
41
42    /// Check if this operation modifies the store
43    pub fn is_write_operation(&self) -> bool {
44        matches!(self, KVOperation::Set { .. } | KVOperation::Delete { .. })
45    }
46
47    /// Check if this operation only reads from the store
48    pub fn is_read_operation(&self) -> bool {
49        !self.is_write_operation()
50    }
51}
52
53/// Result of a KVStore operation
54#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
55pub enum KVResult {
56    /// Operation completed successfully
57    Success,
58    /// Key was not found
59    NotFound,
60    /// Operation failed with an error
61    Error(String),
62}
63
64impl KVResult {
65    /// Check if the result indicates success
66    pub fn is_success(&self) -> bool {
67        matches!(self, KVResult::Success)
68    }
69
70    /// Check if the result indicates a not found error
71    pub fn is_not_found(&self) -> bool {
72        matches!(self, KVResult::NotFound)
73    }
74
75    /// Check if the result indicates an error
76    pub fn is_error(&self) -> bool {
77        matches!(self, KVResult::Error(_))
78    }
79
80    /// Get the error message if this is an error result
81    pub fn error_message(&self) -> Option<&str> {
82        match self {
83            KVResult::Error(msg) => Some(msg),
84            _ => None,
85        }
86    }
87}
88
89impl From<StoreError> for KVResult {
90    fn from(error: StoreError) -> Self {
91        KVResult::Error(error.to_string())
92    }
93}
94
95/// Errors that can occur during KVStore operations
96#[derive(Error, Debug, Clone, PartialEq)]
97pub enum StoreError {
98    /// Invalid key provided
99    #[error("Invalid key: {0}")]
100    InvalidKey(String),
101
102    /// Value is too large to store
103    #[error("Value too large")]
104    ValueTooLarge,
105
106    /// Store has reached maximum capacity
107    #[error("Store is full")]
108    StoreFull,
109
110    /// Serialization error
111    #[error("Serialization error: {0}")]
112    SerializationError(String),
113
114    /// Invalid snapshot
115    #[error("Invalid snapshot")]
116    InvalidSnapshot,
117
118    /// IO error
119    #[error("IO error: {0}")]
120    IoError(String),
121
122    /// Network error
123    #[error("Network error: {0}")]
124    NetworkError(String),
125
126    /// Consensus error
127    #[error("Consensus error: {0}")]
128    ConsensusError(String),
129
130    /// Timeout error
131    #[error("Operation timed out")]
132    Timeout,
133
134    /// Store is shutting down
135    #[error("Store is shutting down")]
136    ShuttingDown,
137
138    /// Internal error
139    #[error("Internal error: {0}")]
140    Internal(String),
141}
142
143impl StoreError {
144    /// Check if this error is recoverable
145    pub fn is_recoverable(&self) -> bool {
146        matches!(
147            self,
148            StoreError::NetworkError(_) | StoreError::Timeout | StoreError::ConsensusError(_)
149        )
150    }
151
152    /// Check if this error indicates a client error (4xx equivalent)
153    pub fn is_client_error(&self) -> bool {
154        matches!(
155            self,
156            StoreError::InvalidKey(_) | StoreError::ValueTooLarge | StoreError::StoreFull
157        )
158    }
159
160    /// Check if this error indicates a server error (5xx equivalent)
161    pub fn is_server_error(&self) -> bool {
162        matches!(
163            self,
164            StoreError::IoError(_) | StoreError::Internal(_) | StoreError::ShuttingDown
165        )
166    }
167}
168
169/// Batch of operations to be executed atomically
170#[derive(Debug, Clone, Serialize, Deserialize)]
171pub struct OperationBatch {
172    /// The operations in this batch
173    pub operations: Vec<KVOperation>,
174    /// Unique identifier for this batch
175    pub batch_id: String,
176    /// Timestamp when the batch was created
177    pub created_at: u64,
178}
179
180impl OperationBatch {
181    /// Create a new operation batch
182    pub fn new(operations: Vec<KVOperation>) -> Self {
183        Self {
184            operations,
185            batch_id: uuid::Uuid::new_v4().to_string(),
186            created_at: std::time::SystemTime::now()
187                .duration_since(std::time::UNIX_EPOCH)
188                .unwrap()
189                .as_millis() as u64,
190        }
191    }
192
193    /// Get the number of operations in this batch
194    pub fn size(&self) -> usize {
195        self.operations.len()
196    }
197
198    /// Check if this batch contains any write operations
199    pub fn has_write_operations(&self) -> bool {
200        self.operations.iter().any(|op| op.is_write_operation())
201    }
202
203    /// Check if this batch contains only read operations
204    pub fn is_read_only(&self) -> bool {
205        self.operations.iter().all(|op| op.is_read_operation())
206    }
207
208    /// Get all keys that will be affected by this batch
209    pub fn affected_keys(&self) -> Vec<&str> {
210        self.operations.iter().map(|op| op.key()).collect()
211    }
212}
213
214/// Result of executing an operation batch
215#[derive(Debug, Clone, Serialize, Deserialize)]
216pub struct BatchResult {
217    /// The batch that was executed
218    pub batch_id: String,
219    /// Results for each operation in the batch
220    pub results: Vec<KVResult>,
221    /// Number of successful operations
222    pub success_count: usize,
223    /// Number of failed operations
224    pub failure_count: usize,
225    /// Time taken to execute the batch (in milliseconds)
226    pub execution_time_ms: u64,
227}
228
229impl BatchResult {
230    /// Create a new batch result
231    pub fn new(batch_id: String, results: Vec<KVResult>, execution_time_ms: u64) -> Self {
232        let success_count = results.iter().filter(|r| r.is_success()).count();
233        let failure_count = results.len() - success_count;
234
235        Self {
236            batch_id,
237            results,
238            success_count,
239            failure_count,
240            execution_time_ms,
241        }
242    }
243
244    /// Check if all operations in the batch succeeded
245    pub fn all_succeeded(&self) -> bool {
246        self.failure_count == 0
247    }
248
249    /// Check if any operations in the batch failed
250    pub fn has_failures(&self) -> bool {
251        self.failure_count > 0
252    }
253
254    /// Get the success rate as a percentage
255    pub fn success_rate(&self) -> f64 {
256        if self.results.is_empty() {
257            0.0
258        } else {
259            (self.success_count as f64 / self.results.len() as f64) * 100.0
260        }
261    }
262}
263
264#[cfg(test)]
265mod tests {
266    use super::*;
267
268    #[test]
269    fn test_kv_operation_properties() {
270        let set_op = KVOperation::Set {
271            key: "test_key".to_string(),
272            value: "test_value".to_string(),
273        };
274        let get_op = KVOperation::Get {
275            key: "test_key".to_string(),
276        };
277        let delete_op = KVOperation::Delete {
278            key: "test_key".to_string(),
279        };
280        let exists_op = KVOperation::Exists {
281            key: "test_key".to_string(),
282        };
283
284        // Test key extraction
285        assert_eq!(set_op.key(), "test_key");
286        assert_eq!(get_op.key(), "test_key");
287        assert_eq!(delete_op.key(), "test_key");
288        assert_eq!(exists_op.key(), "test_key");
289
290        // Test operation types
291        assert_eq!(set_op.operation_type(), "SET");
292        assert_eq!(get_op.operation_type(), "GET");
293        assert_eq!(delete_op.operation_type(), "DELETE");
294        assert_eq!(exists_op.operation_type(), "EXISTS");
295
296        // Test write/read classification
297        assert!(set_op.is_write_operation());
298        assert!(!get_op.is_write_operation());
299        assert!(delete_op.is_write_operation());
300        assert!(!exists_op.is_write_operation());
301
302        assert!(!set_op.is_read_operation());
303        assert!(get_op.is_read_operation());
304        assert!(!delete_op.is_read_operation());
305        assert!(exists_op.is_read_operation());
306    }
307
308    #[test]
309    fn test_kv_result_properties() {
310        let success = KVResult::Success;
311        let not_found = KVResult::NotFound;
312        let error = KVResult::Error("Test error".to_string());
313
314        assert!(success.is_success());
315        assert!(!success.is_not_found());
316        assert!(!success.is_error());
317
318        assert!(!not_found.is_success());
319        assert!(not_found.is_not_found());
320        assert!(!not_found.is_error());
321
322        assert!(!error.is_success());
323        assert!(!error.is_not_found());
324        assert!(error.is_error());
325        assert_eq!(error.error_message().unwrap(), "Test error");
326    }
327
328    #[test]
329    fn test_store_error_classification() {
330        let client_error = StoreError::InvalidKey("test".to_string());
331        let server_error = StoreError::Internal("test".to_string());
332        let recoverable_error = StoreError::NetworkError("test".to_string());
333
334        assert!(client_error.is_client_error());
335        assert!(!client_error.is_server_error());
336        assert!(!client_error.is_recoverable());
337
338        assert!(!server_error.is_client_error());
339        assert!(server_error.is_server_error());
340        assert!(!server_error.is_recoverable());
341
342        assert!(!recoverable_error.is_client_error());
343        assert!(!recoverable_error.is_server_error());
344        assert!(recoverable_error.is_recoverable());
345    }
346
347    #[test]
348    fn test_operation_batch() {
349        let operations = vec![
350            KVOperation::Set {
351                key: "key1".to_string(),
352                value: "value1".to_string(),
353            },
354            KVOperation::Get {
355                key: "key2".to_string(),
356            },
357            KVOperation::Delete {
358                key: "key3".to_string(),
359            },
360        ];
361
362        let batch = OperationBatch::new(operations);
363
364        assert_eq!(batch.size(), 3);
365        assert!(batch.has_write_operations());
366        assert!(!batch.is_read_only());
367
368        let affected_keys = batch.affected_keys();
369        assert_eq!(affected_keys, vec!["key1", "key2", "key3"]);
370    }
371
372    #[test]
373    fn test_batch_result() {
374        let results = vec![
375            KVResult::Success,
376            KVResult::NotFound,
377            KVResult::Error("test".to_string()),
378        ];
379
380        let batch_result = BatchResult::new("test_batch".to_string(), results, 100);
381
382        assert_eq!(batch_result.success_count, 1);
383        assert_eq!(batch_result.failure_count, 2);
384        assert!(!batch_result.all_succeeded());
385        assert!(batch_result.has_failures());
386        assert!((batch_result.success_rate() - 33.333333333333336).abs() < 0.0001);
387    }
388}