Skipping a step
What you want to do
Kim's gift card is sent by email, so her order has nothing to ship. You want the shipping to know that, and leave her order alone, rather than book a parcel of 0 kg.
The code
The shipping now also asks whether the order is a gift card. When it is, it does nothing, and says so with Self::skip_because("nothing-to-ship"), before it books anything.
/// Ships an order through the carrier, with its receipt in the parcel, gives back its tracking
/// number, and says when the parcel has left. A parcel the carrier won't collect may be taken on
/// another attempt. A gift card has nothing to ship.
#[derive(Step)]
struct Ship {
#[input]
order: String,
#[input]
weight: i64,
#[input]
receipt: String,
#[input]
gift_card: bool,
}
#[step]
impl Ship {
fn run(
self,
#[service] carrier: &dyn Carrier,
#[contributor] mut contributor: Contributor<'_>,
#[reporter] mut reporter: StepReporter<'_>,
) -> Result<Outcome, Error> {
if self.gift_card {
return Self::skip_because("nothing-to-ship");
}
let tracking = carrier.book(&self.order, self.weight)?;
contributor.contribute("tracking", tracking);
if !carrier.collects(self.weight) {
return Self::fail("too-heavy");
}
reporter.info(format!("the parcel leaves with receipt {}", self.receipt))?;
Self::succeed()
}
}The trait Carrier it asks for is chapter 12's.
The log is chapter 15's, and now also names the reason a step gave for skipping, and the step whose contributions were thrown away:
EventBody::StepSkipped {
step,
reason: Some(reason),
..
} => Some(format!("{}: {}", step.step, reason.code())),
EventBody::ContributionsDiscarded { step, .. } => Some(step.step.to_string()),The rest of the shop is chapter 15's. It runs the same four orders, and one from a new customer, Ned, whose parcel weighs 90 kg. It prints:
Ann's order:
log orders A-1 #1 journey_started card, gift_card, order, price, weight
log orders A-1 #2 attempt_started charge, attempt 1
log orders A-1 #3 service_provided charge gets payments from bank, a ch16_skipping_a_step::Bank
the bank charges 40 for order A-1
log orders A-1 #4 step_succeeded charge
log orders A-1 #5 contribution_committed receipt
log orders A-1 #6 hook_called audit: on step success of charge
log orders A-1 #7 contribution_committed audit
log orders A-1 #8 attempt_started ship, attempt 1
log orders A-1 #9 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::MainCarrier
the main carrier books order A-1
log orders A-1 #10 step_info ship: the parcel leaves with receipt R-A-1
log orders A-1 #11 step_succeeded ship
log orders A-1 #12 contribution_committed tracking
log orders A-1 #13 journey_succeeded
the journey succeeded, with receipt R-A-1 and tracking T-A-1
audit: charge took 40, receipt R-A-1
Gil's order:
log orders A-7 #1 journey_started card, gift_card, order, price, weight
log orders A-7 #2 attempt_started charge, attempt 1
log orders A-7 #3 service_provided charge gets payments from bank, a ch16_skipping_a_step::Bank
the bank charges 45 for order A-7
log orders A-7 #4 step_succeeded charge
log orders A-7 #5 contribution_committed receipt
log orders A-7 #6 hook_called audit: on step success of charge
log orders A-7 #7 contribution_committed audit
log orders A-7 #8 attempt_started ship, attempt 1
log orders A-7 #9 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::MainCarrier
the main carrier books order A-7
log orders A-7 #10 step_failed ship: too-heavy
log orders A-7 #11 step_retrying ship
log orders A-7 #12 attempt_started ship, attempt 2
log orders A-7 #13 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::BackupCarrier
the backup carrier books order A-7
log orders A-7 #14 step_info ship: the parcel leaves with receipt R-A-7
log orders A-7 #15 step_succeeded ship
log orders A-7 #16 contribution_committed tracking
log orders A-7 #17 journey_succeeded
the journey succeeded, with receipt R-A-7 and tracking B-A-7
audit: charge took 45, receipt R-A-7
Jo's order:
log orders A-8 #1 journey_started card, gift_card, order, price, weight
log orders A-8 #2 attempt_started charge, attempt 1
log orders A-8 #3 service_provided charge gets payments from bank, a ch16_skipping_a_step::Bank
the bank declines card jo-2024: it has expired
log orders A-8 #4 step_failed charge: card-expired
log orders A-8 #5 step_rescuing charge: failure, rescued by charge_rescue
log orders A-8 #6 attempt_started charge_rescue, attempt 1
the bank says card jo-2024 was replaced by jo-2029
log orders A-8 #7 step_succeeded charge_rescue
log orders A-8 #8 contribution_committed card
log orders A-8 #9 data_overwritten card
log orders A-8 #10 step_rescued charge by charge_rescue
log orders A-8 #11 attempt_started charge, attempt 2
the bank charges 30 for order A-8
log orders A-8 #12 step_succeeded charge
log orders A-8 #13 contribution_committed receipt
log orders A-8 #14 hook_called audit: on step success of charge
log orders A-8 #15 contribution_committed audit
log orders A-8 #16 attempt_started ship, attempt 1
log orders A-8 #17 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::MainCarrier
the main carrier books order A-8
log orders A-8 #18 step_info ship: the parcel leaves with receipt R-A-8
log orders A-8 #19 step_succeeded ship
log orders A-8 #20 contribution_committed tracking
log orders A-8 #21 journey_succeeded
the journey succeeded, with receipt R-A-8 and tracking T-A-8
audit: charge took 30, receipt R-A-8
Kim's order:
log orders A-9 #1 journey_started card, gift_card, order, price, weight
log orders A-9 #2 attempt_started charge, attempt 1
log orders A-9 #3 service_provided charge gets payments from bank, a ch16_skipping_a_step::Bank
the bank charges 25 for order A-9
log orders A-9 #4 step_succeeded charge
log orders A-9 #5 contribution_committed receipt
log orders A-9 #6 contribution_committed code
log orders A-9 #7 hook_called audit: on step success of charge
log orders A-9 #8 contribution_committed audit
log orders A-9 #9 attempt_started ship, attempt 1
log orders A-9 #10 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::MainCarrier
log orders A-9 #11 step_skipped ship: nothing-to-ship
log orders A-9 #12 contributions_discarded ship
log orders A-9 #13 journey_succeeded
the journey succeeded, with receipt R-A-9 and tracking none
audit: charge took 25, receipt R-A-9
Ned's order:
log orders A-12 #1 journey_started card, gift_card, order, price, weight
log orders A-12 #2 attempt_started charge, attempt 1
log orders A-12 #3 service_provided charge gets payments from bank, a ch16_skipping_a_step::Bank
the bank charges 50 for order A-12
log orders A-12 #4 step_succeeded charge
log orders A-12 #5 contribution_committed receipt
log orders A-12 #6 hook_called audit: on step success of charge
log orders A-12 #7 contribution_committed audit
log orders A-12 #8 attempt_started ship, attempt 1
log orders A-12 #9 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::MainCarrier
the main carrier books order A-12
log orders A-12 #10 step_failed ship: too-heavy
log orders A-12 #11 step_retrying ship
log orders A-12 #12 attempt_started ship, attempt 2
log orders A-12 #13 service_provided ship gets carrier from carriers, a ch16_skipping_a_step::BackupCarrier
the backup carrier books order A-12
log orders A-12 #14 step_failed ship: too-heavy
log orders A-12 #15 step_given_up ship: retries exhausted
log orders A-12 #16 journey_failed ship
desk: order A-12 failed at ship
the journey failed, with receipt R-A-12 and tracking none
audit: charge took 50, receipt R-A-12How it works
- A skip is the third outcome. A step that runs to the end says how it went in one of three ways: it succeeded, it failed, or it skipped, meaning it decided it had nothing to do for this journey.
Self::skip()says so, andSelf::skip_because(...)adds a reason, as a failure has, which the eventstep_skippedcarries. - Skipping is the step's own decision. Only the shipping knows that a gift card has nothing to ship, so it reads
gift_cardand skips. Ann's, Gil's, Jo's and Ned's orders are not gift cards, so their shipping runs as before. A step skips only for a reason of the business it knows itself, never to hide a failure. - A skipped step leaves the flow. Nothing it contributed is kept: the executor records
contributions_discarded, even though the shipping skipped before contributing anything, and the journey moves on as if the step had not been there. The shipping was the last step, so Kim's journey succeeded, with her receipt and no tracking number. - A skip is not a failure, and is never retried. The shipping has a retry budget of 1, for failures that might be mended, and Kim's shipping never spends it: a step that skipped did what it meant to do, so the executor never tries it again.
- Its services were still provided. The shipping asks for a carrier as a parameter of
run, so the executor lends it one beforerunis called, asservice_providedshows. The shipping never uses it.
So a gift card no longer gets a parcel booked. But look at Ned's order. The main carrier won't collect a parcel of 90 kg, so the shipping is tried again with the backup carrier, which collects parcels only up to 80 kg. That retry could never help, and the shop spends it all the same.
Outcomes
We have shown how a step skips itself when it has nothing to do.
| The usual way | With Itinera |
|---|---|
| A function with nothing to do often returns early with a success or a special value, so callers cannot tell "done" from "nothing to do". | A skip is an outcome of its own, with a reason, recorded as an event, and the journey moves on. |