# Declaring by hand

## What you want to do

The attributes have written the shop's workflow for you since the first chapters, and checked it as they wrote it. You want to see what they write, and to write part of the shop that way yourself: for a declaration put together when the program starts, from the shop's configuration, or for a mistake you'd rather read as a value than as a compile error.

## The code

### The shop by hand

The shop is the one [chapter 36](https://itinera-dev.org/book/rust/36-orders-refused-before-they-start.md) runs, declared in the **long form**: without the attributes, for its pricing step, its audit and its workflow. The pricing is two things: the step, which runs once and implements `Step`, and what builds it for each attempt, which implements `StepFactory`, says what the step needs and builds it from what was resolved. Each input is named once, by a constant holding its key and its type, such as `Input<i64>` or `OptionalInput<i64>`, which both `needs` and `build` use, and `Resolved` hands over what was resolved for the attempt:

Rust, from the program [`ch38_declaring_by_hand`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand.rs)

```rust
const PRICE: Input<i64> = Input::new("price");
const DISCOUNT: OptionalInput<i64> = OptionalInput::new("discount");

/// Works out what an order costs, for one attempt: its price, less its discount if it has one,
/// given back as the amount to charge.
struct Price<'a> {
    price: i64,
    discount: Option<i64>,
    contributor: Contributor<'a>,
}

impl Step for Price<'_> {
    fn run(mut self) -> Result<Outcome, Error> {
        let amount = self.price - self.discount.unwrap_or(0);
        self.contributor.contribute("amount", amount);
        Ok(Outcome::success())
    }
}

/// Builds the pricing for each attempt, from what it needs.
struct Pricing;

impl StepFactory for Pricing {
    type Step<'a> = Price<'a>;

    fn needs(&self) -> StepNeeds {
        StepNeeds::new()
            .input(&PRICE)
            .optional_input(&DISCOUNT)
            .contributor()
    }

    fn build<'a>(&'a self, got: &mut Resolved<'a>) -> Result<Price<'a>, Error> {
        Ok(Price {
            price: got.input(&PRICE)?,
            discount: got.optional_input(&DISCOUNT)?,
            contributor: got.contributor()?,
        })
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch38_declaring_by_hand_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand_async.rs)

```rust
const PRICE: Input<i64> = Input::new("price");
const DISCOUNT: OptionalInput<i64> = OptionalInput::new("discount");

/// Works out what an order costs, for one attempt: its price, less its discount if it has one,
/// given back as the amount to charge.
struct Price<'a> {
    price: i64,
    discount: Option<i64>,
    contributor: Contributor<'a>,
}

impl AsyncStep for Price<'_> {
    async fn run(mut self) -> Result<Outcome, Error> {
        let amount = self.price - self.discount.unwrap_or(0);
        self.contributor.contribute("amount", amount);
        Ok(Outcome::success())
    }
}

/// Builds the pricing for each attempt, from what it needs.
struct Pricing;

impl AsyncStepFactory for Pricing {
    type Step<'a> = Price<'a>;

    fn needs(&self) -> StepNeeds {
        StepNeeds::new()
            .input(&PRICE)
            .optional_input(&DISCOUNT)
            .contributor()
    }

    fn build<'a>(&'a self, got: &mut Resolved<'a, Asynchronous>) -> Result<Price<'a>, Error> {
        Ok(Price {
            price: got.input(&PRICE)?,
            discount: got.optional_input(&DISCOUNT)?,
            contributor: got.contributor()?,
        })
    }
}
```

The audit implements the trait of its one hook, `OnStepSuccess<Orders>`: its `needs` lists what the hook requests, as a `HookNeeds`, and the hook reads it from `Requested` when it is called, with the journey ID, the step's name and the attempt, which every step hook gets:

Rust, from the program [`ch38_declaring_by_hand`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand.rs)

```rust
const AMOUNT: Input<i64> = Input::new("amount");
const RECEIPT: Input<String> = Input::new("receipt");

/// Writes an audit line for every step it is attached to that succeeds: the journey, the attempt
/// that took the money, the amount the order was charged, and the receipt the step gave back.
struct Audit;

impl OnStepSuccess<Orders> for Audit {
    fn needs() -> HookNeeds<StepSuccess> {
        HookNeeds::new()
            .from_workflow(&AMOUNT)
            .from_step(&RECEIPT)
            .contributor()
    }

