use std::num::NonZeroU32;
use itinera::error::Error;
use itinera::event::Event;
use itinera::executor::local_executor_for;
use itinera::journey::{Contributor, DataBag, InitialData, JourneyId};
use itinera::policy::{OnSuccess, step_policy, workflow_policy};
use itinera::report::Reporter;
use itinera::service::{Provided, provide};
use itinera::step::{Outcome, Step, step};
use itinera::value::AnyValue;
use itinera::workflow::{role, workflow};
trait PaymentGateway: Send + Sync {
fn take(&self, amount: i64) -> String;
}
struct Terminal {
shop: &'static str,
attempt: NonZeroU32,
}
impl PaymentGateway for Terminal {
fn take(&self, amount: i64) -> String {
format!(
"{amount} paid at the {} terminal, attempt {}",
self.shop, self.attempt
)
}
}
#[derive(Step)]
struct Charge {
#[input]
amount: i64,
}
#[step]
impl Charge {
fn run(
self,
#[service] payments: &dyn PaymentGateway,
#[contributor] mut contributor: Contributor<'_>,
) -> Result<Outcome, Error> {
if self.amount <= 0 {
return Self::fail("declined");
}
contributor.contribute("payment", payments.take(self.amount));
contributor.contribute("charged", self.amount);
Self::succeed()
}
}
#[derive(Step)]
struct Ship;
#[step]
impl Ship {
fn run(self) -> Result<Outcome, Error> {
Self::succeed()
}
}
#[derive(Default)]
struct Audit;
#[step_policy(name = "audit")]
impl Audit {
#[on_step_success]
fn audited(
&self,
#[from_step] charged: i64,
#[contributor] mut contributor: Contributor<'_>,
) -> Result<Option<OnSuccess>, Error> {
contributor.contribute("audit", format!("charged {charged}"));
Self::carry_on()
}
}
trait Messages {
fn tell(&self, message: &str);
}
#[derive(Default)]
struct Notify;
#[workflow_policy(name = "notify")]
impl Notify {
#[on_workflow_success]
fn paid(
&self,
#[journey_id] journey_id: &JourneyId,
#[from_workflow] charged: i64,
#[from_workflow] payment: String,
#[role] messages: &dyn Messages,
) -> Result<(), Error> {
messages.tell(&format!("order {journey_id}: paid {charged}, {payment}"));
Ok(())
}
}
#[derive(Default)]
struct Printer;
impl Reporter for Printer {
fn report(&mut self, event: &Event) -> Result<(), Error> {
println!("{} {}", event.journey_id, event.kind());
Ok(())
}
}
#[workflow(name = "orders", policies = [Notify], reporters = [Printer])]
#[with_step(Charge, name = "charge", retry_budget = 1, policies = [Audit])]
#[with_step(Ship, name = "ship")]
struct Orders {
shop: &'static str,
}
#[role]
impl Messages for Orders {
fn tell(&self, message: &str) {
println!("{}: {message}", self.shop);
}
}
#[workflow]
impl Orders {
#[input_adapter(steps = ["charge"])]
fn pricing(
&self,
#[from_workflow] price: i64,
#[from_workflow] discount: Option<i64>,
) -> Result<Option<AnyValue>, Error> {
Self::supply(price - discount.unwrap_or_default())
}
#[provider(per_attempt, steps = ["charge"])]
fn payments(
&self,
#[attempt] attempt: NonZeroU32,
) -> Result<Provided<dyn PaymentGateway>, Error> {
Ok(provide!(Terminal {
shop: self.shop,
attempt,
}))
}
#[id_generator]
fn order_id(&self, data: &DataBag) -> Result<String, Error> {
let order: String = data.required("order")?;
Ok(format!("{}-{order}", self.shop))
}
}
#[derive(InitialData)]
struct NewOrder {
order: String,
price: i64,
discount: Option<i64>,
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let orders = [
NewOrder {
order: "1".to_owned(),
price: 40,
discount: Some(5),
},
NewOrder {
order: "2".to_owned(),
price: 10,
discount: Some(10),
},
];
let mut executor = local_executor_for!(Orders);
for order in orders {
let instance = Orders::builder()
.shop("corner")
.initial_data(order)
.create()?;
let result = executor.run(instance)?;
println!("{}: {}", result.journey_id, result.status.kind());
}
Ok(())
}