# Finishing a journey early

## What you want to do

Now and then the website sends an order a second time: Mia's order A-13, paid and shipped yesterday, arrives again today. You want the shop to see that the order is already done, and end its journey there, as succeeded, without charging Mia or booking her parcel a second time.

## The code

The shop keeps an order book, which says whether an order was already completed. It is a service, like the bank:

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

```rust
/// The shop's order book: it says whether an order was already completed.
trait OrderBook: Send + Sync {
    fn completed(&self, order: &str) -> bool;
}
```

The workflow's new provider, `ledger`, makes the order book. In this program it is faked, and holds the one order completed before today, Mia's:

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

```rust
/// The order book, which has the orders completed before today.
#[provider(key = "order_book")]
fn ledger(&self) -> Result<Provided<dyn OrderBook>, Error> {
    Ok(provide!(Ledger {
        completed: vec!["A-13"],
    }))
}
```

A new step, the lookup, asks the order book about the order and gives back what it says, under `completed`. It decides nothing about what happens next:

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

```rust
/// Looks the order up in the order book, and gives back whether it was already completed.
#[derive(Step)]
struct Lookup {
    #[input]
    order: String,
}

#[step]
impl Lookup {
    fn run(
        self,
        #[service] order_book: &dyn OrderBook,
        #[contributor] mut contributor: Contributor<'_>,
    ) -> Result<Outcome, Error> {
        contributor.contribute("completed", order_book.completed(&self.order));
        Self::succeed()
    }
}
```

