use ops_rs::dry_put;
use ops_rs::prelude::*;
use std::sync::{Arc, Mutex};
struct CounterOp {
counter: Arc<Mutex<Vec<u32>>>,
}
impl CounterOp {
fn new(counter: Arc<Mutex<Vec<u32>>>) -> Self {
Self { counter }
}
}
#[async_trait]
impl Op<u32> for CounterOp {
async fn perform(&self, dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<u32> {
let iteration: usize = dry.get("iteration_counter").unwrap_or(0);
let value = (iteration + 1) as u32;
self.counter.lock().unwrap().push(value);
println!(
"✓ CounterOp performed for iteration {}: value = {}",
iteration, value
);
Ok(value)
}
async fn rollback(&self, dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<()> {
let iteration: usize = dry.get("iteration_counter").unwrap_or(0);
let value = (iteration + 1) as u32;
let mut counter = self.counter.lock().unwrap();
if let Some(pos) = counter.iter().position(|&x| x == value) {
counter.remove(pos);
println!(
"↶ CounterOp rolled back for iteration {}: removed value = {}",
iteration, value
);
}
Ok(())
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("CounterOp").build()
}
}
struct FailingOp {
fail_on_iteration: usize,
}
impl FailingOp {
fn new(fail_on_iteration: usize) -> Self {
Self { fail_on_iteration }
}
}
#[async_trait]
impl Op<u32> for FailingOp {
async fn perform(&self, dry: &mut DryContext, _wet: &mut WetContext) -> OpResult<u32> {
let iteration: usize = dry.get("iteration_counter").unwrap_or(0);
if iteration == self.fail_on_iteration {
println!("✗ FailingOp failed on iteration {}", iteration);
return Err(OpError::ExecutionFailed("Intentional failure".to_string()));
}
println!("✓ FailingOp succeeded on iteration {}", iteration);
Ok(iteration as u32)
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("FailingOp").build()
}
}
struct TestLoop {
counter_var: String,
limit_var: String,
shared_counter: Arc<Mutex<Vec<u32>>>,
}
impl TestLoop {
fn new(shared_counter: Arc<Mutex<Vec<u32>>>) -> Self {
Self {
counter_var: "iteration_counter".to_string(),
limit_var: "max_iterations".to_string(),
shared_counter,
}
}
}
#[async_trait]
impl Op<Vec<u32>> for TestLoop {
async fn perform(&self, dry: &mut DryContext, wet: &mut WetContext) -> OpResult<Vec<u32>> {
let limit = dry.get_required::<usize>(&self.limit_var)?;
let ops: Vec<Arc<dyn Op<u32>>> = vec![
Arc::new(CounterOp::new(self.shared_counter.clone())),
Arc::new(FailingOp::new(2)), ];
let loop_op = ops_rs::LoopOp::new(self.counter_var.clone(), limit, ops);
loop_op.perform(dry, wet).await
}
fn metadata(&self) -> OpMetadata {
OpMetadata::builder("TestLoop").build()
}
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== Repeat Macro Rollback Example ===\n");
let counter = Arc::new(Mutex::new(Vec::new()));
let test_loop = TestLoop::new(counter.clone());
let mut dry = DryContext::new();
let mut wet = WetContext::new();
let max_iterations = 4usize;
dry_put!(dry, max_iterations);
println!("Initial counter state: {:?}", *counter.lock().unwrap());
println!("Running loop with {} iterations...\n", max_iterations);
match test_loop.perform(&mut dry, &mut wet).await {
Ok(_) => println!("Loop completed successfully (unexpected)"),
Err(e) => println!("Loop failed as expected: {}\n", e),
}
println!("Final counter state: {:?}", *counter.lock().unwrap());
println!("\n=== Summary ===");
println!("This example demonstrates:");
println!("1. The repeat! macro creates loop operations with rollback support");
println!(
"2. When FailingOp fails on iteration 2, CounterOp from that iteration is rolled back"
);
println!("3. Previous successful iterations (0, 1) remain committed");
println!("4. The counter should contain values from successful iterations only");
Ok(())
}