a3s-flow 0.4.1

Durable workflow engine and Rust SDK for A3S
Documentation
use a3s_flow::{
    FlowEngine, FlowError, FlowRuntime, RuntimeCommand, StepInvocation, WorkflowInvocation,
    WorkflowRunStatus, WorkflowSpec,
};
use async_trait::async_trait;
use serde_json::json;
use std::sync::Arc;

struct CheckoutRuntime;

#[async_trait]
impl FlowRuntime for CheckoutRuntime {
    async fn run_workflow(
        &self,
        invocation: WorkflowInvocation,
    ) -> a3s_flow::Result<RuntimeCommand> {
        let ctx = invocation.context();
        let reserve = ctx.step_output("reserve-inventory");
        let charge = ctx.step_output("charge-card");
        let release = ctx.step_output("release-inventory");

        match (reserve, charge, release) {
            (None, _, _) => Ok(ctx.schedule_step(
                "reserve-inventory",
                "reserve_inventory",
                json!({
                    "sku": ctx.input()["sku"],
                    "quantity": ctx.input()["quantity"],
                }),
            )),
            (Some(reserve), None, _) => Ok(ctx.schedule_step(
                "charge-card",
                "charge_card",
                json!({
                    "orderId": ctx.input()["orderId"],
                    "reservationId": reserve["reservationId"],
                    "amount": ctx.input()["amount"],
                }),
            )),
            (Some(_), Some(charge), None) if charge["ok"] == false => Ok(ctx.schedule_step(
                "release-inventory",
                "release_inventory",
                json!({
                    "reservationId": charge["reservationId"],
                    "reason": charge["reason"],
                }),
            )),
            (Some(reserve), Some(charge), Some(release)) if charge["ok"] == false => Ok(ctx
                .complete(json!({
                    "status": "compensated",
                    "reserve": reserve,
                    "charge": charge,
                    "compensation": release,
                }))),
            (Some(reserve), Some(charge), _) => Ok(ctx.complete(json!({
                "status": "completed",
                "reserve": reserve,
                "charge": charge,
            }))),
        }
    }

    async fn run_step(&self, invocation: StepInvocation) -> a3s_flow::Result<serde_json::Value> {
        match invocation.step_name.as_str() {
            "reserve_inventory" => Ok(json!({
                "ok": true,
                "sku": invocation.input["sku"],
                "quantity": invocation.input["quantity"],
                "reservationId": "resv-0001",
            })),
            "charge_card" => Ok(json!({
                "ok": false,
                "orderId": invocation.input["orderId"],
                "reservationId": invocation.input["reservationId"],
                "reason": "card_declined",
            })),
            "release_inventory" => Ok(json!({
                "released": true,
                "reservationId": invocation.input["reservationId"],
                "reason": invocation.input["reason"],
            })),
            step => Err(FlowError::Runtime(format!("unknown step: {step}"))),
        }
    }
}

#[tokio::main(flavor = "current_thread")]
async fn main() -> a3s_flow::Result<()> {
    let engine = FlowEngine::in_memory(Arc::new(CheckoutRuntime));
    let spec = WorkflowSpec::rust_embedded("examples.checkout", "0.1.0", "examples", "main");

    let run_id = engine
        .start_with_id(
            "checkout-demo-0001",
            spec,
            json!({
                "orderId": "order-0001",
                "sku": "a3s-flow-shirt",
                "quantity": 1,
                "amount": 4200,
            }),
        )
        .await?;
    let snapshot = engine.snapshot(&run_id).await?;

    assert_eq!(snapshot.status, WorkflowRunStatus::Completed);
    assert_eq!(snapshot.output.as_ref().unwrap()["status"], "compensated");

    println!("run_id={}", snapshot.run_id);
    println!("status={:?}", snapshot.status);
    println!("steps={}", snapshot.steps.len());
    println!(
        "output={}",
        serde_json::to_string_pretty(&snapshot.output).unwrap()
    );
    Ok(())
}