A step policy, `already_completed`, reads that answer. Its `on step success` asks for `completed` from the step, as the audit asks for the receipt in [chapter 15](https://itinera-dev.org/book/rust/15-reacting-to-what-a-step-did.md). For an order already completed, it returns `Self::finish_workflow()`, which is the lifecycle `FinishWorkflow`. Otherwise it carries on:

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

```rust
/// Finishes the journey of an order that was already completed: there is nothing left to do for
/// it.
#[derive(Default)]
struct AlreadyCompleted;

#[step_policy(name = "already_completed")]
impl AlreadyCompleted {
    #[on_step_success]
    fn looked_up(&self, #[from_step] completed: bool) -> Result<Option<OnSuccess>, Error> {
        if completed {
            return Self::finish_workflow();
        }
        Self::carry_on()
    }
}
```

The workflow runs the lookup first, with the policy attached to it:

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

```rust
/// The shop's workflow: 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.
/// Once a journey has ended, the customer is told, through the channel the shop configures.
#[workflow(name = "orders", reporters = [Log, Desk], policies = [Notify])]
#[with_step(Lookup, name = "lookup", policies = [AlreadyCompleted])]
#[with_step(Price, name = "price")]
#[with_step(Charge, name = "charge", rescue = RenewCard, policies = [Audit, Outage])]
#[with_step(Ship, name = "ship", retry_budget = 1, policies = [HeavyParcels])]
struct Orders {
    channel: &'static str,
}
```

The other steps, policies and reporters it names are as earlier chapters show them: the pricing, the charge and the audit in [chapter 20](https://itinera-dev.org/book/rust/20-an-optional-input.md), `RenewCard` in [chapter 14](https://itinera-dev.org/book/rust/14-repairing-then-trying-once-more.md), the shipping, `outage` and `heavy_parcels` in [chapter 17](https://itinera-dev.org/book/rust/17-giving-up-a-step-early.md), `notify` in [chapter 19](https://itinera-dev.org/book/rust/19-letting-policies-act-on-the-outside-world.md) and the desk in [chapter 11](https://itinera-dev.org/book/rust/11-naming-a-journey.md).

The log is [chapter 21](https://itinera-dev.org/book/rust/21-deciding-where-a-steps-data-comes-from.md)'s, and now also says which policy finished a journey:

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

```rust
EventBody::JourneySucceeded {
    decided_by: Some(policy),
    ..
} => Some(format!("FinishWorkflow from {policy}")),
```

The rest is [chapter 21](https://itinera-dev.org/book/rust/21-deciding-where-a-steps-data-comes-from.md)'s shop, and `Order`, the output, is [chapter 19](https://itinera-dev.org/book/rust/19-letting-policies-act-on-the-outside-world.md)'s. `main` runs two orders, Ann's and Mia's:

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

```rust
fn main() -> Result<(), Box<dyn std::error::Error>> {
    // The order, the customer, the price, the discount, the parcel's weight in kg and its size in
    // cm, the card, and whether the order is a gift card.
    let orders = [
        (
            "A-1",
            "Ann",
            40,
            Some(10),
            2,
            [30, 20, 10],
            "ann-2028",
            false,
        ),
        ("A-13", "Mia", 50, None, 4, [40, 30, 30], "mia-2029", false),
    ];
    for (order, customer, price, discount, weight, size, card, gift_card) in orders {
        println!("{customer}'s order:");
        let instance = Orders::builder()
            .channel("email")
            .initial_data(NewOrder {
                order: order.to_owned(),
                customer: customer.to_owned(),
                price,
                discount,
                parcel: Parcel { weight, size },
                card: card.to_owned(),
                gift_card,
            })
            .create()?;
        let result = local_executor_for!(Orders).run(instance)?;
        let data: Order = result.output()?;
        let receipt = data.receipt.as_deref().unwrap_or("none");
        let tracking = data.tracking.as_deref().unwrap_or("none");
        println!(
            "the journey {}, with receipt {receipt} and tracking {tracking}",
            result.status.kind(),
        );
        println!("audit: {}", data.audit.as_deref().unwrap_or("none"));
    }
    Ok(())
}
```

It prints:

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

```text
Ann's order:
log orders A-1 #1 journey_started card, customer, discount, gift_card, order, parcel, price
log orders A-1 #2 attempt_started lookup, attempt 1
log orders A-1 #3 service_provided lookup gets order_book from ledger, a ch22_finishing_a_journey_early::Ledger
the order book has no order A-1 yet
log orders A-1 #4 step_succeeded lookup
log orders A-1 #5 contribution_committed completed
log orders A-1 #6 hook_called already_completed: on step success of lookup
log orders A-1 #7 attempt_started price, attempt 1
log orders A-1 #8 step_succeeded price
log orders A-1 #9 contribution_committed amount
log orders A-1 #10 attempt_started charge, attempt 1
log orders A-1 #11 service_provided charge gets payments from bank, a ch22_finishing_a_journey_early::Bank
the bank charges 30 for order A-1
log orders A-1 #12 step_succeeded charge
log orders A-1 #13 contribution_committed receipt
log orders A-1 #14 hook_called audit: on step success of charge
log orders A-1 #15 contribution_committed audit
log orders A-1 #16 attempt_started ship, attempt 1
log orders A-1 #17 input_adapter_supplied ship gets weight from parcels
log orders A-1 #18 service_provided ship gets carrier from carriers, a ch22_finishing_a_journey_early::MainCarrier
the main carrier books order A-1
log orders A-1 #19 step_info ship: the parcel leaves with receipt R-A-1
log orders A-1 #20 step_succeeded ship
log orders A-1 #21 contribution_committed tracking
log orders A-1 #22 optional_input_absent code
email to Ann: Order A-1 is on its way, tracking T-A-1.
log orders A-1 #23 hook_called notify: on workflow success
log orders A-1 #24 journey_succeeded
the journey succeeded, with receipt R-A-1 and tracking T-A-1
audit: charge took 30, receipt R-A-1
Mia's order:
log orders A-13 #1 journey_started card, customer, gift_card, order, parcel, price
log orders A-13 #2 attempt_started lookup, attempt 1
log orders A-13 #3 service_provided lookup gets order_book from ledger, a ch22_finishing_a_journey_early::Ledger
the order book has order A-13 completed already
log orders A-13 #4 step_succeeded lookup
log orders A-13 #5 contribution_committed completed
log orders A-13 #6 hook_called already_completed: on step success of lookup
log orders A-13 #7 optional_input_absent tracking
log orders A-13 #8 optional_input_absent code
email to Mia: Order A-13 is complete.
log orders A-13 #9 hook_called notify: on workflow success
log orders A-13 #10 journey_succeeded FinishWorkflow from already_completed
the journey succeeded, with receipt none and tracking none
audit: none
```

## How it works

1. **`FinishWorkflow` ends the journey at once, as succeeded.** It is the second lifecycle, after `FailWorkflow` in [chapter 17](https://itinera-dev.org/book/rust/17-giving-up-a-step-early.md). Mia's lookup succeeded and contributed `completed`, so `already_completed` finished the journey. Her price, charge and shipping never ran: her log has no attempt of theirs, and the bank and the carriers were never asked, so Mia is charged only once.
2. **The journey says who finished it.** `journey_succeeded` names the policy whose hook returned `FinishWorkflow`, and the log prints it: `FinishWorkflow from already_completed`. Ann's `journey_succeeded` names no policy, because her journey ended by default, after its last step.
3. **Only `on step success` may finish a journey, and its type says so.** `Self::finish_workflow()` returns an `Option<OnSuccess>`, which is what `on step success` returns, and never an `Option<FailWorkflow>`, which is what `on step retry` and `on step abnormal termination` return. So a step that hasn't succeeded can never finish its journey.
4. **What the step contributed is kept.** The hook runs after the lookup's contributions are committed, so `completed` is in Mia's data bag, as in Ann's.
5. **The workflow's own hooks still run.** A journey finished early has succeeded, so `notify`'s `on workflow success` tells Mia. Her data bag holds no tracking number and no gift card's code, so she reads `Order A-13 is complete.` The receipt and the tracking of yesterday's journey belong to that journey, so `main` reads none of them in this one.
6. **The step reports what it found, and the policy decides what follows.** The lookup only says whether the order was completed. Ending the journey there is a decision about the flow, so it lives in a policy. The price, the charge and the shipping could each skip an order already completed, but each of them would then need to know about the order book, and a step added later would need to know too. **A decision about the whole journey belongs in a policy, not in every step it would stop.**

The shop no longer charges an order twice. But it still keeps the money of an order it charges and never ships, such as Ned's in [chapter 17](https://itinera-dev.org/book/rust/17-giving-up-a-step-early.md).

## Outcomes

We have shown how a step policy finishes a journey early, as succeeded, once nothing is left to do.

| The usual way                                                                                                             | With Itinera                                                                                                    |
| ------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------- |
| A process that stops early often does so through a `return` deep in the code, and nothing records which check stopped it. | `on step success` returns `FinishWorkflow`, and `journey_succeeded` names the policy that finished the journey. |
| Skipping work for a request already handled often means a check at the top of each function that does the work.           | One policy, attached to one lookup, ends the journey, and the steps it stops know nothing about it.             |
