use std::time::Duration;
use cano::prelude::*;
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
enum Step {
Reserve,
Charge,
Ship,
Done,
}
struct Reserve;
struct Charge;
struct Ship;
#[saga::task(state = Step)]
impl Reserve {
type Output = u32;
async fn run(&self, _res: &Resources) -> Result<(TaskResult<Step>, u32), CanoError> {
let ticket = 42;
println!("reserve : holding inventory (ticket #{ticket})");
Ok((TaskResult::Single(Step::Charge), ticket))
}
async fn compensate(&self, _res: &Resources, ticket: u32) -> Result<(), CanoError> {
println!("reserve : releasing ticket #{ticket} (rollback)");
Ok(())
}
}
#[saga::task(state = Step)]
impl Charge {
type Output = String;
async fn run(&self, _res: &Resources) -> Result<(TaskResult<Step>, String), CanoError> {
let auth = "auth-XYZ".to_string();
println!("charge : capturing $42.00 (auth {auth})");
Ok((TaskResult::Single(Step::Ship), auth))
}
async fn compensate(&self, _res: &Resources, auth: String) -> Result<(), CanoError> {
println!("charge : refunding auth {auth} (rollback)");
Ok(())
}
}
#[task(state = Step)]
impl Ship {
fn config(&self) -> TaskConfig {
TaskConfig::minimal()
}
async fn run_bare(&self) -> Result<TaskResult<Step>, CanoError> {
println!("ship : dispatching shipment (this will overrun the 200ms budget)…");
tokio::time::sleep(Duration::from_secs(2)).await;
println!("ship : this line should never print");
Ok(TaskResult::Single(Step::Done))
}
}
#[tokio::main]
async fn main() {
let workflow = Workflow::bare()
.with_total_timeout(Duration::from_millis(200))
.register_with_compensation(Step::Reserve, Reserve)
.register_with_compensation(Step::Charge, Charge)
.register(Step::Ship, Ship)
.add_exit_state(Step::Done);
match workflow.orchestrate(Step::Reserve).await {
Ok(state) => println!("\nworkflow completed at {state:?}"),
Err(error) => println!("\nworkflow failed, rolled back: {error}"),
}
}