a3s-flow 0.4.0

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

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct InvoiceWorkflowInput {
    invoice_id: String,
}

#[derive(Deserialize)]
#[serde(rename_all = "camelCase")]
struct LoadInvoiceInput {
    invoice_id: String,
}

#[derive(Deserialize)]
struct ChargeCardInput {
    amount: u64,
}

#[derive(Deserialize, Serialize)]
struct Invoice {
    id: String,
    amount: u64,
    currency: String,
}

#[derive(Deserialize, Serialize)]
struct Charge {
    status: String,
    amount: u64,
}

#[derive(Deserialize, Serialize)]
struct InvoiceWorkflowOutput {
    invoice: Invoice,
    charge: Charge,
}

struct InvoiceRuntime;

#[async_trait]
impl FlowRuntime for InvoiceRuntime {
    async fn run_workflow(
        &self,
        invocation: WorkflowInvocation,
    ) -> a3s_flow::Result<RuntimeCommand> {
        let ctx = invocation.context();
        let input = ctx.input_as::<InvoiceWorkflowInput>()?;
        let invoice = ctx.step_output_as::<Invoice>("load-invoice")?;
        let charge = ctx.step_output_as::<Charge>("charge-card")?;

        match (invoice, charge) {
            (None, _) => Ok(ctx.schedule_step(
                "load-invoice",
                "load_invoice",
                json!({ "invoiceId": input.invoice_id }),
            )),
            (Some(invoice), None) => Ok(ctx.schedule_step(
                "charge-card",
                "charge_card",
                json!({
                    "invoiceId": invoice.id,
                    "amount": invoice.amount,
                }),
            )),
            (Some(invoice), Some(charge)) => {
                Ok(ctx.complete(json!(InvoiceWorkflowOutput { invoice, charge })))
            }
        }
    }

    async fn run_step(&self, invocation: StepInvocation) -> a3s_flow::Result<serde_json::Value> {
        match invocation.step_name.as_str() {
            "load_invoice" => {
                let input = invocation.input_as::<LoadInvoiceInput>()?;
                Ok(json!({
                    "id": input.invoice_id,
                    "amount": 4200,
                    "currency": "USD",
                }))
            }
            "charge_card" => {
                let input = invocation.input_as::<ChargeCardInput>()?;
                Ok(json!({
                    "status": "authorized",
                    "amount": input.amount,
                }))
            }
            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(InvoiceRuntime));
    let spec = WorkflowSpec::rust_embedded("examples.invoice", "0.1.0", "examples", "main");

    let run_id = engine
        .start_with_id(
            "invoice-demo-0001",
            spec,
            json!({ "invoiceId": "inv-0001" }),
        )
        .await?;
    let snapshot = engine.snapshot(&run_id).await?;

    println!("run_id={}", snapshot.run_id);
    println!("status={:?}", snapshot.status);
    let output = snapshot
        .output_as::<InvoiceWorkflowOutput>()?
        .expect("workflow completed");
    println!(
        "output={}",
        serde_json::to_string_pretty(&output).expect("serializable output")
    );
    Ok(())
}