    fn on_step_success(
        &self,
        mut got: Requested<'_, Orders, StepSuccess>,
    ) -> Result<Option<OnSuccess>, Error> {
        let journey = got.journey_id();
        let step = got.step_name();
        let attempt = got.attempt();
        let amount = got.from_workflow(&AMOUNT)?;
        let receipt = got.from_step(&RECEIPT)?;
        got.contributor()?.contribute(
            "audit",
            format!("{journey}: {step} took {amount} on attempt {attempt}, receipt {receipt}"),
        );
        Ok(None)
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch38_declaring_by_hand_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand_async.rs)

```rust
const AMOUNT: Input<i64> = Input::new("amount");
const RECEIPT: Input<String> = Input::new("receipt");

/// Writes an audit line for every step it is attached to that succeeds: the journey, the attempt
/// that took the money, the amount the order was charged, and the receipt the step gave back.
struct Audit;

impl AsyncOnStepSuccess<Orders> for Audit {
    fn needs() -> HookNeeds<StepSuccess> {
        HookNeeds::new()
            .from_workflow(&AMOUNT)
            .from_step(&RECEIPT)
            .contributor()
    }

    async fn on_step_success(
        &self,
        mut got: Requested<'_, Orders, StepSuccess, Asynchronous>,
    ) -> Result<Option<OnSuccess>, Error> {
        let journey = got.journey_id();
        let step = got.step_name();
        let attempt = got.attempt();
        let amount = got.from_workflow(&AMOUNT)?;
        let receipt = got.from_step(&RECEIPT)?;
        got.contributor()?.contribute(
            "audit",
            format!("{journey}: {step} took {amount} on attempt {attempt}, receipt {receipt}"),
        );
        Ok(None)
    }
}
```

`Orders` implements each role's trait, and `Provides` for it. The ID generator, the input adapter and the providers are its plain methods, each reading what it requested from its own `Requested`, such as `Provider<PerAttempt>`, which carries the attempt:

Rust, from the program [`ch38_declaring_by_hand`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand.rs)

```rust
/// The shop's workflow, whose own value is the channel the shop tells its customers through.
struct Orders {
    channel: &'static str,
}

impl Refunds for Orders {
    fn refund(&self, receipt: &str, amount: i64) {
        println!("the bank gives {amount} back on receipt {receipt}");
    }
}

impl Provides<dyn Refunds> for Orders {
    fn role(&self) -> &(dyn Refunds + 'static) {
        self
    }
}

impl Messages for Orders {
    fn tell(&self, customer: &str, message: &str) {
        println!("{} to {customer}: {message}", self.channel);
    }
}

impl Provides<dyn Messages> for Orders {
    fn role(&self) -> &(dyn Messages + 'static) {
        self
    }
}

const PARCEL: Input<Parcel> = Input::new("parcel");

impl Orders {
    /// Names each journey after its order.
    fn order_number(&self, data: &DataBag) -> Result<String, Error> {
        Ok(data.required("order")?)
    }

    /// Gives the shipping the weight of the order's parcel, as the warehouse describes it. Every
    /// other input of the shipping is read from the data bag.
    fn parcels(
        &self,
        mut got: Requested<'_, Self, InputAdapter>,
    ) -> Result<Option<AnyValue>, Error> {
        if got.key() != "weight" {
            return Ok(None);
        }
        let parcel = got.from_workflow(&PARCEL)?;
        Ok(Some(AnyValue::new(parcel.weight)))
    }

    /// The order book, which has the orders completed before today.
    fn ledger(
        &self,
        _: Requested<'_, Self, Provider<PerJourney>>,
    ) -> Result<Provided<dyn OrderBook>, Error> {
        Ok(provide!(Ledger {
            completed: vec!["A-13"],
        }))
    }

    /// The bank, as the payment client of the journey.
    fn bank(
        &self,
        _: Requested<'_, Self, Provider<PerJourney>>,
    ) -> Result<Provided<dyn Payments>, Error> {
        Ok(provide!(Bank { this_year: 2026 }))
    }

    /// The bank's card registry in Europe, for the charge's rescue step only.
    fn cards(
        &self,
        _: Requested<'_, Self, Provider<PerJourney>>,
    ) -> Result<Provided<dyn Cards>, Error> {
        Ok(Provided::from_box(card_registry("eu")).with_data("eu".to_owned()))
    }

    /// A carrier for every attempt of a step: the main carrier first, the backup carrier after.
    fn carriers(
        &self,
        got: Requested<'_, Self, Provider<PerAttempt>>,
    ) -> Result<Provided<dyn Carrier>, Error> {
        if got.attempt() == NonZeroU32::MIN {
            return Ok(provide!(MainCarrier));
        }
        Ok(provide!(BackupCarrier))
    }
}
```

Rust, the same in its asynchronous form, from the program [`ch38_declaring_by_hand_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand_async.rs)

```rust
/// The shop's workflow, whose own value is the channel the shop tells its customers through.
struct Orders {
    channel: &'static str,
}

#[async_trait]
impl Refunds for Orders {
    async fn refund(&self, receipt: &str, amount: i64) {
        network().await;
        println!("the bank gives {amount} back on receipt {receipt}");
    }
}

impl Provides<dyn Refunds> for Orders {
    fn role(&self) -> &(dyn Refunds + 'static) {
        self
    }
}

#[async_trait]
impl Messages for Orders {
    async fn tell(&self, customer: &str, message: &str) {
        network().await;
        println!("{} to {customer}: {message}", self.channel);
    }
}

impl Provides<dyn Messages> for Orders {
    fn role(&self) -> &(dyn Messages + 'static) {
        self
    }
}

const PARCEL: Input<Parcel> = Input::new("parcel");

impl Orders {
    /// Names each journey after its order.
    fn order_number(&self, data: &DataBag) -> Result<String, Error> {
        Ok(data.required("order")?)
    }

    /// Gives the shipping the weight of the order's parcel, as the warehouse describes it. Every
    /// other input of the shipping is read from the data bag.
    fn parcels(
        &self,
        mut got: Requested<'_, Self, InputAdapter>,
    ) -> Result<Option<AnyValue>, Error> {
        if got.key() != "weight" {
            return Ok(None);
        }
        let parcel = got.from_workflow(&PARCEL)?;
        Ok(Some(AnyValue::new(parcel.weight)))
    }

    /// The order book, which has the orders completed before today.
    async fn ledger(
        &self,
        _: Requested<'_, Self, Provider<PerJourney>, Asynchronous>,
    ) -> Result<Provided<dyn OrderBook>, Error> {
        Ok(provide!(Ledger {
            completed: vec!["A-13"],
        }))
    }

    /// The bank, as the payment client of the journey.
    async fn bank(
        &self,
        _: Requested<'_, Self, Provider<PerJourney>, Asynchronous>,
    ) -> Result<Provided<dyn Payments>, Error> {
        Ok(provide!(Bank { this_year: 2026 }))
    }

    /// The bank's card registry in Europe, for the charge's rescue step only.
    async fn cards(
        &self,
        _: Requested<'_, Self, Provider<PerJourney>, Asynchronous>,
    ) -> Result<Provided<dyn Cards>, Error> {
        Ok(Provided::from_box(card_registry("eu")).with_data("eu".to_owned()))
    }

    /// A carrier for every attempt of a step: the main carrier first, the backup carrier after.
    async fn carriers(
        &self,
        got: Requested<'_, Self, Provider<PerAttempt>, Asynchronous>,
    ) -> Result<Provided<dyn Carrier>, Error> {
        if got.attempt() == NonZeroU32::MIN {
            return Ok(provide!(MainCarrier));
        }
        Ok(provide!(BackupCarrier))
    }
}
```

`Parcel` is [chapter 21](https://itinera-dev.org/book/rust/21-deciding-where-a-steps-data-comes-from.md)'s, `OrderBook` is [chapter 22](https://itinera-dev.org/book/rust/22-finishing-a-journey-early.md)'s, `Payments` and `Carrier` are [chapter 12](https://itinera-dev.org/book/rust/12-trying-again.md)'s, `Messages` [chapter 19](https://itinera-dev.org/book/rust/19-letting-policies-act-on-the-outside-world.md)'s, `Refunds` [chapter 23](https://itinera-dev.org/book/rust/23-undoing-a-charge-when-the-shipping-fails.md)'s, and `Cards` and `card_registry` are [chapter 35](https://itinera-dev.org/book/rust/35-giving-the-rescue-step-a-service-of-its-own.md)'s.

And the workflow is put together by its builder, part by part, each service under a constant holding its key and its trait. The other steps and policies are still declared with the attributes, and go into the builder through their `descriptor`, and the rescue step through `rescued_by`:

Rust, from the program [`ch38_declaring_by_hand`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand.rs)

```rust
const ORDER_BOOK: Service<dyn OrderBook> = Service::new("order_book");
const PAYMENTS: Service<dyn Payments> = Service::new("payments");
const CARDS: Service<dyn Cards> = Service::new("cards");
const CARRIER: Service<dyn Carrier> = Service::new("carrier");

/// Declares the shop's workflow, part by part, and checks it. It looks the order up, and finishes
/// an order already completed. Otherwise it works out what the order costs, then charges it,
/// repairing a charge that fails and auditing one that succeeds, then ships, and shipping may be
/// tried once more, its charge refunded if it is given up. Once a journey has ended, the customer
/// is told.
fn orders() -> Result<WorkflowDescriptor<Orders>, Violations> {
    let audit = StepPolicyDescriptor::new("audit", || Audit).on_step_success();
    let parcels = InputAdapterDescriptor::new("parcels", step_name!("ship"), Orders::parcels)
        .needing(HookNeeds::new().from_workflow(&PARCEL));
    let cards = ServiceProviderDescriptor::new("cards", &CARDS, Orders::cards)
        .step(step_name!("charge_rescue"));
    WorkflowDescriptor::builder("orders")
        .step(Lookup::descriptor(step_name!("lookup")).policy(AlreadyCompleted::descriptor()))
        .step(StepDescriptor::new(step_name!("price"), Pricing))
        .step(
            Charge::descriptor(step_name!("charge"))
                .policy(audit)
                .policy(Outage::descriptor())
                .rescued_by::<RenewCard>(),
        )
        .step(
            Ship::descriptor(step_name!("ship"))
                .retry_budget(1)
                .policy(FailedShipping::descriptor()),
        )
        .policy(Notify::descriptor())
        .reporter::<Log>()
        .reporter::<Desk>()
        .id_generator(Orders::order_number)
        .input_adapter(parcels)
        .provider(ServiceProviderDescriptor::new(
            "ledger",
            &ORDER_BOOK,
            Orders::ledger,
        ))
        .provider(ServiceProviderDescriptor::new(
            "bank",
            &PAYMENTS,
            Orders::bank,
        ))
        .provider(cards)
        .provider(ServiceProviderDescriptor::per_attempt(
            "carriers",
            &CARRIER,
            Orders::carriers,
        ))
        .build()
}
```

Rust, the same in its asynchronous form, from the program [`ch38_declaring_by_hand_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand_async.rs)

```rust
const ORDER_BOOK: Service<dyn OrderBook> = Service::new("order_book");
const PAYMENTS: Service<dyn Payments> = Service::new("payments");
const CARDS: Service<dyn Cards> = Service::new("cards");
const CARRIER: Service<dyn Carrier> = Service::new("carrier");

/// Declares the shop's asynchronous workflow, part by part, and checks it. It looks the order up,
/// and finishes an order already completed. Otherwise it works out what the order costs, then
/// charges it, repairing a charge that fails and auditing one that succeeds, then ships, and
/// shipping may be tried once more, its charge refunded if it is given up. Once a journey has
/// ended, the customer is told.
fn orders() -> Result<WorkflowDescriptor<Orders, Asynchronous>, Violations> {
    let audit = StepPolicyDescriptor::new_async("audit", || Audit).on_step_success();
    let parcels = InputAdapterDescriptor::new("parcels", step_name!("ship"), Orders::parcels)
        .needing(HookNeeds::new().from_workflow(&PARCEL));
    let cards = ServiceProviderDescriptor::new_async("cards", &CARDS, Orders::cards)
        .step(step_name!("charge_rescue"));
    WorkflowDescriptor::async_builder("orders")
        .step(Lookup::descriptor(step_name!("lookup")).policy(AlreadyCompleted::descriptor()))
        .step(StepDescriptor::new_async(step_name!("price"), Pricing))
        .step(
            Charge::descriptor(step_name!("charge"))
                .policy(audit)
                .policy(Outage::descriptor())
                .rescued_by::<RenewCard>(),
        )
        .step(
            Ship::descriptor(step_name!("ship"))
                .retry_budget(1)
                .policy(FailedShipping::descriptor()),
        )
        .policy(Notify::descriptor())
        .reporter::<Desk>()
        .async_reporter::<Log>()
        .id_generator(Orders::order_number)
        .input_adapter(parcels)
        .provider(ServiceProviderDescriptor::new_async(
            "ledger",
            &ORDER_BOOK,
            Orders::ledger,
        ))
        .provider(ServiceProviderDescriptor::new_async(
            "bank",
            &PAYMENTS,
            Orders::bank,
        ))
        .provider(cards)
        .provider(ServiceProviderDescriptor::per_attempt_async(
            "carriers",
            &CARRIER,
            Orders::carriers,
        ))
        .build()
}
```

The rest is as earlier chapters show it: the lookup and `already_completed` in [chapter 22](https://itinera-dev.org/book/rust/22-finishing-a-journey-early.md), the charge in [chapter 33](https://itinera-dev.org/book/rust/33-giving-every-reporter-what-it-needs.md), `RenewCard` in [chapter 35](https://itinera-dev.org/book/rust/35-giving-the-rescue-step-a-service-of-its-own.md), the shipping in [chapter 17](https://itinera-dev.org/book/rust/17-giving-up-a-step-early.md), `outage` in [chapter 34](https://itinera-dev.org/book/rust/34-saying-which-attempt-took-the-money.md), `failed_shipping` in [chapter 23](https://itinera-dev.org/book/rust/23-undoing-a-charge-when-the-shipping-fails.md), `notify` in [chapter 19](https://itinera-dev.org/book/rust/19-letting-policies-act-on-the-outside-world.md), the log in [chapter 33](https://itinera-dev.org/book/rust/33-giving-every-reporter-what-it-needs.md), [chapter 35](https://itinera-dev.org/book/rust/35-giving-the-rescue-step-a-service-of-its-own.md) and [chapter 36](https://itinera-dev.org/book/rust/36-orders-refused-before-they-start.md), and the desk in [chapter 11](https://itinera-dev.org/book/rust/11-naming-a-journey.md).

In the asynchronous form, the pricing implements `AsyncStep` and `AsyncStepFactory`, the audit `AsyncOnStepSuccess`, the roles take `#[async_trait]`, and every descriptor is made with its asynchronous constructor, such as `WorkflowDescriptor::async_builder`.

`main` calls `orders()` once, before any journey, and makes each order's instance with `orders.instance(Orders { channel: "email" })`:

Rust, from the program [`ch38_declaring_by_hand`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand.rs)

```rust
let created = orders
    .instance(Orders { channel: "email" })
    .reporters_global_metadata("customer", customer.to_owned())
    .initial_data(NewOrder {
        order: order.map(str::to_owned),
        customer: customer.to_owned(),
        price,
        discount,
        parcel: Parcel { weight, size },
        card: card.map(str::to_owned),
        gift_card,
    })
    .create();
```

Rust, the same in its asynchronous form, from the program [`ch38_declaring_by_hand_async`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_declaring_by_hand_async.rs)

```rust
/// An instance of the shop's workflow for an order paid with this card.
fn order(
    orders: &WorkflowDescriptor<Orders, Asynchronous>,
    order: Option<&str>,
    customer: &str,
    price: i64,
    discount: Option<i64>,
    parcel: Parcel,
    card: &str,
) -> Result<Instance<Orders, Asynchronous>, Box<dyn std::error::Error>> {
    Ok(orders
        .instance(Orders { channel: "email" })
        .reporters_global_metadata("customer", customer.to_owned())
        .initial_data(NewOrder {
            order: order.map(str::to_owned),
            customer: customer.to_owned(),
            price,
            discount,
            parcel,
            card: Some(card.to_owned()),
            gift_card: false,
        })
        .create()?)
}

/// Runs an order's journey with an executor of its own.
async fn journey(instance: Instance<Orders, Asynchronous>) -> Result<JourneyResult, Refusal> {
    AsyncLocalExecutor::new().run(instance).await
}
```

In the asynchronous form, `order` makes the instance that way, `journey` runs it with `AsyncLocalExecutor::new()`, and `main` reads back only the audit line, as `Audited`, from [chapter 34](https://itinera-dev.org/book/rust/34-saying-which-attempt-took-the-money.md). `main` runs Ann's order and Jo's, and prints:

What `ch38_declaring_by_hand` prints, recorded in [`ch38_declaring_by_hand.txt`](https://github.com/itinera-dev/book/blob/main/rust/expected/ch38_declaring_by_hand.txt)

```text
Ann's order A-1:
log orders A-1 for Ann #1 journey_started card, customer, discount, gift_card, order, parcel, price
log orders A-1 for Ann #2 attempt_started lookup, attempt 1
log orders A-1 for Ann #3 service_provided lookup gets order_book from ledger, a ch38_declaring_by_hand::Ledger
the order book has no order A-1 yet
log orders A-1 for Ann #4 step_succeeded lookup
log orders A-1 for Ann #5 contribution_committed completed
log orders A-1 for Ann #6 hook_called already_completed: on step success of lookup
log orders A-1 for Ann #7 attempt_started price, attempt 1
log orders A-1 for Ann #8 step_succeeded price
log orders A-1 for Ann #9 contribution_committed amount
log orders A-1 for Ann #10 attempt_started charge, attempt 1
log orders A-1 for Ann #11 service_provided charge gets payments from bank, a ch38_declaring_by_hand::Bank
the bank charges 30 for order A-1
log orders A-1 for Ann, receipt R-A-1 #12 global_metadata_set receipt
log orders A-1 for Ann, receipt R-A-1 #13 step_succeeded charge
log orders A-1 for Ann, receipt R-A-1 #14 contribution_committed receipt
log orders A-1 for Ann, receipt R-A-1 #15 hook_called audit: on step success of charge
log orders A-1 for Ann, receipt R-A-1 #16 contribution_committed audit
log orders A-1 for Ann, receipt R-A-1 #17 attempt_started ship, attempt 1
log orders A-1 for Ann, receipt R-A-1 #18 input_adapter_supplied ship gets weight from parcels
log orders A-1 for Ann, receipt R-A-1 #19 service_provided ship gets carrier from carriers, a ch38_declaring_by_hand::MainCarrier
the main carrier books order A-1
log orders A-1 for Ann, receipt R-A-1 #20 step_info ship: the parcel leaves with receipt R-A-1
log orders A-1 for Ann, receipt R-A-1 #21 step_succeeded ship
log orders A-1 for Ann, receipt R-A-1 #22 contribution_committed tracking
log orders A-1 for Ann, receipt R-A-1 #23 optional_input_absent code
email to Ann: Order A-1 is on its way, tracking T-A-1.
log orders A-1 for Ann, receipt R-A-1 #24 hook_called notify: on workflow success
log orders A-1 for Ann, receipt R-A-1 #25 journey_succeeded
the journey succeeded, with receipt R-A-1 and tracking T-A-1
audit: A-1: charge took 30 on attempt 1, receipt R-A-1
Jo's order A-8:
log orders A-8 for Jo #1 journey_started card, customer, gift_card, order, parcel, price
log orders A-8 for Jo #2 attempt_started lookup, attempt 1
log orders A-8 for Jo #3 service_provided lookup gets order_book from ledger, a ch38_declaring_by_hand::Ledger
the order book has no order A-8 yet
log orders A-8 for Jo #4 step_succeeded lookup
log orders A-8 for Jo #5 contribution_committed completed
log orders A-8 for Jo #6 hook_called already_completed: on step success of lookup
log orders A-8 for Jo #7 attempt_started price, attempt 1
log orders A-8 for Jo #8 optional_input_absent discount
log orders A-8 for Jo #9 step_succeeded price
log orders A-8 for Jo #10 contribution_committed amount
log orders A-8 for Jo #11 attempt_started charge, attempt 1
log orders A-8 for Jo #12 service_provided charge gets payments from bank, a ch38_declaring_by_hand::Bank
the bank declines card jo-2024: it has expired
log orders A-8 for Jo #13 step_failed charge: card-expired
log orders A-8 for Jo #14 step_rescuing charge: failure, rescued by charge_rescue
log orders A-8 for Jo #15 attempt_started charge_rescue, attempt 1
log orders A-8 for Jo #16 service_provided charge_rescue gets cards from cards, a dyn ch38_declaring_by_hand::Cards, with eu
the card registry in eu says card jo-2024 was replaced by jo-2029
log orders A-8 for Jo #17 step_succeeded charge_rescue
log orders A-8 for Jo #18 contribution_committed card
log orders A-8 for Jo #19 data_overwritten card
log orders A-8 for Jo #20 step_rescued charge by charge_rescue
log orders A-8 for Jo #21 attempt_started charge, attempt 2
the bank charges 30 for order A-8
log orders A-8 for Jo, receipt R-A-8 #22 global_metadata_set receipt
log orders A-8 for Jo, receipt R-A-8 #23 step_succeeded charge
log orders A-8 for Jo, receipt R-A-8 #24 contribution_committed receipt
log orders A-8 for Jo, receipt R-A-8 #25 hook_called audit: on step success of charge
log orders A-8 for Jo, receipt R-A-8 #26 contribution_committed audit
log orders A-8 for Jo, receipt R-A-8 #27 attempt_started ship, attempt 1
log orders A-8 for Jo, receipt R-A-8 #28 input_adapter_supplied ship gets weight from parcels
log orders A-8 for Jo, receipt R-A-8 #29 service_provided ship gets carrier from carriers, a ch38_declaring_by_hand::MainCarrier
the main carrier books order A-8
log orders A-8 for Jo, receipt R-A-8 #30 step_info ship: the parcel leaves with receipt R-A-8
log orders A-8 for Jo, receipt R-A-8 #31 step_succeeded ship
log orders A-8 for Jo, receipt R-A-8 #32 contribution_committed tracking
log orders A-8 for Jo, receipt R-A-8 #33 optional_input_absent code
email to Jo: Order A-8 is on its way, tracking T-A-8.
log orders A-8 for Jo, receipt R-A-8 #34 hook_called notify: on workflow success
log orders A-8 for Jo, receipt R-A-8 #35 journey_succeeded
the journey succeeded, with receipt R-A-8 and tracking T-A-8
audit: A-8: charge took 30 on attempt 2, receipt R-A-8
```

What the asynchronous form `ch38_declaring_by_hand_async` prints, recorded in [`ch38_declaring_by_hand_async.txt`](https://github.com/itinera-dev/book/blob/main/rust/expected/ch38_declaring_by_hand_async.txt)

```text
Ann's order A-1:
log orders A-1 for Ann #1 journey_started card, customer, discount, gift_card, order, parcel, price
log orders A-1 for Ann #2 attempt_started lookup, attempt 1
log orders A-1 for Ann #3 service_provided lookup gets order_book from ledger, a ch38_declaring_by_hand_async::Ledger
the order book has no order A-1 yet
log orders A-1 for Ann #4 step_succeeded lookup
log orders A-1 for Ann #5 contribution_committed completed
log orders A-1 for Ann #6 hook_called already_completed: on step success of lookup
log orders A-1 for Ann #7 attempt_started price, attempt 1
log orders A-1 for Ann #8 step_succeeded price
log orders A-1 for Ann #9 contribution_committed amount
log orders A-1 for Ann #10 attempt_started charge, attempt 1
log orders A-1 for Ann #11 service_provided charge gets payments from bank, a ch38_declaring_by_hand_async::Bank
the bank charges 30 for order A-1
log orders A-1 for Ann, receipt R-A-1 #12 global_metadata_set receipt
log orders A-1 for Ann, receipt R-A-1 #13 step_succeeded charge
log orders A-1 for Ann, receipt R-A-1 #14 contribution_committed receipt
log orders A-1 for Ann, receipt R-A-1 #15 hook_called audit: on step success of charge
log orders A-1 for Ann, receipt R-A-1 #16 contribution_committed audit
log orders A-1 for Ann, receipt R-A-1 #17 attempt_started ship, attempt 1
log orders A-1 for Ann, receipt R-A-1 #18 input_adapter_supplied ship gets weight from parcels
log orders A-1 for Ann, receipt R-A-1 #19 service_provided ship gets carrier from carriers, a ch38_declaring_by_hand_async::MainCarrier
the main carrier books order A-1
log orders A-1 for Ann, receipt R-A-1 #20 step_info ship: the parcel leaves with receipt R-A-1
log orders A-1 for Ann, receipt R-A-1 #21 step_succeeded ship
log orders A-1 for Ann, receipt R-A-1 #22 contribution_committed tracking
log orders A-1 for Ann, receipt R-A-1 #23 optional_input_absent code
email to Ann: Order A-1 is on its way, tracking T-A-1.
log orders A-1 for Ann, receipt R-A-1 #24 hook_called notify: on workflow success
log orders A-1 for Ann, receipt R-A-1 #25 journey_succeeded
order A-1 succeeded
audit: A-1: charge took 30 on attempt 1, receipt R-A-1
Jo's order A-8:
log orders A-8 for Jo #1 journey_started card, customer, gift_card, order, parcel, price
log orders A-8 for Jo #2 attempt_started lookup, attempt 1
log orders A-8 for Jo #3 service_provided lookup gets order_book from ledger, a ch38_declaring_by_hand_async::Ledger
the order book has no order A-8 yet
log orders A-8 for Jo #4 step_succeeded lookup
log orders A-8 for Jo #5 contribution_committed completed
log orders A-8 for Jo #6 hook_called already_completed: on step success of lookup
log orders A-8 for Jo #7 attempt_started price, attempt 1
log orders A-8 for Jo #8 optional_input_absent discount
log orders A-8 for Jo #9 step_succeeded price
log orders A-8 for Jo #10 contribution_committed amount
log orders A-8 for Jo #11 attempt_started charge, attempt 1
log orders A-8 for Jo #12 service_provided charge gets payments from bank, a ch38_declaring_by_hand_async::Bank
the bank declines card jo-2024: it has expired
log orders A-8 for Jo #13 step_failed charge: card-expired
log orders A-8 for Jo #14 step_rescuing charge: failure, rescued by charge_rescue
log orders A-8 for Jo #15 attempt_started charge_rescue, attempt 1
log orders A-8 for Jo #16 service_provided charge_rescue gets cards from cards, a dyn ch38_declaring_by_hand_async::Cards, with eu
the card registry in eu says card jo-2024 was replaced by jo-2029
log orders A-8 for Jo #17 step_succeeded charge_rescue
log orders A-8 for Jo #18 contribution_committed card
log orders A-8 for Jo #19 data_overwritten card
log orders A-8 for Jo #20 step_rescued charge by charge_rescue
log orders A-8 for Jo #21 attempt_started charge, attempt 2
the bank charges 30 for order A-8
log orders A-8 for Jo, receipt R-A-8 #22 global_metadata_set receipt
log orders A-8 for Jo, receipt R-A-8 #23 step_succeeded charge
log orders A-8 for Jo, receipt R-A-8 #24 contribution_committed receipt
log orders A-8 for Jo, receipt R-A-8 #25 hook_called audit: on step success of charge
log orders A-8 for Jo, receipt R-A-8 #26 contribution_committed audit
log orders A-8 for Jo, receipt R-A-8 #27 attempt_started ship, attempt 1
log orders A-8 for Jo, receipt R-A-8 #28 input_adapter_supplied ship gets weight from parcels
log orders A-8 for Jo, receipt R-A-8 #29 service_provided ship gets carrier from carriers, a ch38_declaring_by_hand_async::MainCarrier
the main carrier books order A-8
log orders A-8 for Jo, receipt R-A-8 #30 step_info ship: the parcel leaves with receipt R-A-8
log orders A-8 for Jo, receipt R-A-8 #31 step_succeeded ship
log orders A-8 for Jo, receipt R-A-8 #32 contribution_committed tracking
log orders A-8 for Jo, receipt R-A-8 #33 optional_input_absent code
email to Jo: Order A-8 is on its way, tracking T-A-8.
log orders A-8 for Jo, receipt R-A-8 #34 hook_called notify: on workflow success
log orders A-8 for Jo, receipt R-A-8 #35 journey_succeeded
order A-8 succeeded
audit: A-8: charge took 30 on attempt 2, receipt R-A-8
```

### A workflow put together wrongly

This small shop has steps and policies of its own, and `parcels_for` declares its adapter:

Rust, from the program [`ch38_violations`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_violations.rs)

```rust
/// Charges an order's price.
#[derive(Step)]
struct Charge {
    #[input]
    price: i64,
}

#[step]
impl Charge {
    fn run(self, #[contributor] mut contributor: Contributor<'_>) -> Result<Outcome, Error> {
        if self.price <= 0 {
            return Self::fail("nothing-to-charge");
        }
        contributor.contribute("charged", self.price);
        Self::succeed()
    }
}

/// Ships an order's parcel, of this weight in kg, through the carrier.
#[derive(Step)]
struct Ship {
    #[input]
    #[expect(dead_code, reason = "the shipping is declared, never run")]
    weight: i64,
}

#[step]
impl Ship {
    fn run(self) -> Result<Outcome, Error> {
        Self::succeed()
    }
}

/// Audits every successful charge.
#[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()
    }
}

/// Writes a receipt for every successful charge.
#[derive(Default)]
struct Receipts;

#[step_policy(name = "receipts")]
impl Receipts {
    #[on_step_success]
    fn written(
        &self,
        #[from_step] charged: i64,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<Option<OnSuccess>, Error> {
        contributor.contribute("receipt", format!("R-{charged}"));
        Self::carry_on()
    }
}

/// The shop's workflow, which holds nothing of its own.
struct Orders;

/// How the warehouse describes an order's parcel: its weight in kg, and its size in cm.
#[derive(Clone, Serialize, Deserialize)]
struct Parcel {
    weight: i64,
    size: [i64; 3],
}

const PARCEL: Input<Parcel> = Input::new("parcel");

/// Gives the shipping the weight of the order's parcel. Every other input is read from the data
/// bag.
fn parcels(
    _: &Orders,
    mut got: Requested<'_, Orders, InputAdapter>,
) -> Result<Option<AnyValue>, Error> {
    if got.key() != "weight" {
        return Ok(None);
    }
    Ok(Some(AnyValue::new(got.from_workflow(&PARCEL)?.weight)))
}

/// Declares the parcels adapter, attached to the step of this name.
fn parcels_for(step: StepName) -> InputAdapterDescriptor<Orders> {
    InputAdapterDescriptor::new("parcels", step, parcels)
        .needing(HookNeeds::new().from_workflow(&PARCEL))
}
```

It is declared by hand with three mistakes: `ship` listed twice, two policies of the charge that both define `on step success`, and the parcels adapter attached to `shipping`, a step the workflow doesn't have:

Rust, from the program [`ch38_violations`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_violations.rs)

```rust
let refused = WorkflowDescriptor::builder("orders")
    .step(
        Charge::descriptor(step_name!("charge"))
            .policy(Audit::descriptor())
            .policy(Receipts::descriptor()),
    )
    .step(Ship::descriptor(step_name!("ship")))
    .step(Ship::descriptor(step_name!("ship")).retry_budget(1))
    .input_adapter(parcels_for(step_name!("shipping")))
    .build();
```

The program compiles. `build` returns the violations instead of the workflow, and `main` reads them:

Rust, from the program [`ch38_violations`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_violations.rs)

```rust
match refused {
    Ok(_) => println!("the workflow was built"),
    Err(violations) => {
        println!("the workflow was refused:");
        for violation in &violations {
            println!("  {violation}");
        }
    }
}
```

Fixed, it builds, and `main` prints its listing:

Rust, from the program [`ch38_violations`](https://github.com/itinera-dev/book/blob/main/rust/examples/ch38_violations.rs)

```rust
let built = WorkflowDescriptor::builder("orders")
    .step(Charge::descriptor(step_name!("charge")).policy(Audit::descriptor()))
    .step(Ship::descriptor(step_name!("ship")).retry_budget(1))
    .input_adapter(parcels_for(step_name!("ship")))
    .build();
match built {
    Ok(orders) => {
        println!("the fixed workflow was built:");
        for listed in orders.listing() {
            println!("  {}. {}", listed.position, listed.step);
        }
    }
    Err(violations) => println!("still refused: {violations}"),
}
```

It prints:

What `ch38_violations` prints, recorded in [`ch38_violations.txt`](https://github.com/itinera-dev/book/blob/main/rust/expected/ch38_violations.txt)

```text
the workflow was refused:
  duplicate step name: ship
  hook defined twice: on step success on step charge
  input adapter for unknown step: parcels on shipping
the fixed workflow was built:
  1. charge
  2. ship
```

## How it works

1. **Both forms make the same descriptors.** A workflow is a **workflow descriptor**: its name, a step descriptor for each step in order, its policies, reporters, ID generator, input adapters and providers. The attributes write them from your types, and the long form writes them by hand. The executor runs either the same way.
2. **Each attribute stands for a trait or a builder.** `#[step]` makes the step's `StepDescriptor`, through `descriptor(name)`, from what `#[derive(Step)]` says the step needs. `#[step_policy]` implements a trait for each hook and makes a `StepPolicyDescriptor`. `#[provider]` makes a `ServiceProviderDescriptor`, and `#[role]` implements `Provides` for the workflow. `#[workflow(...)]` builds the `WorkflowDescriptor` with `WorkflowDescriptor::builder`, once. And `local_executor_for!(Orders)` stands for `LocalExecutor::new()`, or `AsyncLocalExecutor::new()` for an asynchronous workflow.
3. **A step is built afresh for every attempt.** The factory builds `Price` from what was resolved for that attempt, which is why `Price<'a>` borrows its contributor: it lives for one attempt. Nothing carries over to the next.
4. **`build` checks the declaration, as admission.** It returns `Result<WorkflowDescriptor<Orders>, Violations>`: the workflow, or every violation found, each displayed as the specification names it. The attributes run the same checks while the program compiles, as [chapter 37](https://itinera-dev.org/book/rust/37-mistakes-caught-before-a-journey.md) showed; the long form runs them when the program starts.
5. **The two forms mix.** Most of the shop here is still declared with the attributes. Write by hand only the part that needs it.
6. **When you need the long form.** A declaration put together from configuration when the program starts, such as a gift-wrapping step only in the shops that offer it; violations read as values; a step or policy generic over a type, which the attributes refuse; or a program built without the attributes at all, with `default-features = false` on `itinera`, which turns off its `macros` option. Otherwise, the attributes say the same in fewer lines.

The shop can now declare every part of its workflow, in either form. What the book hasn't said is how to decide, for a new rule of the shop, which part it belongs in.

## Outcomes

We have shown what the attributes write, how to write it by hand, and how a workflow declared by hand reports its mistakes.

| The usual way                                                                     | With Itinera                                                                         |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------ |
| Wiring written by a framework's annotations is often hidden in code nobody reads. | The attributes write public descriptors, which you can write yourself, part by part. |
| A wiring mistake found at start-up often stops at the first one.                  | `build` returns every violation, as values.                                          |
