use async_trait::async_trait;
use rabia_core::smr::StateMachine;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub enum CounterCommand {
Increment(i64),
Decrement(i64),
Set(i64),
Get,
Reset,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct CounterResponse {
pub value: i64,
pub success: bool,
pub message: Option<String>,
}
impl CounterResponse {
pub fn success(value: i64) -> Self {
Self {
value,
success: true,
message: None,
}
}
pub fn error(value: i64, message: String) -> Self {
Self {
value,
success: false,
message: Some(message),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct CounterState {
pub value: i64,
pub operation_count: u64,
}
#[derive(Debug, Clone)]
pub struct CounterSMR {
state: CounterState,
}
impl CounterSMR {
pub fn new() -> Self {
Self {
state: CounterState::default(),
}
}
pub fn with_value(initial_value: i64) -> Self {
Self {
state: CounterState {
value: initial_value,
operation_count: 0,
},
}
}
pub fn value(&self) -> i64 {
self.state.value
}
pub fn operation_count(&self) -> u64 {
self.state.operation_count
}
}
impl Default for CounterSMR {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl StateMachine for CounterSMR {
type Command = CounterCommand;
type Response = CounterResponse;
type State = CounterState;
async fn apply_command(&mut self, command: Self::Command) -> Self::Response {
self.state.operation_count += 1;
match command {
CounterCommand::Increment(value) => {
match self.state.value.checked_add(value) {
Some(new_value) => {
self.state.value = new_value;
CounterResponse::success(self.state.value)
}
None => CounterResponse::error(
self.state.value,
"Overflow: cannot increment counter".to_string(),
),
}
}
CounterCommand::Decrement(value) => {
match self.state.value.checked_sub(value) {
Some(new_value) => {
self.state.value = new_value;
CounterResponse::success(self.state.value)
}
None => CounterResponse::error(
self.state.value,
"Underflow: cannot decrement counter".to_string(),
),
}
}
CounterCommand::Set(value) => {
self.state.value = value;
CounterResponse::success(self.state.value)
}
CounterCommand::Get => {
CounterResponse::success(self.state.value)
}
CounterCommand::Reset => {
self.state.value = 0;
CounterResponse::success(self.state.value)
}
}
}
fn get_state(&self) -> Self::State {
self.state.clone()
}
fn set_state(&mut self, state: Self::State) {
self.state = state;
}
fn serialize_state(&self) -> Vec<u8> {
bincode::serialize(&self.state).unwrap_or_default()
}
fn deserialize_state(&mut self, data: &[u8]) -> Result<(), Box<dyn std::error::Error>> {
self.state = bincode::deserialize(data)?;
Ok(())
}
async fn apply_commands(&mut self, commands: Vec<Self::Command>) -> Vec<Self::Response> {
let mut responses = Vec::with_capacity(commands.len());
for command in commands {
responses.push(self.apply_command(command).await);
}
responses
}
fn is_deterministic(&self) -> bool {
true
}
}
#[cfg(test)]
mod tests {
use super::*;
#[tokio::test]
async fn test_counter_basic_operations() {
let mut counter = CounterSMR::new();
let response = counter.apply_command(CounterCommand::Increment(5)).await;
assert!(response.success);
assert_eq!(response.value, 5);
assert_eq!(counter.value(), 5);
let response = counter.apply_command(CounterCommand::Decrement(2)).await;
assert!(response.success);
assert_eq!(response.value, 3);
assert_eq!(counter.value(), 3);
let response = counter.apply_command(CounterCommand::Set(10)).await;
assert!(response.success);
assert_eq!(response.value, 10);
assert_eq!(counter.value(), 10);
let response = counter.apply_command(CounterCommand::Get).await;
assert!(response.success);
assert_eq!(response.value, 10);
let response = counter.apply_command(CounterCommand::Reset).await;
assert!(response.success);
assert_eq!(response.value, 0);
assert_eq!(counter.value(), 0);
}
#[tokio::test]
async fn test_counter_overflow_underflow() {
let mut counter = CounterSMR::with_value(i64::MAX);
let response = counter.apply_command(CounterCommand::Increment(1)).await;
assert!(!response.success);
assert_eq!(response.value, i64::MAX);
assert!(response.message.as_ref().unwrap().contains("Overflow"));
counter = CounterSMR::with_value(i64::MIN);
let response = counter.apply_command(CounterCommand::Decrement(1)).await;
assert!(!response.success);
assert_eq!(response.value, i64::MIN);
assert!(response.message.as_ref().unwrap().contains("Underflow"));
}
#[tokio::test]
async fn test_counter_state_serialization() {
let mut counter = CounterSMR::new();
counter.apply_command(CounterCommand::Increment(42)).await;
counter.apply_command(CounterCommand::Decrement(10)).await;
let serialized = counter.serialize_state();
assert!(!serialized.is_empty());
let mut new_counter = CounterSMR::new();
new_counter.deserialize_state(&serialized).unwrap();
assert_eq!(new_counter.value(), 32);
assert_eq!(new_counter.operation_count(), 2);
assert_eq!(new_counter.get_state(), counter.get_state());
}
#[tokio::test]
async fn test_counter_multiple_commands() {
let mut counter = CounterSMR::new();
let commands = vec![
CounterCommand::Increment(10),
CounterCommand::Increment(5),
CounterCommand::Decrement(3),
CounterCommand::Set(100),
CounterCommand::Get,
];
let responses = counter.apply_commands(commands).await;
assert_eq!(responses.len(), 5);
assert!(responses.iter().all(|r| r.success));
assert_eq!(counter.value(), 100);
assert_eq!(counter.operation_count(), 5);
assert_eq!(responses[0].value, 10); assert_eq!(responses[1].value, 15); assert_eq!(responses[2].value, 12); assert_eq!(responses[3].value, 100); assert_eq!(responses[4].value, 100); }
#[test]
fn test_counter_deterministic() {
let counter = CounterSMR::new();
assert!(counter.is_deterministic());
}
}