use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum KVOperation {
Set { key: String, value: String },
Get { key: String },
Delete { key: String },
Exists { key: String },
}
impl KVOperation {
pub fn key(&self) -> &str {
match self {
KVOperation::Set { key, .. } => key,
KVOperation::Get { key } => key,
KVOperation::Delete { key } => key,
KVOperation::Exists { key } => key,
}
}
pub fn operation_type(&self) -> &'static str {
match self {
KVOperation::Set { .. } => "SET",
KVOperation::Get { .. } => "GET",
KVOperation::Delete { .. } => "DELETE",
KVOperation::Exists { .. } => "EXISTS",
}
}
pub fn is_write_operation(&self) -> bool {
matches!(self, KVOperation::Set { .. } | KVOperation::Delete { .. })
}
pub fn is_read_operation(&self) -> bool {
!self.is_write_operation()
}
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum KVResult {
Success,
NotFound,
Error(String),
}
impl KVResult {
pub fn is_success(&self) -> bool {
matches!(self, KVResult::Success)
}
pub fn is_not_found(&self) -> bool {
matches!(self, KVResult::NotFound)
}
pub fn is_error(&self) -> bool {
matches!(self, KVResult::Error(_))
}
pub fn error_message(&self) -> Option<&str> {
match self {
KVResult::Error(msg) => Some(msg),
_ => None,
}
}
}
impl From<StoreError> for KVResult {
fn from(error: StoreError) -> Self {
KVResult::Error(error.to_string())
}
}
#[derive(Error, Debug, Clone, PartialEq)]
pub enum StoreError {
#[error("Invalid key: {0}")]
InvalidKey(String),
#[error("Value too large")]
ValueTooLarge,
#[error("Store is full")]
StoreFull,
#[error("Serialization error: {0}")]
SerializationError(String),
#[error("Invalid snapshot")]
InvalidSnapshot,
#[error("IO error: {0}")]
IoError(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Consensus error: {0}")]
ConsensusError(String),
#[error("Operation timed out")]
Timeout,
#[error("Store is shutting down")]
ShuttingDown,
#[error("Internal error: {0}")]
Internal(String),
}
impl StoreError {
pub fn is_recoverable(&self) -> bool {
matches!(
self,
StoreError::NetworkError(_) | StoreError::Timeout | StoreError::ConsensusError(_)
)
}
pub fn is_client_error(&self) -> bool {
matches!(
self,
StoreError::InvalidKey(_) | StoreError::ValueTooLarge | StoreError::StoreFull
)
}
pub fn is_server_error(&self) -> bool {
matches!(
self,
StoreError::IoError(_) | StoreError::Internal(_) | StoreError::ShuttingDown
)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct OperationBatch {
pub operations: Vec<KVOperation>,
pub batch_id: String,
pub created_at: u64,
}
impl OperationBatch {
pub fn new(operations: Vec<KVOperation>) -> Self {
Self {
operations,
batch_id: uuid::Uuid::new_v4().to_string(),
created_at: std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_millis() as u64,
}
}
pub fn size(&self) -> usize {
self.operations.len()
}
pub fn has_write_operations(&self) -> bool {
self.operations.iter().any(|op| op.is_write_operation())
}
pub fn is_read_only(&self) -> bool {
self.operations.iter().all(|op| op.is_read_operation())
}
pub fn affected_keys(&self) -> Vec<&str> {
self.operations.iter().map(|op| op.key()).collect()
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BatchResult {
pub batch_id: String,
pub results: Vec<KVResult>,
pub success_count: usize,
pub failure_count: usize,
pub execution_time_ms: u64,
}
impl BatchResult {
pub fn new(batch_id: String, results: Vec<KVResult>, execution_time_ms: u64) -> Self {
let success_count = results.iter().filter(|r| r.is_success()).count();
let failure_count = results.len() - success_count;
Self {
batch_id,
results,
success_count,
failure_count,
execution_time_ms,
}
}
pub fn all_succeeded(&self) -> bool {
self.failure_count == 0
}
pub fn has_failures(&self) -> bool {
self.failure_count > 0
}
pub fn success_rate(&self) -> f64 {
if self.results.is_empty() {
0.0
} else {
(self.success_count as f64 / self.results.len() as f64) * 100.0
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_kv_operation_properties() {
let set_op = KVOperation::Set {
key: "test_key".to_string(),
value: "test_value".to_string(),
};
let get_op = KVOperation::Get {
key: "test_key".to_string(),
};
let delete_op = KVOperation::Delete {
key: "test_key".to_string(),
};
let exists_op = KVOperation::Exists {
key: "test_key".to_string(),
};
assert_eq!(set_op.key(), "test_key");
assert_eq!(get_op.key(), "test_key");
assert_eq!(delete_op.key(), "test_key");
assert_eq!(exists_op.key(), "test_key");
assert_eq!(set_op.operation_type(), "SET");
assert_eq!(get_op.operation_type(), "GET");
assert_eq!(delete_op.operation_type(), "DELETE");
assert_eq!(exists_op.operation_type(), "EXISTS");
assert!(set_op.is_write_operation());
assert!(!get_op.is_write_operation());
assert!(delete_op.is_write_operation());
assert!(!exists_op.is_write_operation());
assert!(!set_op.is_read_operation());
assert!(get_op.is_read_operation());
assert!(!delete_op.is_read_operation());
assert!(exists_op.is_read_operation());
}
#[test]
fn test_kv_result_properties() {
let success = KVResult::Success;
let not_found = KVResult::NotFound;
let error = KVResult::Error("Test error".to_string());
assert!(success.is_success());
assert!(!success.is_not_found());
assert!(!success.is_error());
assert!(!not_found.is_success());
assert!(not_found.is_not_found());
assert!(!not_found.is_error());
assert!(!error.is_success());
assert!(!error.is_not_found());
assert!(error.is_error());
assert_eq!(error.error_message().unwrap(), "Test error");
}
#[test]
fn test_store_error_classification() {
let client_error = StoreError::InvalidKey("test".to_string());
let server_error = StoreError::Internal("test".to_string());
let recoverable_error = StoreError::NetworkError("test".to_string());
assert!(client_error.is_client_error());
assert!(!client_error.is_server_error());
assert!(!client_error.is_recoverable());
assert!(!server_error.is_client_error());
assert!(server_error.is_server_error());
assert!(!server_error.is_recoverable());
assert!(!recoverable_error.is_client_error());
assert!(!recoverable_error.is_server_error());
assert!(recoverable_error.is_recoverable());
}
#[test]
fn test_operation_batch() {
let operations = vec![
KVOperation::Set {
key: "key1".to_string(),
value: "value1".to_string(),
},
KVOperation::Get {
key: "key2".to_string(),
},
KVOperation::Delete {
key: "key3".to_string(),
},
];
let batch = OperationBatch::new(operations);
assert_eq!(batch.size(), 3);
assert!(batch.has_write_operations());
assert!(!batch.is_read_only());
let affected_keys = batch.affected_keys();
assert_eq!(affected_keys, vec!["key1", "key2", "key3"]);
}
#[test]
fn test_batch_result() {
let results = vec![
KVResult::Success,
KVResult::NotFound,
KVResult::Error("test".to_string()),
];
let batch_result = BatchResult::new("test_batch".to_string(), results, 100);
assert_eq!(batch_result.success_count, 1);
assert_eq!(batch_result.failure_count, 2);
assert!(!batch_result.all_succeeded());
assert!(batch_result.has_failures());
assert!((batch_result.success_rate() - 33.333333333333336).abs() < 0.0001);
}
}